클래스, 캡슐화, 상속, 다형성을 구현하는 것으로 C# 언어가 객체지향 언어라고 말할 수 는 있지만, 현실 세계에서 프로그래밍 하려면 좀 더 다양한 요소가 필요하다.
타입 유형 확장
중첩 클래스
중첩 클래스(nested class는 클래스 내부에 또 다른 클래스를 정의하는 것이다.
중첩 클래스가 필요한가?
ex) 하드디스크는 내부에 플래터(platter)라고 하는 원형 금속판을 여러 개 포함하고 있으며, 헤드(head)가 그 금속판 위에 데이터를 읽고 쓰는 동작을 수행한다. 이를 C#으로 표현하면 3개의 클래스로 만들 수 있다.
class Platter
{
}
class Head
{
}
public class HardDisk
{
platter [] platter;
Head head;
}
하지만 플래터와 헤드는 다른 구성 요소에 재사용되기보다는 하드디스크 전용으로 내장되는 것이 일반적이다. 그런데 위와 같이 3개의 클래스를 나누어 정의하면 Platter와 Head가 다른 클래스에서 사용되는 것을 막을 수 없다. - 개발자가 실수할 여지가 남게 된다. 따라서 개념상으로 보면 하드디스크 내부로 정의를 제한하는 것이 더 올바른 표현일 수 있다.
public class HardDisk
{
class Platter
{
}
class Head
{
}
platter [] platter;
Head head;
}
class인 경우 접근 제한자를 생략하면 기본적으로 internal이 지정된다.
중첩 클래스의 경우 접근 제한자가 생략되면 다른 멤버와 마찬가지로 private가 지정되어 외부에서 인스턴스를 직접 생성하는 것이 불가능해진다.
HardDisk.Head head = new HardDisk.Head(); //접근 제한 컴파일 오류
중첩 클래스를 외부에서 사용하고 싶다면 명시적으로 public 접근 제한자를 지정해야 한다.
추상 클래스
메서드 오버라이드는 일반적으로 virtual 메서드를 정의한 부모 클래스에서 그에 대한 기본적인 기능을 구현하고 자식 클래스에서는 override 예약어를 이용해 그 기능을 재정의한다.
한 부모 클래스와 자식 클래스 모두 new를 이용해 인스턴스를 생성하는 것이 가능하다. 그런데 때로는 부모 클래스의 인스턴스를 생성하지 못하게 하면서 ㅌ특정 메서드에 대해 자식들이 반드시 재정의하도록 강제하고 싶을 수 있다.
추상 클래스(abstract class)와 추상 메서드(abstract method)는 그와 같은 상황을 위해 존재한다.
추상 메서드는 abstract 예약어가 지정되고 구현 코드가 없는 메서드를 말한다.
추상 메서드는 일반 클래스에 존재할 수 없으며, 반드시 추상 클래스 안에서만 선언할 수 있다.
abstract 예약어가 지정된 추상 메서드를 다른 말로 정의하면 '코드 없는 가상 메서드(virtual method)'라고 이해하면 된다.
- 추상 메서드에는 접근 제한자로 private를 지정할 수 없다. 추상 메서드는 반드시 자식 클래스에서 재정의해야 한다는 점을 감안해보면 이해할 수 있다.
추상 클래스는 abstract 예약어가 지정되어 있다는 점을 제외하면 일반 클래스 정의와 완전 동일하다.
이 예약어로 인해 일반 클래스와 차별화 되는 점
- new를 사용해 인스턴스로 만들 수 없다.
- 추상 메서드를 가질 수 있다.
- 추상 클래스를 new로 인스턴스화 할 수 없는 것은 추상 클래스 내부에 구현 코드가 없는 메서드, 즉 추상 메서드가 있기 때문이다.
- 만약 추상 클래스가 new를 통해 존재한다면 추상 메서드를 호출하는 경우 어떤 식으로 동작할 지 예측할 수 없을 것이다.
- 추상 클래스에 반드시 추상 메서드가 포함되어 있어야 하는 것은 아니지만 그래도 추상 크랠스는 new로 인스턴스화 할 수 없다.
어떤 경우에 추상 클래스가 필요한가?
class Point
{
int x, y;
public Point(int x, int y)
{
this.x = x; this.y = y;
}
public override string ToString()
{
return "X: " + x + "Y: " + y;
}
}
abstract class DrawingObject //추상 클래스
{
public abstract void Draw(); //추상 메서드(코드 없는 가상 메서드)
public void Move() { Console.WriteLine("Move"); } //일반 메서드도 정의 가능
}
class Line : DrawingObject //추상 클래스를 상속받는 Line 클래스
{
Point pt1, pt2;
public Line(Point pt1, Point pt2)
{
this.pt1 = pt1;
this.pt2 = pt2;
}
public override void Draw() //추상 클래스의 추상 메서드를 반드시 정의해야 함
{
Console.WriteLine("Line " + pt1.ToString() + " ~ " + pt2.ToString());
}
}
DrawingObjet line = new Line(new Point(10, 10), new Point(20, 20));
line.Draw(); //다형성에 따라 Line.Draw가 호출됨.
추상 메서드는 가상 메서드에 속하기 때문에 자식 클래스에서 override 예약어를 사용해 재정의한다.
가상 메서드이므로 다형성의 특징이 그대로 적용된다.
부모클래스에서 Draw 동작을 미리 정의해두는 것도 가능하지 않기 때문에 추상 클래스와 추상 메서드를 조합한 것이다.
코드가 비어있는 가상 메서드와 일반 클래스의 조합으로 정의해도 무방하지만 가상 메서드는 자식 클래스에서 재정의하지 않아도 컴파일할 때 오류가 발생하지 않지만 추상 클래스의 추상 메서드는 자식 클래스에서 반드시 재정의해야만 컴파일된다.
컴파일 단계부터 재정의를 강제하고 싶을 때 유용하게 사용할 수 있는 것이 바로 추상 클래스와 추상 메서드다.
델리게이트
타입은 '값'을 담을 수 있다. 그렇다면 그 '값'의 범위에 '메서드'도 포함될 수 있는가?
short형 변수가 short 값 범위의 값을 가리키는 것처럼 다음과 같은 메서드가 정의된 경우, 메서드 자체를 값으로 갖는 타입도 가능하다.
public class Disk
{
public int Clean(object arg)
{
Console.WriteLine("작업 실행");
return 0;
}
}
Disk disk = new Disk();
[타입] cleanFunc = new [타입](disk.Clean); //메서드를 인자로 갖는 타입의 인스턴스 생성
이렇게 메서드를 가리킬 수 있는 타입을 C#에서는 델리게이트(delegate)라는 구문으로 제공한다.
델리게이트 타입을 만드는 방법은 일반적인 class 구문이 아니고 delegate라는 예약어로 표현한다.
- 접근제한자 delegate 대상_메서드의_반환타입 식별자(...대상_메서드의_매개변수_목록...);
- 설명: 대상이 될 델리게이트 메서드의 반환 타입 및 매개변수 목록과 일치하는 델리게이트 타입을 정의한다.
- 참고로 C/C++ 개발자에게는 델리게이트를 간단하게 함수 포인터라고 설명한다. →C# 9.0에서는 델리게이트보다 성능을 향상시킨 함수 포인터 구문이 제공된다.
delegate를 정의하는 방법
FuncDelegate라는 이름의 타입이 정의되었고, 이 타입은 int 반환값과 object 인자를 하나 받는 메서드를 가리킬 수 있다.
Disk disk = new Disk();
FuncDelegate cleanFunc = new FuncDelegate(disk.clean);
C# 2.0부터는 Delegate 타입을 좀 더 쉽게 사용할 수 있는데, new 없이 마치 일반 숫자형 타입처럼 대입할 수 있는 문법을 제공한다.
FuncDelegate cleanFunc = new FunckDelegate(disk.Clean);
FuncDelegate workFunc = disk.Clean;
- 관례적으로 델리게이트 타입의 이름은 끝에 Delegate라는 접미사를 붙인다.
이처럼 메서드를 가리키는 타입의 인스턴스인 cleanFunck라는 변수로 어떤 역할을 할 수 있나?
메서드를 가리키고 있으니 당연히 그 메서드를 호출하는 역할을 한다.
즉 다음의 두 구문은 완전히 동일한 실행 결과를 나타낸다.
Disk disk = new Disk();
FuncDelegate cleanFunc = disk.Clean;
disk.Clean(null); //Clean 메서드를 직접 호출
cleanFunc(null); //델리게이트 인스턴스를 통해 Clean 메서드를 호출
인스턴스가 메서드를 호출할 수 있다는 점을 제외하고 델리게이트는 완전한 타입에 속한다.
델리게이트를 담는 배열도 만들 수 있고, 시그니처가 동일한 메서드라면 인스턴스/정적 유형에 상관없이 모두 가리킬 수 있다.
namespace ConsoleApp2;
public class Mathmatics
{
delegate int CalcDelegate(int x, int y);
static int Add(int x, int y) { return x + y; }
static int Subtract(int x, int y) { return x - y; }
static int Multiply(int x, int y) { return x * y; }
static int Divide(int x, int y) { return x / y; }
CalcDelegate[] methods;
public Mathmatics()
{
//static 메서드를 가리키는 델리게이트 배열 초기화
methods = new CalcDelegate[] { Mathmatics.Add, Mathmatics.Subtract, Mathmatics.Multiply, Mathmatics.Divide };
}
public void Calculate(char opCode, int operand1, int operand2)
{
switch(opCode)
{
case '+':
Console.WriteLine("+: " + methods[0](operand1, operand2));
break;
case '-':
Console.WriteLine("-: " + methods[1](operand1, operand2));
break;
case '*':
Console.WriteLine("*: " + methods[2](operand1, operand2));
break;
case '/':
Console.WriteLine("/: " + methods[3](operand1, operand2));
break;
}
}
}
class Program
{
// 3개의 매개변수를 받고 void를 반환하는 델리게이트 정의
// 매개변수의 타입이 중요할 뿐 매개변수의 이름은 임의로 정할 수 있다.
delegate void WorkDelegate(char arg1, int arg2, int arg3);
static void Main(string[] args)
{
Mathmatics math = new Mathmatics();
WorkDelegate work = math.Calculate;
work('+', 10, 5);
work('-', 10, 5);
work('*', 10, 5);
work('/', 10, 5);
}
}
델리게이트가 타입이라는 점은 중요하다.
이 때문에 변수가 사용되는 곳이라면 델리게이트 또한 함께 사용되는데, 이것이 가지는 의미
- 메서드의 반환값으로 델리게이트를 사용할 수 있다.
- 메서드의 인자로 델리게이트를 전달할 수 있다.
- 클래스의 멤버로 델리게이트를 정의할 수 있다.
델리게이트가 메서드를 가리키는 것이기에 다음과 같이 해석할 수 있다.
- 메서드의 반환값으로 메서드를 사용할 수 있다.
- 메서드의 인자로 메서드를 전달할 수 있다.
- 클래스의 멤버로 메서드를 정의할 수 있다.
- 메서드가 프로그래밍 언어에서 이런 특성을 지닐 때 그것을 1급 함수(first-class function)라 한다. 따라서 C#은 1급 함수가 지원되는 언어로, 이후 델리게이트의 특성을 좀 더 보강한 익명 함수, 람다 표현식이 제공된다.
델리게이트의 실체: 타입
왜 델리게이트가 타입인가?
- delegate라는 예약어가 사용된 것과 class 타입은 전혀 상관없는 관계로 보이는데 타입과 동등한 위치에 있는가?
- delegate 예약어가 메서드를 가리킬 수 있는 내부 닷넷 타입에 대한 '간편 표기법'이라는 점에 있다.
- 그 내부 타입의 이름은 MulticastDelegate다.
- System.MulticastDelegate 타입은 System.Delegate 타입을 상속받고, 그것은 다시 System.Objet를 상속 받는다.
- C#은 MulticastDelegate를 직접 상속해서 정의하는 구문을 허용하지 않는다.
위의 코드인 WorkDelegate를 delegate 예약어 없이 정의한다면 개발자가 직접 코드를 작성해야 한다.
class WorkDelegate : System.MulticastDelegate
{
public WorkDelegate)object obj, IntPtr method);
public virtual void Invoke(char arg1, int arg2, int arg3);
}
또한 인스턴스 생성 및 호출까지도 C# 컴파일러의 개입이 없다면 수작업으로 해야한다.
Mathematics math = new Mathematics();
WorkDelegate func = new WorkDelegate(math, math.Calculate);
func.Invoke('+', 10, 5);
delegate 예약어의 도움으로 MulticastDelegate 타입의 존재를 모른 채 메서드를 가리키는 타입을 좀 더 쉽게 사용할 수 있다.
델리게이트 응용
두 개의 정수에 대해 단 한 번의 함수 호출로 사칙 연산 메서드가 모두 호출되는 예제
특이하게 += 연산자를 이용해 메서드를 델리게이트 인스턴스에 추가하는데, 이 역시 C# 컴파일러가 빌드 시에 자동으로 다음과 같은 구문으로 바꿔준다.
CalcDelegate calc = new CalcDelegate(Add);
CalcDelegate subtractCalc = new CalcDelegate(Subtract);
CalcDelegate multiplyCalc = new CalcDelegate(Multiply);
CalcDelegate divideCalc = new CalcDelegate(Divide);
calc = CalcDelegate.Combine(calc, subtractCalc) as CalcDelegate;
calc = CalcDelegate.Combine(calc, multiplyCalc) as CalcDelegate;
calc = CalcDelegate.Combine(calc, divideCalc) as CalcDelegate;
-=연산자도 지원한다.
델리게이트에 -= 연산자를 사용하면 MulticastDelegate의 메서드 보관 목록에서 해당 메서드를 제거하는 역할을 한다.
calcDelegate calc = Add;
calc += Subtract;
calc += Multiply;
calc += Divide;
calc(10, 5); //Add, Subtract, Multiply, Divide 메서드 모두 호출
calc -= Multiply; //목록에서 Multiply 메서드를 제거
calc(10, 5); //Add, Subtract, Divide 메서드만 호출
이로서 잊지 말아야 할 것은 델리게이트는 타입이다.
클래스 내부에서 CalcDelegate 델리게이트를 정의했다면 그것은 중첩 클래스일뿐 그 이상도 이하도 아니다.
콜백 메서드
- 콜백(callback) 메서드는 메서드를 사용하는 전형적인 패턴의 하나다.
- 이 개념을 완전하게 이해하려면 메서드 이밪ㅇ에서의 호출자(caller)와 피호출자(callee) 관계를 이해해야 한다.
- 콜백이란 역으로 피호출자에서 호출자의 메서드를 호출하는 것을 의미하고, 이때 역으로 호출된 '호출자 측의 메서드'를 콜백 메서드라고 한다.
1번 호출에서는 Source 타입이 호출자이고 Target 타입이 피호출자가 된다. 하지만 피호출자가 정의한 Do 메서드 내부에서 다시 호출자의 타입에 정의된 메서드를 호출하고 있다.
2번 호출을 콜백이라 하고 Source 타입의 GetResult 멤버가 콜백 메서드가 된다.
델리게이트가 콜백 호출 패턴에서 어떤 역할을 담당하는가?
콜백은 메서드를 호출하는 것이기 때문에 상황에서 실제 필요한 것은 타입이 아니라 하나의 메서드일 뿐이다.
타입 자체를 전달해서 실수를 유발할 여지를 남기기보다는 메서드에 대한 델리게이트만 전달해서 이 문제를 해결할 수 있다.
delegate int GetResultDelegate(); //int를 반환하고 매개변수가 없는 델리게이트 타입을 정의
class Target
{
public void Do(GetResultDelegate getResult)
{
Console.WriteLine(getResult()) //콜백 메서드 호출
}
}
class Source
{
public int GetResult() //콜백 용도로 전달될 메서드
{
result 10;
}
public void Test()
{
Target target = new Target();
target.Do(new GetResultDelegate(this.GetResult));
}
}
- 피호출자가 호출하는 메서드가 호출자 내부에 정의된 메서드로 한정되지는 않는다.
- 다른 타입에 정의된 메서드를 피호출자에 전달해서 호출되는 경우도 있으며, 이러한 '역 호출'을 보통 콜백이라고 한다.
위의 콜백 패턴을 보면 Target 타입의 Do 메서드를 호출하면서 콜백 메서드를 전달하는데, 이로 인해 Do 메서드는 내부의 동작에 콜백 메서드를 반영하게 된다.
이미 정의되어 있는 메서드 내의 특정 코드 영역을 '콜백 메서드'에 정의된 코드로 치환하는 것과 같은 역할을 한다.
코드를 치환한다는 의미를 적절히 살리는 예시
//선택 정렬 알고리즘
namespace ConsoleApp2;
class SortObject //배열을 정렬할 수 있는 기능을 가진 타입 정의
{
int[] numbers;
public SortObject(int[] numbers) //배열을 생성자의 인자로 받아서 보관
{
this.numbers = numbers;
}
public void Sort() //전형적인 선택 정렬 알고리즘을 구현한 메서드
{
//numbers 배열의 요소를 크기순으로 정렬
int temp;
for (int i = 0; i < numbers.Length; i++)
{
int lowPos = i;
for (int j = i + 1; j < numbers.Length; j++)
{
if (numbers[j] < numbers[lowPos])
{
lowPos = j;
}
}
temp = numbers[lowPos];
numbers[lowPos] = numbers[i];
numbers[i] = temp;
}
}
public void Display() //number 요소를 화면에 출력
{
for (int i = 0; i < numbers.Length; i++)
Console.Write(numbers[i] + ", ");
}
}
class Program
{
static void Main(string[] args)
{
int[] intArray = new int[] { 5, 2, 3, 1, 0, 4 };
SortObject so = new SortObject(intArray);
so.Sort();
so.Display();
}
}
SortObject 클래스는 Sort라는 단 하나의 메서드를 제공해서 int형 배열을 크기순(오름차순: ascending)으로 정렬한다.
여기서 배열을 내림차순(descending)으로 정렬하고 싶다면 for문 내의 비교 연산자 하나만 수정하면 된다.
public void Sort() //전형적인 선택 정렬 알고리즘을 구현한 메서드
{
//numbers 배열의 요소를 크기순으로 정렬
int temp;
for (int i = 0; i < numbers.Length; i++)
{
int lowPos = i;
for (int j = i + 1; j < numbers.Length; j++)
{
if (numbers[j] > numbers[lowPos]) // <연산자를 >로 변경
{
lowPos = j;
}
}
temp = numbers[lowPos];
numbers[lowPos] = numbers[i];
numbers[i] = temp;
}
}
오름차순과 내림차순을 SortObject에서 함께 구현해야 한다면 각각을 구현하는 두 개의 Sort 메서드를 만들어야하는가?
비교 코드로 인해 대부분의 코드가 중복되는 메서드를 2개 만드는 것은 좋지 않다.
Sort메서드에 bool ascending이라는 매개변수를 추가해 오름차순과 내림차순을 선택하게 하는 것이 좋다.
public void Sort(bool ascending) //전형적인 선택 정렬 알고리즘을 구현한 메서드
{
//생략
if(ascending == true) //오름차순 정렬
{
if(number[j] < numbers[lowPos])
{
lowPos = j;
}
}
else //내림차순 정렬
{
if(numbers[j] > numbers[lowPos])
{
lowPos = j;
}
}
//생략
}
'비교하는 코드'를 외부에서 선택하도록 델리게이트로 만드는 것도 가능하다.
public delegate bool CompareDelegate(int arg1, int arg2);
public void Sort(CompareDelegate compareMethod)
{
//생략
if(compareMethod(numbers[j], numbers[lowPos]))
{
lowPos = j;
}
//생략
}
Sort메서드의 코드는 간결해지고 오름차순 내림차순을 외부에서 원하는 대로 정하는 것이 가능해졌다.
class Program
{
static void Main(string[] args)
{
int[] intArray = new int[] {5, 2, 3, 1, 0, 4};
SortObject so = new SortObject(intArray);
so.Sort(AscendingCompare); //오름차순 정렬을 할 수 있는 메서드 전달
so.Display();
Console.WriteLine();
so.Sort(DescendingCompare); //내림차순 정렬을 할 수 있는 메서드 전달
so.Display();
}
public static bool AscendingCompare(int arg1, int arg2)
{
return (arg1 > arg2);
}
}
정렬 대상을 int타입으로 다루고 있었는데, 이를 Person이라는 객체로 변경하면 어떻게 되는가?
class Person
{
public int Age;
public string Name;
public Person(int age, string name)
{
this.Age = age;
this.Name = name;
}
public override string ToString()
{
return Name + ": " + Age;
}
}
새롭게 SortPerson 타입을 정의하고 Person 타입의 Age 필드 순으로 정렬하도록 바꾸는 것이 가능하다.
class SortPerson
{
Person[] men;
public SortPerson(Person[] men)
{
this.men = men;
}
public void Sort()
{
for(int i = 0; i < men.Length; i++)
{
if(men[j].Age < men[lowPos].Age
{
lowPos = j;
}
}
temp = men[lowPos];
men[lowPos] = men[i];
men[i] = temp;
}
}
public void Display()
{
for(int i = 0; i < men.Length; i++)
{
Console.WriteLine(men[i] + ",");
}
}
델리게이트를 사용하지 않고 Age 필드에 대해 내림차순을 기능을 추가하면, boolean 변수를 Sort에 추가하고 변수의 값에 따라 if 문을 추가해 비교 연산자를 바꾸면 된다.
Person타입에 Address, Telephone 등의 속성이 추가되고 그러한 속성에 대해서도 정렬을 지원해야 한다면?
Sort 메서드가 정렬이라는 본래 목적에서 벗어나 정렬 대상이 되는 필드를 선택하기 위해 코드가 점점 더 복잡해지는 현상이 발생한다.
Sort 메서드를 만드는 개발자는 그 메서드를 사용하는 측에서 모든 종류 의 정렬을 사용할지에 대해서도 알 수 없지만 방어적으로 코드를 생성해야 한다는 것도 문제다.
이러한 모든 복잡성을 델리게이트를 사용해 해결할 수 있다. Sort 메서드에서 Name 필드로 오름차순 정렬을 원하는 개발자는 단지 그에 맞게 정렬하는 코드를 직접 제공하면 된다.
delegate bool ComparteDelegate(Person arg1, Person arg2);
class SortPerson
{
//생략
public void Sort(CompareDelegate compareMethod) //비교를 위한 델리게이트 인자
{
//생략
if(compareMethod(men[j], men[lowPos]))
{
lowPos = j;
}
//생략
}
//생략
}
class Program
{
static bool AscSortByName(Person arg1, Person arg2)
{
// string 객체의 CompareTo 메서드는 문자열 비교를 수행
// 문자열이 사전 정렬 순으로 비교해서 크면 1, 같으면 0, 작으면 -1을 반환
// 따라서 -보다 작은 값을 반환한 경우를 true로 가정하면 오름차순 정렬
return arg1.Name.CompareTo(arg2.Name) < 0;
}
static void Main(string[] args)
{
Person[] personArray = new Person[]
{
new Person(51, "Anders"),
new Person(37, "Scott"),
new Person(45, "Peter"),
new Person(62, "Mads"),
};
SortPerson so = new SortPerson(personArray);
so.Sort(AscSortByName);
so.Display();
}
}
이처럼 SortPerson 타입을 사용하는 개발자는 자신이 원하는 정렬 기능을 갖추게끔 델리게이트를 제공하면 된다.
이를 더 유연하게 작성할 수 있는가?
위의 코드는 '정렬'을 위한 코드는 대부분 변경되지 않고 타입에 의존적인 코드만 바뀐 것을 볼 수 있는데, 모든 타입의 부모인 object를 사용하면 2개의 분리된 클래스를 하나로 합칠 수 있는가?
가능하다면 '정렬 알고리즘'을 만드는 개발자는 정렬을 위한 코드에 더 집중할 수 있게 된다.
delegate bool CompareDelegate(object arg1, object arg2); //object 인자 2개
class SortObject
{
object[] things;
public SortObject(object[] things) //object 배열
{
this.things = things;
}
public void Sort(CompareDelegate compareMethod)
{
object temp;
for(int i = 0; i < hings.Length; i++)
{
int lowPos = i;
for(j = i + 1; j < things.Length; j++)
{
if(compareMethod(things[j], things[lowPos]))
{
lowPos = j;
}
}
temp = things[lowPos];
things[lowPos] = things[i];
things[i] = temp;
}
}
//DisPlay 코드 생략
}
class Program
{
static bool AscSortByName(object arg1, object arg2)
{
Person person1 = arg1 as Person; //대상 타입으로 형 변환
Person person2 = arg2 as Person;
return person1.Name.CompareTo(person2.Name) < 0;
}
static void Main(string[] args)
{
//배열 초기화 코드 생략
SortObject so = new SortObject(personArray);
so.Sort(AscSortByName);
so.Display();
}
}
object를 사용해 Sort 메서드를 타입에 종속적이지 않게 만들고 객체를 비교하는 코드를 외부에서 지정할 수 있도록 델리게이트를 사용했는데, 이렇게 되면 int 및 Person 타입에 제한되지 않고 모든 타입에 대해 SortObject 클래스를 이용해 정렬을 수행할 수 있다.
적절하게 델리게이트만 전달하는 것으로 코드 재사용 능력을 극대화한 것이다.
Reference
시작하세요! C# 12 프로그래밍 기본 문법부터 실전 예제까지
'C#' 카테고리의 다른 글
C# 객체지향 문법 [C#의 클래스 확장 - 구조체] (5) | 2025.06.04 |
---|---|
C# 객체지향 문법 [C#의 클래스 확장 - 인터페이스] (0) | 2025.05.27 |
C# 객체지향 문법 [다형성] (0) | 2025.05.17 |
C# 객체지향 문법 [상속] (0) | 2025.05.15 |
C# 객체지향 문법 [캡슐화] (0) | 2025.05.13 |