[ 학습 흐름 ]
[ 학습 흐름 ]
- 객체지향 프로그래밍과 클래스
- 클래스의 선언과 객체의 생성
- 정적 필드와 메소드
- 객체 복사하기
- this 키워드
- 접근 한정자로 공개 수준 결정하기
- 상속으로 코드 재활용하기
- 기반 클래스와 파생 클래스 사이의 형식 반환
- 오버라이딩과 다형성
- 메소드 숨기기
- 오버라이딩 봉인하기
- 중첩 클래스와 분할 클래스
- 확장 메소드
- 구조체
[ 객체지향 프로그래밍과 클래스 ]
[ 객체 지향 프로그래밍 ]
객체 지향 프로그래밍
- 코드 내 모든것을 객체로 표현하려는 프로그래밍 패러다임 .
- OOP (Object Oriented Programming )이라고도 함.
객체
- 세상의 모든것을 지칭
- 객체는 속성과 기능 두가지 특징이 있다 .
- C#코드에서는 속성은 데이터 , 기능은 메소드로 표현한다 . 즉 , 객체는 데이터와 메소드로 이루어진다 .
[ 클래스 ]
클래스
- 객체를 만들기 위한 청사진 .
- 클래스는 객체가 가지게 될 속성과 기능을 정의하지만 실체를 가지진 않는다 .
- 클래스를 이용해 만든 객체가 실체를 가진다 .
string a = "123";
string b = "Hello";
- a와 b는 객체이며 , string은 문자열을 다루는 클래스이다 .
- a / b 은 string 의 실체 , 즉 인스턴스라도 부른다 .
- 객체에서 뽑아낸 속성과 기능은 클래스안에 변수와 메소드로 표현된다 .
형식
- 클래스는 객체지향적인 관점에서 객체를 위한 청사진 혹은 데이터와 메소드를 묶는 집합
- 코드에서 보는 클래스는 복합 데이터 형식이다 .
[ 클래스의 선언과 객체의 생성 ]
[ 클래스의 선언 ]
선언
class Cat
{
//필드
public string Name;
public string Color;
//메서드
public void Meow
{
Console.WriteLine("{0} : 야옹",Name);
}
}
- Name/Cat 처럼 클래스 안에 선언된 변수들을 필드라 한다 .
- 필드 , 메서드를 비롯 프로퍼티,이벤트등 클래스 내에 선언된 요소들을 멤버라고 한다 .
[ 객체의 선언 ]
선언
Cat Kitty = new Cat();
kitty.Color = "White"
kitty.Name = "Kitty";
kitty.Meow();
Cat Nero = new Cat();
Nero.Color = "Black"
Nero.Name = "Nero";
Nero.Meow();
객체의 생성
Cat kitty = new Cat();
- Cat()부분은 생성자이다 .
- 생성자는 클래스의 이름과 동일하며 객체를 생성하는 역할을 한다 .
- new 키워드는 생성자를 호출 , 객체를 생성하는 연산자이다 .
형식
Cat kitty;
- 모든 클래스는 복합 데이터 형식이자 참조형식이다 .
- 위의 선언문에서 kitty는 null을 가진다 .
- kitty자체에 메모리 할당이 아닌 참조로서 객체가 있는곳을 가리킬 뿐이기 때문이다 .
- new 연산자와 생성자를 이용해 힙에 객체를 생성하고 , kitty는 생성자가 힙에 선언한 객체를 가리키다 .
[ 생성자와 종료자 ]
[ 생성자 ]
선언
class 클래스 이름
{
한정자 클래스 이름 ( 매개변수 목록 )
{
}
}
- 생성자는 클래스와 이름이 같고 반환형식이 없다
- 생성자는 클래스의 객체를 생성하는 기능뿐이기 때문이다 .
기본 생성자
- 클래스 선언시 명시적으로 생성자를 구현하지 않아도 컴파일러가 자동으로 만들어준다 .
생산자 오버로딩
class Cat
{
public string Name;
public string Color;
public Cat()
{
Name="";
Color="";
}
public Cat(string _name,string _color)
{
Name=_name;
Color=_color;
}
}
// 생성자를 이용한 인스턴스 생성
Cat Nabi = new Cat ("Nabi","Brown")
- 객체 생성시점에 객체의 필드를 원하는 값으로 초기화 하고 싶을 수 있다 .
- 이를 위해 기본 생성자 뿐 아니라 오버로딩된 생성자를 다양한 버전으로 준비 할 수 있다 .
- 생성자를 직접 정의하면 컴파일러는 매개변수 없는 기본 생성자를 제공하지 않는다 .
[ 종료자 ]
선언
class 이름
{
// 종료자
~ 이름()
{
//내용
}
}
- 클래스의 이름에 ~를 붙인다 .
- 매개변수도 없고 , 한정자도 사용하지 않는다 .
- 오버로딩도 불가하며 직접 호출 할 수 없다 .
- CLR의 가비지 컬렉터가 객체가 소멸되는 시점을 판단 , 종료자를 호출한다 .
안쓰는 이유
- CLR의 가비지 컬렉터의 동작이 언제할지 예측 할 수 없다 .
- 종료자를 명시적으로 구현하면 가비지 컬렉터는 객체로부터 상속받은 Finalize() 메서드를 호출한다 .
- 이는 응용프로그램의 성능저하를 일으키기에 권장하지 않는다 .
- 또한 CLR의 가비지 컬렉터의 객체 소멸방식이 더 똑똑하기에 가비지 컬렉터에 맡기는게 좋다 .
using System;
namespace Constructor;
class MainApp
{
class Cat
{
public Cat()
{
Name = "";
Color = "";
}
public Cat (string _name,string _color)
{
Name = _name;
Color = _color;
}
~Cat()
{
Console.WriteLine("잘가");
}
public string Name;
public string Color;
public void Meow()
{
Console.WriteLine($"{Name} : 야옹");
}
}
static void Main(string[] args)
{
Cat kitty = new Cat("키디", "하얀색");
kitty.Meow();
Console.WriteLine($"{kitty.Name} : {kitty.Color}");
Cat nero = new Cat("네로", "검정색");
nero.Meow();
Console.WriteLine($"{nero.Name} : {nero.Color}");
Console.ReadLine();
}
}
[ 정적 필드와 메소드 ]
[ 클래스의 선언 ]
staitc
- static은 메소드나 필드가 클래스의 인트턴스가 아닌 클래스 자체에 소속되도록 지정하는 한정자이다 .
정적 필드
- 어떤 필드가 클래스에 소속된다는 것은 프로그램 전체에서 유일하게 존재한다는 뜻 .
- 프로그램 전체에 걸쳐 공유해야 하는 변수가 있다면 정적 필드를 이용한다 .
class MyClass
{
public int a;
public int b;
}
class MyStaticClass
{
public static int a;
public static int b;
}
public static void Main()
{
//인스턴스 생성후 접근
MyClass obj = new MyClass();
obj.a=3;
//인스턴스 생성 없이 클래스의 이름을 통해 필드에 직접 접근
MyStaticClass.a = 3;
}
정적 메서드
- 비정적 메서드는 인스턴스 메서드라고 한다 . (인스턴스를 생성해야지만 호출 가능)
- 객체 내부 데이터를 이용해야 한다면 인스턴스 메서드를 사용한다 .
- 내부 데이터를 이용할 일이 없다면 별도의 인스턴스 생성 없이 호출 가능한 정적 메서드를 사용한다 .
class MyClass
{
public void InstanceMethod()
{
}
}
class MyStaticClass
{
public static void StaticMethod()
{
}
}
public static void Main()
{
//인스턴스 생성후 호출
MyClass obj = new MyClass();
obj.InstanceMethod();
//인스턴스 생성 없이 클래스의 이름을 통해 바로 호출
MyStaticClass.StaticMethod();
}
[ 객체 복사하기 : 얕은 복사와 깊은 복사 ]
[ 객체의 복사 ]
얕은 복사
class MyClass
{
public int MyField1;
public int MyField2;
}
MyClass source = new MyClass();
source.MyField1 =10;
source.MyField2 =20;
MyClass target = source;
target.MyField2 = 30;
Console.WriteLine("{0} , {1}",source.MyField1 , source.MyField2);
Console.WriteLine("{0} , {1}",target.MyField1 , target.MyField2);
- 위의 경우 결과값을 10 20 / 10 30으로 기대하겠지만 10 30/10 30의 결과가 발생한다 .
- 이유는 클래스가 참조 형식이기 때문이다 .
- 참조 형식은 힙 영역에 객체를 할당하고 스택에 있는 참조가 힙 영역에 할당된 메모리를 가리킨다 .
- 그렇기에 source와 target은 힙에 있는 실제 객체가 아닌 스택에 있는 참조를 복사했기에 같은 곳을 가리키는 것.
- 이렇게 객체를 복사할때 참조만 복사하는 것을 얕은 복사라고 한다 .
깊은 복사
class MyClass
{
//객체를 힙에 새로 할당해서 그곳에 자신의 멤버를 복사해 넣는다
public MyClass DeepCopy()
{
MyClass newCopy = new MyClass();
newCopy.MyField1 = this.MyField1;
newCopy.MyField2 = this.MyField2;
return newCopy;
}
}
- target이 힙에 보관되어 있는 내용을 source로부터 복사해서 별도의 힙 공간에 객체를 보관하기를 원한다 .
- 즉 ,깊은 복사를 수행하려면 위와 같이 메서드를 직접 정의해야 한다 . (C#에 자동으로 해주는 구문이 없다)
- 깊은 복사 기능을 가질 클래스는 ICLoneable이라는 인터페이스를 상속하여 Clone이라는 메서드를 구현하는게 좋다.
using System;
using System.Diagnostics;
using System.Formats.Tar;
namespace DeepCopy;
class MainApp
{
class MyClass
{
public int MF1;
public int MF2;
public MyClass DeepCopy()
{
MyClass newClass = new MyClass();
newClass.MF1 = this.MF1;
newClass.MF2 = this.MF2;
return newClass;
}
}
static void Main(string[] args)
{
{
Console.WriteLine("Shallow Copy");
MyClass source = new MyClass();
source.MF1 = 10;
source.MF2 = 20;
MyClass target = source;
target.MF2 = 30;
Console.WriteLine($"{source.MF1},{source.MF2}");
Console.WriteLine($"{target.MF1},{target.MF2}");
}
{
Console.WriteLine("Deep Copy");
MyClass source = new MyClass();
source.MF1 = 10;
source.MF2 = 20;
MyClass target = source.DeepCopy();
target.MF2 = 30;
Console.WriteLine($"{source.MF1},{source.MF2}");
Console.WriteLine($"{target.MF1},{target.MF2}");
}
}
}
[ this 키워드 ]
[ this ]
- 객체가 자신을 지칭할 때 사용하는 키워드
- 객체 내부에서는 자신의 필드 / 메서드에 접근시 this 키워드를 사용한다
- 매개변수와 필드의 이름이 같아 모호할때 사용한다 .
class Employee
{
private string Name;
public void SetName( string Name )
{
this.Name = Name;
}
}
[ this 생성자 ]
class MyClass
{
int a,b,c;
public MyClass()
{
this.a = 5425;
}
public MyClass(int b)
{
this.a = 5425;
this.b = b;
}
public MyClass(int b,int c)
{
this.a = 5425;
this.b = b;
this.c = c;
}
}
- MyClass 생성자안에 똑같은 코드가 겹쳐있다 .
- 각각의 생성자에서 겹치지 않는 부분만 호출하기위해 new 연산자로 생성자를 호출하려면 새로운 객체를 만들어야한다 .
- this() 키워드는 자기 자신의 생성자를 가리킨다 .
- this()는 생성자의 코드블록 내부가 아닌 앞쪽에서만 사용 가능하다 .
class MyClass
{
int a,b,c;
public MyClass()
{
this.a = 5425;
}
//MyClass()를 호출
public MyClass(int b):this()
{
this.b = b;
}
//MyClass(int)를 호출, 넘기고자 하는 인자를 this( 여기 )에 넣어준다
public MyClass(int b,int c):this(b)
{
this.c=c;
}
}
[ 접근 한정자로 공개 수준 결정하기 ]
[ 은닉성 ]
- 객체 지향 프로그래밍은 클래스의 사용자에게 필요한 최소기능만 노출하고 내부로 감출 것을 요구한다 .
- 특히 필드는 상수를 제외하고는 무조건 감추는게 좋다 .
[ 접근 한정자 ]
- 감추고 싶은것은 감추고 , 보여주고 싶은 것은 보여줄 수 있도록 코드를 수식한다 .
- 필드 , 메소드 , 프로퍼티 등 모든 요소에 사용 가능하다 .
- 접근 한정자로 수식하지 않은 클래스의 멤버는 private로 접근 수준이 자동 지정된다 .
접근 한정자 | 설명 |
public | 클래스의 내부/외부 모든곳에서 접근 가능하다 . |
protected | 클래스의 외부에서는 접근 불가 , 파생 클래스에서는 접근 가능하다 , |
private | 클래스의 내부에서만 접근 할 수 있다. |
internal | 같은 어셈블리에 있는 코드에서만 public으로 접근가능 다른 어셈블리에 있는 코드에서는 private과 같은 수준의 접근성을 지닌다 . |
protected internal | 같은 어셈블리에 있는 코드에서만 protected 로 접근 할 수 있다 . |
private protected | 같은 어셈블리에 있는 클래스에서 상속받은 클래스 내부에서만 접근 가능하다 . |
using System;
namespace AccessModifier;
class WaterHeater()
{
protected int temperature;
public void SetTemperature(int temperature)
{
if (temperature < -5 || temperature > 42)
{
throw new Exception("Out of Temperature range");
}
this.temperature = temperature;
}
internal void TurnOnWater()
{
Console.WriteLine($"Turn on the water : {temperature}");
}
}
class MainApp
{
static void Main(string[] args)
{
try
{
WaterHeater heater = new WaterHeater();
heater.SetTemperature(20);
heater.TurnOnWater();
heater.SetTemperature(-2);
heater.TurnOnWater();
heater.SetTemperature(50);
heater.TurnOnWater();
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
}
}
[ 상속으로 코드 재활용하기 ]
[ 상속 ]
class 기반 클래스
{
//멤버 선언
}
class 파생 클래스 : 기반 클래스
{
//아무 멤버가 없어도 기반 클래스의 모든것을 물려받는다
//다만, private 로 선언된 멤버는 제외된다
}
- 클래스는 다른 클래스로부터 유산을 물려 받을 수 있다
- 파생 클래스는 객체 생성시 내부적으로 기반 클래스의 생성자를 호출한 후 자신의 생성자를 호출
- 소멸시에는 파생클래스 - 기반 클래스의 순서로 종료자를 호출한다 .
- 즉 , 기반 클래스 생성자 - 파생 클래스 생성자 - 파생 클래스 종료자 - 기반 클래스 종료자의 순서로 호출된다 .
[ 매개변수의 전달 ]
-기반 클래스의 생성자가 매개변수를 입력받도록 선언되었다면 , base 키워드를 사용하여 넘겨 줄 수 있다 .
class Base
{
protected string Name;
public Base(string Name)
{
this.Name=Name;
}
}
class Derived : Base
{
public Deprived(string Name):base(Name)
{
}
}
using System;
namespace Inheritance;
class Base
{
protected string Name;
public Base(string name)
{
this.Name = name;
Console.WriteLine($"{this.Name} Base()");
}
~Base()
{
Console.WriteLine($"{this.Name} ~ Base()");
}
public void BaseMethod()
{
Console.WriteLine($"{this.Name} BaseMethod()");
}
}
class Derived : Base
{
public Derived(string name):base(name)
{
Console.WriteLine($"{this.Name} Derived()");
}
~Derived()
{
Console.WriteLine($"{this.Name} ~Derived()");
}
public void DerivedMethod()
{
Console.WriteLine($"{this.Name} DerivedMethod()");
}
}
class MainApp
{
static void Main(string[] args)
{
Base a = new Base("a");
a.BaseMethod();
Derived b= new Derived("b");
b.BaseMethod();
b.DerivedMethod();
}
}
[ 기반 클래스와 파생 클래스 사이의 형식 변환 ]
[ 상속 ]
class Mammal
{
public void Nurse(){ .. }
}
class Dog : Mammal
{
public void Bark(){ .. }
}
class Cat:Mammal
{
pubilc void Meow(){ .. }
}
Mammal mammal = new Mammal();
mamal.Nurse();
mammal = new Dog();
mammal.Nurse();
Dog dog = (Dog)mammal;
dog.Nurse();
dog.Bark();
mammal = new Cat();
mammal.Nurse();
Cat cat = (Cat)mammal;
cat.Nurse();
cat.Meow();
- 다음과 같이 포유류는 포유류 / 개도 포유류 / 고양이도 포유류이다 .
- 기반 클래스와 파생 클래스 사이에서는 족보를 오르내리는 형식 변환이 가능하다 .
- 파생 클래스의 인스턴스는 기반 클래스의 인스턴스로도 사용 가능하다 .
- 이는 코드의 생산성을 높일수 있다 . ex) 각 동물들을 받는 메서드가 아닌 mammal 하나만 받는 메서드
[ is , as 연산자 ]
연산자 | 설명 |
is | 객체가 해당 형식에 해당하는지 검사하여 그 결과를 bool 값으로 반환한다 . |
as | 형식 변환 연산자와 같은 역할이지만 형식 변환 연산자느 변환 실패시 예외를 던지는 반면 , as 연산자는 객체 참조를 null로 만든다는 것이 다르다 . |
- 일반적으로 형식변환 대신 as 연산자를 권장한다 .
- as 연산자는 참조형식에만 사용가능하다 .
- 형식 변환에 실패하더라도 예외로 코드의 실행이 점프하는 일이 없어 코드 관리가 더 수월하다 .
using System;
namespace TypeCasting;
class Mammal
{
public void Nurse()
{
Console.WriteLine("Nurse()");
}
}
class Dog:Mammal
{
public void Bark()
{
Console.WriteLine("Bark()");
}
}
class Cat:Mammal
{
public void Meow()
{
Console.WriteLine("Meow()");
}
}
class MainApp
{
static void Main(string[] args)
{
Mammal mammal = new Dog();
Dog dog;
if(mammal is Dog)
{
dog = (Dog)mammal;
dog.Bark();
}
Mammal mammal2 = new Cat();
Cat cat = mammal2 as Cat;
cat?.Meow();
Cat cat2 = mammal as Cat;
if (cat2 != null)
cat2.Meow();
else
Console.WriteLine("Cat2 is Not a Cat");
}
}
[ 오버라이딩과 다형성 ]
[ 다형성 ]
- 다형성은 객체가 여러 형태를 가질 수 있음을 의미한다 .
- 다형성은 원래 하위 형식 다형성이라 불리는데 이는 파생 클래스를 통해 다형성을 실현한다는 것이다 .
- 메소드를 재정의하는 것을 오버라이딩이라 한다 .
- 이를 위해 기반 클래스의 메서드는 virtual / 파생 클래스의 오버라이딩 메서드는 override로 한정해야 한다 .
- 기반 클래스의 메서드는 base. 으로 호출 가능하다 .
using System;
namespace Overriding;
class ArmorSuite
{
public virtual void Initialize()
{
Console.WriteLine("Armored");
}
}
class IronMan: ArmorSuite
{
public override void Initialize()
{
base.Initialize();
Console.WriteLine("Repulsor Rays Armed");
}
}
class WarMachine: ArmorSuite
{
public override void Initialize()
{
base.Initialize();
Console.WriteLine("Micro - Rocket Laucnher Armed");
}
}
class MainApp
{
static void Main(string[] args)
{
ArmorSuite armorsuite = new ArmorSuite();
armorsuite.Initialize();
ArmorSuite ironMan=new IronMan();
ironMan.Initialize();
ArmorSuite warMachine = new WarMachine();
warMachine.Initialize();
}
}
[ 메소드 숨기기 ]
[ 메소드 숨기기 ]
- 메소드 숨기기는 CLR에게 기반 클래스에서 구현된 버전의 메소드를 감추고 파생 클래스에서 구현된 버전만 보여준다 .
- 이는 파생 클래스의 메소드를 new 한정자로 수식함으로 할 수 있다.
- 완전한 다형성을 표현하지 못하는 한계가 있다 .
[ 메소드 숨기기 vs 오버라이딩 ]
오버라이딩(Overriding)
- 파생클래스에서 기반클래스에 이미 정의된 메서드를 다시 정의하는 것.
- 파생클래스에서 오버라이딩된 메서드는 기반 클래스의 메서드를 가리킨다.
- 따라서, 객체의 실제 타입에 따라 호출되는 메서드가 결정되며 이것이 다형성의 핵심이다 .
- 오버라이딩은 부모 클래스의 메서드와 동일한 시그니처(메서드 이름, 매개변수 타입 및 개수)를 가져야 한다 .
메서드 숨기기(Method Hiding):
- 파생클래스에서 기반 클래스의 메서드와 동일한 이름을 가진 새로운 메서드를 정의하는 것.
- 메서드 숨기기는 오버라이딩과는 달리 new 키워드를 사용하여 선언한다.
- 메서드 숨기기된 메서드는 부모 클래스의 메서드를 가리키지 않는다 .
- 따라서, 호출은 변수의 선언된 타입에 따라 결정됩니다.
- 부모 클래스의 메서드와 동일한 이름을 가지지만, 시그니처가(매개변수 타입 및 개수가 다를 수 있음)다를 수 있다 .
결론
- 오버라이딩은 다형성을 지원하고 서브클래스에서 부모 클래스의 동작을 대체하는 반면,
메서드 숨기기는 다형성을 지원하지 않고 호출은 변수의 선언된 타입에 따라 결정된다 .
using System;
namespace MethodHiding;
class Base
{
public void MyMethod()
{
Console.WriteLine("Base.MyMethod");
}
}
class Derived:Base
{
public new void MyMethod()
{
Console.WriteLine("Deprived.MyMethod");
}
}
class MainApp
{
static void Main(string[] args)
{
Base baseObj = new Base();
baseObj.MyMethod();
Derived baseObj2 = new Derived();
baseObj2.MyMethod();
Base baseOrDerived = new Derived();
baseOrDerived.MyMethod();
}
}
[ 오버라이딩 봉인하기 ]
[ 오버 라이딩 봉인 ]
- 클래스를 봉인하는 것처럼 메소드도 sealed 키워드로 봉인 할 수 있다 .
- virtual로 선언된 가상 메서드를 오버라이딩한 버전의 메소드만 가능하다 .
- 봉인 메소드는 파생 클래스의 작성자르 위한 기반 클래스 작성자의 배려이다 .
- 오버라이딩 한 메소드는 파생 클래스의 파생 클래스에서도 자동으로 오버라이딩이 가능하기에 이를 막을 브레이크이다 .
class Base
{
public virtual void SealMe(){}
}
class Derived:Base
{
public sealed override void SealMe(){}
}
class WantToOverride:Derived
{
//컴파일 에러가 발생함 .
public override void SealMe(){}
}
[ 읽기 전용 필드 ]
[ 읽기 전용 필드 ]
- 읽기 전용 필드는 상수와 변수 그 중간어딘가의 필드
- 클래스나 구조체의 멤버로만 존재하며 , 생성자 안에서 한번 값을 지정하면 그 후로는 값 변경이 안된다 .
- 읽기 전용 필드는 생성자 안에서만 초기화 가능하며 그 외에 수정 시도가 발생시 컴파일 에러가 발생한다.
using System;
using System.Collections.ObjectModel;
namespace readOnlyField;
class Configuration
{
readonly int min;
readonly int max;
public Configuration(int min, int max)
{
this.min = min;
this.max = max;
}
public void ChangeMax(int max)
{
//생성자 이외의 곳에서 값을 수정하려 하니 컴파일 에러 발생
//this.max = max;
}
}
class MainApp
{
static void Main(string[] args)
{
Configuration c = new Configuration(100, 10);
}
}
[ 중첩 클래스 ]
[ 중첩 클래스 ]
class OuterClass
{
private int OuterMember;
class NestedClass
{
public void DoSomething()
{
OuterClass outer = new OuterClass();
outer.OuterMember= 10;
}
}
}
- 클래스 안에 선언되어 있는 클래스
- 자신이 소속된 클래스의 맴버에 자유롭게 접근 가능하다 . (private 포함)
[ 중첩 클래스의 사용 이유 ]
- 클래스 외부에 공개하고 싶지 않은 형식을 만들때
- 현재 클래스의 일부분처럼 표현할 수 있는 클래스를 만들고자 할 때
- 다른 클래스의 private 멤버에 접근 가능하다는 점에서 중첩 클래스는 은닉성을 무너뜨리지만 , 개발자에게 유연한 표현력을 제공한다 .
using System;
using System.Collections.Generic;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
namespace NestedClass;
class Configuration
{
List<ItemValue> listConfig = new List<ItemValue>();
public void SetConfig(string item,string value)
{
ItemValue iv = new ItemValue();
iv.SetValue(this, item, value);
}
public string GetConfig(string item)
{
foreach (ItemValue iv in listConfig)
{
if (iv.GetItem() == item)
return iv.GetValue();
}
return "";
}
class ItemValue
{
string item;
string value;
public void SetValue(Configuration config,string item,string value)
{
this.item = item;
this.value = value;
bool found = false;
for(int i=0;i<config.listConfig.Count;i++)
{
if (config.listConfig[i].item == item)
{
config.listConfig[i] = this;
found = true;
break;
}
}
if(found==false)
config.listConfig.Add(this);
}
public string GetItem() { return item; }
public string GetValue() { return value; }
}
}
class MainApp
{
static void Main(string[] args)
{
Configuration config = new Configuration();
config.SetConfig("Version", "v 5.0");
config.SetConfig("Size", "655,324KB");
Console.WriteLine(config.GetConfig("Version"));
Console.WriteLine(config.GetConfig("Size"));
config.SetConfig("Version", "v 5.01");
Console.WriteLine(config.GetConfig("Version"));
}
}
[ 분할 클래스 ]
[ 분할 클래스 ]
- 여러번 나눠서 구현하는 클래스
- 클래스의 구현이 길어질 경우 여러 파일에 나눠서 구현할 수 있게 함 (소스 코드 관리의 편의)
- 컴파일러는 분할 구현된 코드를 나로 묶어 컴파일 한다
using System;
using System.Collections.Generic;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
namespace PartialClass;
partial class MyClass
{
public void Method1()
{
Console.WriteLine("1");
}
public void Method2()
{
Console.WriteLine("2");
}
}
partial class MyClass
{
public void Method3()
{
Console.WriteLine("3");
}
public void Method4()
{
Console.WriteLine("4");
}
}
class MainApp
{
static void Main(string[] args)
{
MyClass obj = new MyClass();
obj.Method1();
obj.Method2();
obj.Method3();
obj.Method4();
}
}
[ 확장 메소드 ]
[ 확장 메소드 ]
- 기존 클래스의 기능을 확장하는 기법
- 메소드를 선언 , static 한정자로 수식하고 메소드의 첫번째 매개변수는 this 키워드화 함께 확장하려는 클래스(형식)
의 인스턴스
- 선언을 위한 클래스도 static 한정자로 수식해야 한다 .
using System;
using MyExtension;
namespace MyExtension
{
public static class IntegerExtensioin
{
public static int Square(this int myInt)
{
return myInt * myInt;
}
public static int Power(this int myInt,int exponent)
{
int result = myInt;
for(int i=1;i<exponent; i++)
{
result*= myInt;
}
return result;
}
}
public static class StringExtension
{
public static string Append(this string myString,string appendString)
{
return myString+appendString;
}
}
}
namespace ExtensionMethod
{
class MainApp
{
static void Main(string[] args)
{
Console.WriteLine($"3^2 : {3.Square()}");
Console.WriteLine($"3^4 : {3.Power(4)}");
Console.WriteLine($"2^10 : {2.Power(10)}");
string hello = "Hello";
Console.WriteLine(hello.Append(",World"));
}
}
}
[ 구조체 ]
[ 구조체 ]
struct MyStruct
{
public int MyField1;
public int MyField2;
public void MyMethod()
{
}
}
- c#의 복합 데이터 형식
- struct 키워드를 이용하여 선언한다 .
- 클래스는 실세계의 객체를 추상화하는데 목적을 두고 , 구조체는 데이터를 담기 위한 자료구조로 사영된다
- 클래스는 은닉성을 위해 필드를 private로 선언했지만 , 구조체는 데이터를 담기에 편의를 위해 public으로 선언하는 편
[ 구조체와 클래스 ]
특징 | 클래스 | 구조체 |
키워드 | class | struct |
형식 | 참조형식 (힙에 할당) | 값 형식 (스택에 할당) |
복사 | 얕은 복사(Shallow Copy) | 깊은 복사(Deep Copy) |
인스턴스 생성 | new 연산자 , 생성자 필요 | 선언만으로 생성 |
생성자 | 매개변수 없는 생성자 가능 | 매개변수 없는 생성자 선언 불가능 |
상속 | 가능 | 값 형식이므로 상속 불가 |
- 구조체는 값 형식이기에 스택에 할당 . 인스턴스가 선언된 블록이 끝나는 지점에 메모리가 사라진다 .
- 가비지 콜렉터를 사용하지 않기에 클래스에 비해 성능에 이점이 있다.
[ 구조체의 복사 ]
MyStruct s;
s.MyField1 = 1;
s.MyField2 = 2;
MyStruct t;
t=s;
s.MyField1 =3;
//s의 MyField 1은 3 이지만 t의 MyField1은 1이다 .
- 구조체는 값 형식이기에 할당 연산자 = 를 통해 모든 필드가 그대로 복사된다 .
[ 구조체의 인스턴스 생성 ]
- 구조체는 생성자를 호출할때가 아니면 굳이 new 연산자를 이용하지 않아도 인스턴스를 만들 수 있다 .
- 구조체는 매개변수 없는 생성자는 선언할 수 없지만 각 필드는 CLR이 기본값으로 초기화해준다 .
using System;
namespace Structure;
struct Point3D
{
public int x;
public int y;
public int z;
public Point3D(int x, int y, int z)
{
this.x = x;
this.y = y;
this.z = z;
}
//Systme.Object 형식의 ToString()메소드를 오버라이딩
public override string ToString()
{
return string.Format($"{x},{y},{z}");
}
}
class MainApp
{
static void Main(string[] args)
{
//선언만으로 인스턴스가 생성됨
Point3D p3D1;
p3D1.x = 10;
p3D1.y = 20;
p3D1.z = 40;
Console.WriteLine( p3D1.ToString() );
//생성자를 통한 인스턴스 생성 역시 가능하다
Point3D p3D2=new Point3D(100,200,300);
//구조체의 인스턴스를 다른 인스턴스에 할당하면 깊은 복사가 이루어진다
Point3D p3D3 = p3D2;
p3D3.z = 4000;
Console.WriteLine(p3D2.ToString());
Console.WriteLine(p3D3.ToString());
}
}
[ 변경 불가능 객체 ]
- 객체는 속성 (상태)과 기능(행위)로 구성된다 .
- 객체의 속성(상태)는 필드와 프로퍼티를 통해 표현된다 .
- 상태의 변화를 혀용하는 객체를 변경가능 객체 , 상태의 변화를 허용하지 않는 객체를 변경불가능 객체라 한다 .
[ 변경 불가능 객체의 장점 ]
- 멀티 스레드 간의 동기화를 할 필요가 없어 프로그램 성능이 향상된다 .
- 모든 필드와 프로퍼티의 값을 수정 할 수 없다 .
- 이로 인해 버그로 인한 상태(데이터)오염을 막을 수 있다
[ 변경 불가능 구조체 ]
readonly struct 구조체 이름
{
public readonly int ImmutableField;//OK
public int MutableField;//컴파일 에러 발생!
public 구조체 이름(int value)
{
ImmutableField = value;//생성자에서만 초기화 가능하다
}
}
class SomeClass
{
public void SomeMethod()
{
ImmutableStruct is = newImmutable(123);
is,ImmutableField= 100;//컴파일 에러 발생!
}
}
- 구조체에 readonly 키워드를 붙임으로 변경불가능 구조체로 선언이 가능하다 .
- 반면 클래스는 변경 불가능으로 선언 할 수 없다 .
- 컴파일러는 해당 구조체의 모든 필드가 readonlt로 선언되도록 강제한다 . 선언 안된 프로퍼티는 컴파일 에러를 일으킨다.
- 읽기전용 필드를 수정하려는 시도 역시 컴파일 에러가 발생한다 .
using System;
namespace ReadOnlyStruct;
readonly struct RGBColor
{
public readonly byte R;
public readonly byte G;
public readonly byte B;
public RGBColor(byte r,byte g,byte b)
{
this.R = r;
this.G = g;
this.B = b;
}
}
class MainApp
{
static void Main(string[] args)
{
RGBColor Red = new RGBColor(255, 0,0);
//Red.G = 100; 컴파일 에러 발생
//Red 객체에서 G의 값만 100으로 바꾸려면 새로운 객체를 만들어야 한다 .
RGBColor myColor = new RGBColor(Red.R, 100, Red.B);
}
}
[ 읽기 전용 메서드 ]
- 읽기 전용 메서드 역시 구조체에서만 선언 할 수 있다 .
- 읽기 전용 메서드에서 객체의 필드를 바꾸려 들면 컴파일 에러가 발생한다 .
using System;
namespace ReadOnlyMethod;
struct ACSetting
{
public double currentCelcius;
public double target;
public readonly double GetFahrenheit()
{
//화씨 계산 결과를 target에 저장하면 컴파일 에러 발생
//target = currentCelcius * 1.8 + 32;
return currentCelcius * 1.8 + 32;
}
}
class MainApp
{
static void Main(string[] args)
{
ACSetting acs;
acs.currentCelcius = 25;
acs.target = 25;
Console.WriteLine($"{acs.GetFahrenheit()}");
Console.WriteLine($"{acs.target}");
}
}
[ 튜플 ]
[ 튜플 ]
- 여러 필드를 담을 수 있는 구조체
- 튜플은 형식 이름이 없어 , 응용 프로그램 전체에서 사용할 형식을 선언할 때가 아닌 ,
즉석에서 사용할 복합 데이터 형식에 적합하다.
- 구조체이므로 값 형식이다 .
//컴파일러가 튜플의 모양을 보고 직접 형식을 결정하도록 var를 이용해 선언
//튜플은괄호사이에 두개 이상의 필드를 지정함으로써 만들어짐
var tuple = (123,789);
Console.WriteLine($"{tuple.Item1},{tuple.Item2}");
- 위와 같은 형식을 명명되지 않은 튜플이라 칭한다 .
var tuple = (Name:"박상현",Age:17);
Console.WriteLine($"{tuple.Name},{tuple.Age]");
- 위와 같은 형식을 명명된 튜플이라 한다 .
var tuple = (Name:"박상현",Age:17);
var (name,age)=tuple;//튜플의 분해
Console.WriteLine($"{name},{age}");
var (name,_)=tuple;//Age 필드 무시
-위와 같이 튜플을 분해 할 수도 있다 .
- 또한 _ 연산자로 분해할때 특정 필드를 무시 할 수 있다 .
var (name2,age2) = ("박문수",34);
- 튜플의 분해를 이용해서 여러 변수를 단번에 생성하고 초기화 할 수 있다 .
var unnamed = ("수퍼맨",9999);
var named = (Name:"박상현",/Age : 17);
named = unnamed;//할당 가능
Console.WriteLine($"{name.Name},{name.Age}");//출력결과 : 수퍼맨,9999
- 명명된 튜플과 명명되지 않은 튜플간에 필드의 수와 형식이 같다면 할당이 가능하다 .
using System;
namespace Tuple;
class MainApp
{
static void Main(string[] args)
{
var a = ("슈퍼맨", 9999);
Console.WriteLine($"{a.Item1},{a.Item2}");
var b = (Name: "박상현", Age: 17);
Console.WriteLine($"{b.Name},{b.Age}");
var (name, age) = b;
Console.WriteLine($"{name},{age}");
var (name2, age2) = ("박문수",1);
Console.WriteLine($"{name2},{age2}");
b = a;
Console.WriteLine($"{b.Name},{b.Age}");
}
}
[ 튜플과 switch ]
- 튜플이 분해가 가능한 이유는 분해자를 구현하고 있기 때문이다 .
- 분해 결과는 switch 문/식의 분기 조건에 사용 가능하다 .
- 위치 패턴 매칭이라고 한다 .
- 이는 식별자나 데이터 형식이 아닌 분해된 요소의 위치에 따라 값이 일치하는지 판단하는 것
using System;
namespace PositionalPattern;
class MainApp
{
static double GetDiscountRate(object client)
{
return client switch
{
("학생", int n) when n < 18 => 0.2,
("학생", _) => 0.1,
("일반", int n) when n < 18 => 0.1,
("일반", _) => 0.05,
_ => 0,
};
}
static void Main(string[] args)
{
var alice = (Job: "학생", Age: 17);
var bob = (Job: "학생", Age: 23);
var charlie = (Job: "일반", Age: 15);
var dave = (Job: "일반", Age: 21);
Console.WriteLine($"alice : {GetDiscountRate(alice)}");
Console.WriteLine($"bob : {GetDiscountRate(bob)}");
Console.WriteLine($"charlie : {GetDiscountRate(charlie)}");
Console.WriteLine($"dave : {GetDiscountRate(dave)}");
}
}
'C# > 이것이 C#이다' 카테고리의 다른 글
[ 이것이 C#이다 ] Chapter 09 . 프로퍼티 (0) | 2024.03.12 |
---|---|
[ 이것이 C#이다 ] Chapter 08 . 인터페이스와 추상 클래스 (2) | 2024.03.12 |
[ 이것이 C#이다 ] Chapter 06 . 메소드로 코드 간추리기 (0) | 2024.03.04 |
[ 이것이 C#이다 ] Chapter 05 . 코드의 흐름 제어하기 (0) | 2024.02.28 |
[ 이것이 C#이다 ] Chapter 04 . 데이터를 가공하는 연산자 (0) | 2024.02.27 |