# 40 \~ 47

### 40 대리자

#### 40.1 대리자(위임/델리케이트)

대리자는 delegate 키워드를 사용하여 만든다. 대리자는 함수 자체를 데이터 하나로 보고 다른 메서드를 대신 실행하는 기능이다. (간단하게 말하면 함수 포인터에 좀 더 기능 추가된거)

```cs
class DelegateDemo
{
    static void Hi() => Console.WriteLine("안녕하세요.");

    delegate void SayDelegate();

    static void Main()
    {
    	// 방법 1
        SayDelegate say = Hi;
        say();

		// 방법 2
        var hi = new SayDelegate(Hi);
        hi();
    }
}
```

Hi 대신 say로 호출할 수 있다.

#### 40.2 대리자를 사용하여 메서드 대신 호출하기

대리자를 생성했다면 해당 대리자를 사용하여 구조가 동일한 다른 메서드를 대신 호출할 수 있다.

```cs
대리자 변수 = new 대리자(메서드이름);
```

대리자 형식 변수에 메서드를 등록하는 코드 (간단하게 `new 대리자()` 키워드는 생략하고 함수 이름만 써서 등록할수도 있다)

```cs
대리자 변수 += new 대리자(메서드이름);
```

대리자에는 += 연산자를 사용하여 대신할 메서드를 하나 이상 등록할 수 있다.

**대리자로 함수 대신 호출**

```cs
class DelegateNote
{
    delegate void SayPointer();
    static void Hello() => Console.WriteLine("Hello Delegate");

    static void Main()
    {
        SayPointer sayPointer = new SayPointer(Hello); // 실행할 함수 전달

        sayPointer(); // 대리자 변수에 괄호 붙여서 메서드 호출
        sayPointer.Invoke(); // Invoke() 붙여서 명시적 호출
    }
}
```

invoke()는 붙여도 되고 안 붙여도 됨

**이름 없는 메서드를 대신 호출**

```cs
class AnonymousDelegate
{
    delegate void SayDelegate();
    static void Main()
    {
        // delegate 키워드로 함수를 바로 정의해서 사용
        SayDelegate say = delegate ()
        {
            Console.WriteLine("반갑습니다.");
        };
        say();
    }
}
```

익명 메서드 만들어서 넣을 수도 있다 근데 이거보단 람다식을 자주쓴다고 저자가 언급

> 람다식? (입력 파라미터) => { 실행문장 블럭 };

**강력한 형식의 대리자**

```cs
> public delegate double DelegateType(double x, double y);
> DelegateType pow = Math.Pow;
> double actual = pow(2, 10);
> actual
1024
```

매개변수랑 반환 값만 맞으면 `Math.Pow` 같은 것도 가져다 넣을 수 있다.

#### 40.3 대리자를 사용하여 메서드 여러 개를 다중 호출하기

```cs
GoHome go = new GoHome(CarDriver.GoLeft);
go += new GoHome(CarDriver.GoForward);
go += new GoHome(CarDriver.GoRight);
go += new GoHome(CarDriver.GoLeft);
go += new GoHome(CarDriver.GoLeft);
go();
```

대리자에 메서드 여러개 묶어놓고 한 번에 쓸 수 있다. 매개변수와 반환값은 같아야 함.

#### 40.5 메서드의 매개변수에 대리자 형식 사용하기

```cs
class DelegateParameter
{
    delegate void Runner();

    static void Main()
    {
        RunnerCall(new Runner(Go));
        RunnerCall(new Runner(Back));
    }

    static void RunnerCall(Runner runner) => runner();
    static void Go() => Console.WriteLine("직진");
    static void Back() => Console.WriteLine("후진");
}
```

new Runner()은 생략하고 메서드만 바로 넣어도 됨

#### 40.6 Action, Func, Predicate 대리자

* Action 대리자: 반환값이 없는 메서드를 대신 호출한다.
* Func 대리자: 매개변수와 반환값이 있는 메서드를 대신 호출한다.
* Predicate 대리자: T 매개변수에 대한 bool 값을 반환하는 메서드를 대신 호출한다.

닷넷 API에 내장된 유용한 제네릭 대리자들이다.

**`Action<T>` 대리자 사용하기**

```cs
> Action<string> printf = Console.WriteLine;
> printf("메서드 대신 호출");
메서드 대신 호출
```

**`Func<T>` 대리자 사용하기**

```cs
> Func<int, int> abs = Math.Abs;
> abs(-10)
10
```

`Func<매개변수형식, 반환값형식>`으로 특정 메서드 또는 익명 메서드를 대신 호출할 수 있는 대리자 개체를 만들 수 있다.

```cs
> Func<double, double, double> pow = Math.Pow;
> pow(2, 20)
1048576
```

입력 매개변수를 2개 받는 `Math.Pow()` 메서드를 대신 호출하는 `pow()` 함수 만들기

```cs
> Func<int, int> anonymous = delegate (int x) { return x * x; }; // 익명 메서드 담기
> anonymous(2)
4
> Func<int, double> lambda = x => x / (double)2; // 람다 식 담기
> lambda(3)
1.5
```

Func 대리자와 람디 식을 사용하면 LINQ처럼 C#에서 함수형 프로그래밍 스타일로 개발할 수 있다. Func 대리자는 이미 있는 메서드 이외에 익명 메서드 및 람다 식을 담아 사용할 수 있다.

**Predicate 대리자 사용하기**

```cs
> Predicate<string> isNullOrEmpty = String.IsNullOrEmpty;
> isNullOrEmpty("Not Null")
false
> Predicate<Type> isPrimitive = t => t.IsPrimitive;
> isPrimitive(typeof(int))
true
```

`Predicate<T>` 대리자는 T를 매개변수로 받아 어떤 로직을 수행한 후 그 결과를 bool 형식으로 반환하는 메서드를 대신 호출

#### 40.7 메서드의 매개변수로 메서드 전달하기

```cs
class PassMethodAsParameter
{
    static int StringLength(string data) => data.Length;

    static void StringLengthPrint(Func<string, int> stringLength, string message)
        => Console.WriteLine($"메시지의 크기는 {stringLength(message)}입니다.");

    static void Main() => StringLengthPrint(StringLength, "안녕하세요.");
}
```

닷넷에 내장된 제네릭 대리자인 Func를 사용하면 메서드의 매개변수로 메서드 이름 자체를 지정해서 넘겨줄 수 있다.

**`Expression<T>`**

`Func<T>`가 바로 실행 가능한 대리자 개체를 생성한다면, `Expression<T>`는 대리자 구문 자체를 담은 개체를 만들고, 이를 사용하려면 `Compile()` 같은 추가 메서드를 호출하여 대리자 개체를 만든다. 이게 뭔지 알려면 Expression Tree 개념을 알아야 한다고 함.

### 41 이벤트

이벤트는 메서드 등록과 해지만 가능하고 직접 호출은 불가능한 delegate이다.

### 42 클래스 기타

#### 42.1 부분 클래스

파일 하나에 클래스 구현하는게 힘들면 partial 키워드를 붙여 CS 파일 여러 개로 클래스를 분할할 수 있다.

같은 파일 내에서 멤버나 메서드를 동일한 이름의 클래스에 나누어 관리할 때도 사용할 수 있다.

```cs
public partial class Person
{
	public string Name { get; set; }
    public int Age { get; set; }
}

public partial class Person
{
	public void Print() => Console.WriteLine($"{Name} : {Age}");
}
```

#### 42.2 정적 클래스

정적 클래스

* static 키워드를 붙임
* static 멤버만 가짐
* 인스턴스화될 수 없음
* 유틸리티 클래스 용도로 사용
* 팩터리 클래스

#### 42.4 함수형 프로그래밍 스타일: 메서드 체이닝

```cs
> class Point
. {
.     public readonly int x;
.     public readonly int y;
.     public Point(int x, int y)
.     {
.         this.x = x;
.         this.y = y;
.     }
. 
.     public Point MoveBy(int dx, int dy)
.     {
.         return new Point(x + dx, y + dy);
.     }
. }
> 
> var p = (new Point(0, 0)).MoveBy(10, 10).MoveBy(20, 20).MoveBy(30, 30);
> $"X : {p.x}, Y : {p.y}"
"X : 60, Y : 60"
```

메서드의 반환값을 자신의 클래스 형식으로 지정하면 메서드를 계속 반복해서 호출하는 함수형 프로그래밍 스타일을 제공할 수 있다.

### 43 상속으로 클래스 확장하기

클래스 간에 부모와 자식 관계를 설정하는 것을 개체 관계 프로그래밍이라 한다. 상속은 부모 클래스에 정의된 내용을 다시 사용하거나 확장 또는 수정하여 자식 클래스로 만드는 것.

#### 43.1 클래스 상속하기

상속 : 부모 클래스의 모든 멤버를 자식 클래스가 재사용하도록 허가하는 기능 C#은 단일 상속만 지원함. 다중 상속은 인터페이스로만 할 수 있음.

**클래스 상속 구문**

```cs
public class 기본클래스이름
{
	// 기본 클래스의 멤버 정의
}

public class 파생클래스이름 : 기본클래스이름
{
	// 기본 클래스의 멤버를 포함한 자식 클래스의 멤버 정의
}
```

* System.Object 클래스 : 모든 클래스의 부모 클래스. 모든 클래스는 자동으로 Object에서 상속받는다. (상속받는 코드는 생략됨)
* 기본(base) 클래스 : 다른 클래스의 부모 클래스가 되는 클래스. Base 클래스, Super 클래스라고도 한다.
* 파생(derived) 클래스 : 다른 클래스의 자식 클래스가 되는 클래스. Derived 클래스, Sub 클래스, 자식 클래스라고도 한다.

#### 43.2 부모 클래스와 자식 클래스

public, protected로 선언된 멤버들은 자식 클래스에서 사용 가능

```cs
using System;

namespace InheritanceDemo
{
    class Parent
    {
        public void Foo() => Console.WriteLine("부모 클래스의 멤버 호출");
    }

    class Child : Parent
    {
        public void Bar() => Console.WriteLine("자식 클래스의 멤버 호출");
    }

    class InheritanceDemo
    {
        static void Main()
        {
            var child = new Child();
            child.Foo();
            child.Bar();
        }
    }
}
```

#### 43.5 부모 클래스 형식 변수에 자식 클래스의 개체 할당하기

```cs
> class Developer
. {
.     public override string ToString()
.     {
.         return "developer";
.     }
. }
> class WebDeveloper : Developer
. {
.     public override string ToString() => "Web dev";
. }
> 
> class MobileDeveloper : Developer
. {
.     public override string ToString() => "mobile dev";
. }
> 
> var web = new WebDeveloper();
> Console.WriteLine(web);
Web dev
> var mobile = new MobileDeveloper();
> Console.WriteLine(mobile);
mobile dev
```

#### 43.7 this와 this(), base와 base()

클래스 내에서 this는 자신을 의미하고, this()는 자신의 생성자를 나타낸다. base는 부모 클래스를 의미하고, base()는 부모 클래스의 생성자를 나타낸다.

**base 키워드를 사용하여 부모 클래스의 생성자 호출하기**

자식 클래스 생성자에서 바로 어떤 일 처리하는게 아니라 부모 클래스의 생성자에 전달할 때가 있음. 이때 자식 클래스의 생성자에서 콜론 기호 뒤에 base()를 사용하여 부모 클래스의 생성자를 호출함.

```cs
namespace ConstructorBase
{
    class Parent
    {
        public Parent(string message) => Console.WriteLine(message);
    }
    class Child : Parent
    {
        public Child(string message) : base(message) { }
    }
    class ConstructorBase
    {
        static void Main()
        {
            string message = "자식 클래스의 생성자를 호출할 때 부모 클래스의 생성자로 전달";
            var child = new Child(message);
        }
    }
}
```

초기화할 때 부모 생성자 가져오는거

> **생성자의 특징**

클래스와 이름이 같다 생성자의 이름은 클래스의 이름과 동일해야 합니다.

>

반환형이 없다 생성자는 반환형을 명시하지 않으며, 반환값도 없습니다. 예를 들어, void 키워드조차 사용하지 않습니다.

>

자동 호출 객체를 new 키워드로 생성할 때 자동으로 호출됩니다.

>

기본 생성자(Default Constructor) 매개변수가 없는 생성자는 기본 생성자라고 하며, 클래스를 정의할 때 생성자를 명시하지 않으면 컴파일러가 기본 생성자를 자동으로 제공합니다.

```cs
namespace BaseKeyword
{
    public class Car
    {
        private string name;
        public Car(string name)
        {
            this.name = name;
        }
        public void Run() => Console.WriteLine($"{this.name}가 달린다.");
    }

    public class My : Car
    {
        public My() : this("나의 자동차") { }
        public My(string name) : base(name) { }
    }
    public class Your : Car
    {
        public Your() : base("너의 자동차") { }
    }

    class BaseKeyword
    {
        static void Main()
        {
            (new My()).Run();
            (new My("나의 끝내주는 자동차")).Run();
            new Your().Run();
        }
    }
}
```

My의 생성자 2개 : 매개변수 없는 친구가 매개변수 있는 친구를 호출 `(new My()).Run()`에 있는 괄호는 없어도 됨

#### 43.8 봉인 클래스

봉인(sealed) 클래스 : 더 이상 다른 클래스에 상속되지 않게 사용하는 클래스. 최종 클래스라고도 하며, 선언부에 sealed 키워드를 붙여 만든다.

```cs
sealed partial class App : Application
```

#### 43.9 추상 클래스

클래스를 선언할 때 추가로 abstract 키워드를 붙여 클래스를 선언할 수 있다. 이를 추상(abstract) 클래스라 한다. 추상 클래스는 다른 클래스의 부모 클래스 역할을 한다

```cs
public abstract class AbstractClassDemo
```

**추상 클래스를 사용하여 부모 클래스 만들기**

추상 클래스는 일반적인 클래스들의 부모 역할을 하는 클래스, 즉 공통 기능들을 모아 놓은 클래스 역할을 한다.

* 추상 클래스로 개체 못 만든다.
* 클래스 설계할 때 멤버 이름 통일할 때 쓴다.
* 상속 준 후에 하위 클래스에서 추가 기능 직접 구현해야 하는 강제성을 띤다

```cs
> public abstract class TableBase
. { 
.     public int Id { get; set; }
.     public bool Active { get; set; }
. }
> TableBase tableBase = new TableBase();
(1,23): error CS0144: 추상 형식 또는 인터페이스 'TableBase'의 인스턴스를 만들 수 없습니다.
> public class Children : TableBase
. { 
.     public string Name { get; set; }
. }
> var child = new Children() { Id = 1, Active = true, Name = "child" };
> if (child.Active)
. {
.     Console.WriteLine($"{child.Id} - {child.Name}");
. }
1 - child
```

**추상 클래스를 만들고 상속하기**

```cs
> public class Square : Shape
. {
.     private int _size;
.     public Square(int size)
.     {
.         _size = size;
.     }
. 
.     public override double GetArea()
.     {
.         return _size * _size;
.     }
. }
> 
> Square square = new Square(10);
> square.GetArea()
100
```

#### 43.10 자식 클래스에만 멤버 상속하기

`protected`는 자식 클래스들까지만 접근 가능한 멤버

#### 43.11 기본 클래스의 멤버 숨기기

부모 클래스에 만든 특정 메서드를 자식 클래스에서 새롭게 정의해서 사용할 때 `new` 키워드를 사용하면 된다. 이를 메서드 오버라이드라고도 한다.

```cs
> class Parent
. {
.     public void Work() => Console.WriteLine("programmer");
. }
> 
> class Child : Parent
. {
.     public new void Work() => Console.WriteLine("progamer"); 
. }
> 
> var child = new Child();
> child.Work();
progamer
```

new를 생략할수도 있지만, 명시적으로 사용해야 좋음. 컴파일러도 경고 안 날리고.

gpt는 new는 숨김이고, 메서드 오버라이딩이 아니라고 함.

| **특징**         | **숨김(shadowing)**      | **오버라이딩(overriding)**                |
| -------------- | ---------------------- | ------------------------------------ |
| **키워드**        | `new`                  | `override`                           |
| **부모 멤버 요구사항** | 특별한 요구사항 없음            | 부모의 멤버는 `virtual` 또는 `abstract`이어야 함 |
| **호출 결정 시점**   | 컴파일 타임 (정적 바인딩)        | 런타임 (동적 바인딩)                         |
| **호출 방식**      | 변수 타입에 따라 결정           | 실제 객체 타입에 따라 결정                      |
| **다형성 지원**     | 다형성 불가능                | 다형성 가능                               |
| **부모 멤버 상태**   | 여전히 호출 가능 (부모 참조 사용 시) | 부모 멤버는 자식 메서드에 의해 재정의됨               |

### 44 메서드 오버라이드

#### 44.1 메서드 오버라이드: 재정의

메서드 오버라이드 : 부모 클래스에 만들었던 메서드를 동일한 이름으로 자식 클래스에서 다시 정의해서 사용하는 것 부모 클래스에 `virtual` 키워드로 선언해놓은 메서드는 자식 클래스에서 `override` 키워드로 재정의해서 사용 가능하다.

#### 44.2 상속 관계에서 메서드 오버라이드

```cs
> public class Parent
. {
.     public void Say() => Console.WriteLine("parent_hi");
.     public void Run() => Console.WriteLine("parent_run");
.     public virtual void Walk() => Console.WriteLine("parent_walk");
. }
> 
> public class Child : Parent
. {
.     public void Say() => Console.WriteLine("child_hi");
.     public new void Run() => Console.WriteLine("child_run");
.     public override void Walk() => Console.WriteLine("child_walk");
. }
> Child c = new Child();
> c.Say();
child_hi
> c.Run();
child_run
> c.Walk();
child_walk
```

메서드 재정의하는 세 가지 방법

#### 44.3 메서드 오버로드와 오버라이드

오버로드(overload)는 여러 번 정의하는 것 오버라이드는 다시 정의하는 것

```cs
> static void Print(int number) => Console.WriteLine(number);
> static void Print(ref int number) => Console.WriteLine(++number);
> var number = 100;
> Print(number);
100
> Print(ref number);
101
> Print(number);
101
```

매개변수에 따라 호출되는 함수가 다르다. 이게 오버로드

#### 44.4 메서드 오버라이드 봉인

메서드에도 `sealed` 키워드를 붙여 더 이상 오버라이드해서 사용하지 못하도록 설정 가능

### 45 인터페이스

#### 45.1 인터페이스

인터페이스를 사용하면 전체 프로그램의 설계도에 대한 명세서를 작성할 수 있다. 인터페이스는 클래스 또는 구조체에 포함될 수 있는 관련 있는 메서드들을 묶어 관리한다. 인터페이스는 명세서(specification)(규약, 표준) 역할을 한다. 인터페이스를 상속받아 그 내용을 구현하는 클래스는 인터페이스에 선언된 멤버가 반드시 구현되어 있다고 보장한다.

* 인터페이스는 `interface` 키워드로 만든다. 인터페이스엔 실행 가능한 코드와 데이터가 없다. (원래는 없는데 나중에 추가됨)
* 인터페이스 내의 모든 멤버는 기본적으로 public이다.
* C#에서 인터페이스 이름은 ICar, IFood 등 대문자 I로 시작한다.

```cs
namespace InterfaceNote
{
    interface ICar
    {
        void Go();
    }

    class Car : ICar
    {
        public void Go() => Console.WriteLine("상속한 인터페이스에 정의된 모든 멤버를 반드시 구현해야 함");
    }

    class InterfaceNote
    {
        static void Main()
        {
            var car = new Car();
            car.Go();
        }
    }
}
```

#### 45.2 인터페이스 형식 개체에 인스턴스 담기

```cs
IRepository repository = new Repository();
```

#### 45.3 생성자의 매개변수에 인터페이스 사용하기

생성자의 매개변수에 인터페이스 형식을 사용하면 해당 인터페이스를 상속하는 모든 클래스의 인스턴스를 받을 수 있다.

```cs
namespace InterfaceDemo
{
    interface IBattery
    {
        string GetName();
    }

    class Good : IBattery
    {
        public string GetName() => "Good";
    }

    class Bad : IBattery
    {
        public string GetName() => "Bad";
    }

    class Car
    {
        private IBattery _battery;

        public Car(IBattery battery)
        {
            _battery = battery;
        }

        public void Run() => Console.WriteLine("{0} 배터리를 장착한 자동차가 달립니다." , _battery.GetName());
    }

    class InterfaceDemo
    {
        static void Main(string[] args)
        {
            var good = new Car(new Good()); good.Run();
            new Car(new Bad()).Run();
        }
    }
}
```

#### 45.4 인터페이스를 사용한 다중 상속 구현하기

C#에서 클래스는 단일 상속만 지원하고, 인터페이스는 여러 개 상속받을 수 있다.

```cs
class Dog : IAnimal, IDog
```

#### 45.5 명시적인 인터페이스 구현하기

인터페이스는 다중 상속이 가능하기 때문에, 각 인터페이스에 동일한 멤버가 구현되어 있을 때가 있다. 이때는 명시적으로 어떤 인터페이스의 멤버를 실행할지 지정해야 한다.

> 어차피 인터페이스에는 구현 없고 상속받는 클래스가 알아서 하는거니까 지정 안해도 상관 없지 않아?

**1. 명시적 인터페이스 구현이 필요한 이유**

명시적 인터페이스 구현은 다음과 같은 상황에서 유용합니다:

**(1) 다중 상속에서 구현이 달라야 할 때**

만약 `IA.DoWork()`와 `IB.DoWork()`의 동작이 달라야 한다면, 명시적 인터페이스 구현을 사용해야 합니다.

```csharp
interface IA
{
    void DoWork();
}

interface IB
{
    void DoWork();
}

class MyClass : IA, IB
{
    void IA.DoWork()
    {
        Console.WriteLine("IA의 DoWork 실행");
    }

    void IB.DoWork()
    {
        Console.WriteLine("IB의 DoWork 실행");
    }
}
```

**호출 방식:**

```csharp
IA a = new MyClass();
a.DoWork(); // IA의 DoWork 실행

IB b = new MyClass();
b.DoWork(); // IB의 DoWork 실행
```

위 코드에서 `MyClass`는 `IA`와 `IB`의 `DoWork()`를 각각 다르게 구현하여 두 인터페이스를 구분할 수 있습니다.

***

**(2) 인터페이스 멤버를 숨기고 싶을 때**

명시적 인터페이스 구현은 **인터페이스의 멤버를 클래스의 공용 API로 노출하지 않고 숨길 수 있는 방법**입니다.

```csharp
interface IPrivate
{
    void Secret();
}

class MyClass : IPrivate
{
    void IPrivate.Secret()
    {
        Console.WriteLine("비밀 작업 수행 중...");
    }
}
```

**호출 방식:**

```csharp
var myClass = new MyClass();
// myClass.Secret(); // 오류: IPrivate.Secret은 직접 호출할 수 없음

IPrivate privateAccess = myClass;
privateAccess.Secret(); // 비밀 작업 수행 중...
```

* `IPrivate.Secret()`은 인터페이스를 통해서만 호출할 수 있습니다.
* 클래스의 외부 사용자가 불필요한 인터페이스 멤버에 접근하지 못하게 제어할 수 있습니다.

***

**2. 명시적 구현이 불필요한 경우**

명시적 구현이 **필요하지 않은 경우**는 다음과 같습니다:

* 여러 인터페이스에 동일한 멤버 이름과 시그니처가 있지만, 동작이 동일한 경우:

```csharp
  interface IA
  {
      void DoWork();
  }

  interface IB
  {
      void DoWork();
  }

  class MyClass : IA, IB
  {
      public void DoWork()
      {
          Console.WriteLine("동일한 DoWork 실행");
      }
  }
```

* `MyClass.DoWork()`는 `IA`와 `IB` 모두의 요구사항을 만족하므로 별도로 구분할 필요가 없습니다.

> **`Cast<T>()` 메서드로 List<자식>을 List<부모>로 변환**

```cs
interface A {}
class B : A {}
>
List<A> convertAll = (new List<B>).ConvertAll(x => (A)x);
List<A> astoff = (new List<B>()).Cast<A>().ToList();
```

자식 클래스의 컬렉션 인스턴스를 부모 클래스의 컬렉션 인스턴스에 대입할 땐 `ConvertAll()` 또는 `Cast<T>()` 메서드를 사용할 수 있다.

#### 45.7 IEnumerator 인터페이스 사용하기

`IEnumerator` 인터페이스는 문자열 배열 등 `GetEnumerator()` 메서드의 결괏값을 담아 `MoveNext()` 메서드로 값이 있는지 확인하고, `Current` 속성으로 현재 반복되는 데이터를 가져다 사용할 수 있다.

#### 45.8 IDisposable 인터페이스 사용하기

IDisposable을 상속하는 클래스는 `Dispose()` 메서드를 구현해야 한다. 이 메서드는 해당 클래스의 개체를 다 사용한 후 마지막으로 호출해서 정리하는 역할을 한다.

### 46 특성과 리플렉션

#### 46.1 특성

특성은 코드에 설명을 추가로 붙이는 것이다.

```cs
[Obsolete]
public class OldClass {}
```

#### 46.2 Obsolete 특성 사용하기

![](https://velog.velcdn.com/images/biomatrix117/post/4e08f674-4261-4c6e-8238-0521da7c5de7/image.png)

![](https://velog.velcdn.com/images/biomatrix117/post/0b417360-5087-4ed8-8806-e504df69c09d/image.png)

#### 46.4 \[Conditional] 특성 사용하기

특정 기호에 따라 실행 여부를 결정할 수 있다.

```cs
#define RELEASE
using System;
using System.Diagnostics;

public class ConditionalDemo
{
    static void Main()
    {
        DebugMethod();
        ReleaseMethod();
    }

    [Conditional("DEBUG")]
    static void DebugMethod() => Console.WriteLine("디버그 환경에서만 표시");

    [Conditional("RELEASE")]
    static void ReleaseMethod() => Console.WriteLine("릴리스 환경에서만 표시");
}
```

![](https://velog.velcdn.com/images/biomatrix117/post/2adced45-59f6-4434-be90-9cd830ff59a5/image.png)

비주얼 스튜디오 상단에 있는 드롭다운에서 프로그램에 debug와 release 기호를 제공할 수 있다.

#### 46.5 특성을 사용하여 메서드 호출 정보 얻기

```cs
using System.Runtime.CompilerServices;
using static System.Console;

class CallerInformation
{
    static void Main()
    {
        TraceMessage("여기에서 무엇인가 실행...");
    }

    public static void TraceMessage(string message,
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string sourceFilePath = "",
        [CallerLineNumber] int sourceLineNumber = 0)
    {
        WriteLine("실행 내용 : " + message);
        WriteLine("멤버 이름 : " + memberName);
        WriteLine("실행 내용 : " + sourceFilePath);
        WriteLine("실행 내용 : " + sourceLineNumber);
    }
}
```

> 실행 내용 : 여기에서 무엇인가 실행... 멤버 이름 : Main 실행 내용 : C:\Users...\Project1\ConsoleApp1\Program.cs 실행 내용 : 8

#### 46.6 사용자 지정 특성 만들기

```cs
using System;

public class SampleAttribute : Attribute
{
    public SampleAttribute() => Console.WriteLine("사용자 지정 특성 사용됨");
}

[Sample] public class CustomAttributeTest { }

class AttributePractice
{
    static void Main()
    {
        Attribute.GetCustomAttributes(typeof(CustomAttributeTest));
    }
}
```

> 특성을 사용할 때는 다음 두 가지 방식이 모두 가능합니다: \[Sample] // 접미사 Attribute 생략 \[SampleAttribute] // 완전한 이름 사용

**매개변수가 있는 사용자 지정 특성 만들기**

```cs
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
public class NickNameAttribute : Attribute
{
	public string Name { get; set; }
    public NickNameAttribute(string name) { Name = name; }
}
```

#### 46.7 리플렉션

reflection : 동적으로 특정 어셈블리 또는 형식에 대한 메타데이터를 `Type` 개체로 반환하는 것

#### 46.8 Type과 Assembly 클래스

```cs
> string r = "Reflection";
> Type t = r.GetType();
> t
```

Type 클래스로 문자열 개체 정보를 얻어 출력하는 리플렉션 예제

```cs
> System.Reflection.Assembly assembly = typeof(System.Random).Assembly;
> assembly
[System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85dea779]
```

Aseembly 클래스를 사용하여 특정 어셈블리의 정보를 얻는 예제

#### 46.9 특정 클래스의 메서드와 속성을 동적으로 호출하기

```cs
// Test 클래스에 대한 Type 개체 가져오기
Type t = typeof(test);

// 원하는 멤버를 조건에 따라 가져오기
MemberInfo[] members = t.GetMembers(BindingFlags.Static | BindingFlags.Public);
```

리플렉션을 사용하여 Test 클래스의 정적 멤버 리스트를 얻는 예제

### 47 객체와 객체 지향 프로그래밍 (OOP)

#### 47.1 객체 지향 프로그래밍 소개하기

**개체**

클래스를 사용하여 새로운 형식을 정의하고, 개체는 데이터와 기능을 숨기는 캡슐화 기능을 제공한다.

* (is something) 무엇이 된다.
* (has data) 데이터를 가진다.
* (perfoms action) 기능을 수행한다.

클래스 : 설계도 객체 : 설계도로 조립한 물건 필드 : 클래스의 부품. 외부에 공개할 땐 public 대신 readonly와 const 필드만 허용하길 권장 생성자 : 자동차 시동 걸기. 클래스 내 필드를 초기화한다. 소멸자 : 자동차 시동 끄기. GC 엔진이 대신 해준다. 메서드 : 클래스의 기능과 동작. 속성 : 자동차의 속성, 특징, 색상, 모양 등 표현. private, public 인덱서 : 자동체 카탈로그 역할. 만든 객체를 배열 형식으로 사용할 수 있게 한다.

#### 47.4 캡슐화를 사용하여 좀 더 세련된 프로그램 만들기

```cs
public class Person
{
	private string name;
    public void SetName(string n) => name = n;
    public string GetName() => this.name;
}
```

#### 47.5 다형성 기법을 사용하여 프로그램 융통성 높이기

```cs
public abstract class Animal
{
	public abstract string Cry();
}
public class Dog : Animal
{
	public override string Cry() => "멍멍멍";
}
public class Cat : Animal
{
	public override string Cry() => "야옹";
}
public class Trainer
{
	public void DoCry(Animal animal)
    {
    	Console.WriteLine("{0}", animal.Cry());
    }
}
```

#### 47.6 클래스의 멤버 종합 연습: 자동차 클래스 구현하기

```cs
using System;
using System.Collections;

// 네임스페이스 : 클래스 이름 충돌 방지
namespace CarWorld
{
    // 인터페이스 : 표준, 다중 상속
    interface IStandard { void Run(); }

    /// <summary>
    /// 클래스: 설계도
    /// </summary>
    class Car : IStandard
    {
        #region 필드
        private string name;
        private string[] names;
        private readonly int _Length;
        #endregion

        #region 생성자
        public Car()
        {
            this.name = "좋은차";
        }
        public Car(string name)
        {
            this.name = name;
        }
        public Car(int length)
        {
            this.name = "좋은차";
            _Length = length;
            names = new string[length];
        }
        #endregion

        #region 메서드
        public void Run() => Console.WriteLine("{0} 자동차가 달립니다.", name);
        #endregion

        #region 속성
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        public int Length { get { return _Length; } }
        #endregion

        #region 소멸자
        ~Car()
        {
            Console.WriteLine("{0} 자동차가 폐차됨.", name);
        }
        #endregion

        #region 인덱서
        public string this[int index]
        {
            get { return names[index]; }
            set { names[index] = value; }
        }
        #endregion

        #region 이터레이터
        public IEnumerator GetEnumerator()
        {
            for (int i = 0; i < _Length; i++)
            {
                yield return names[i];
            }
        }
        #endregion

        #region 대리자
        public delegate void EventHandler();
        #endregion

        #region 이벤트
        public event EventHandler Click;
        #endregion

        #region 이벤트 처리기
        public void OnClick()
        {
            if (Click != null)
            {
                Click();
            }
        }
        #endregion
    }

    class CarWorld
    {
        static void Main()
        {
            Car campingCar = new Car("캠핑카");
            campingCar.Run();

            Car sportsCar = new Car();
            sportsCar.Name = "스포츠카";
            sportsCar.Run();

            Car cars = new Car(2);
            cars[0] = "1번 자동차";
            cars[1] = "2번 자동차";
            for (int i = 0; i < cars.Length; i++)
            {
                Console.WriteLine(cars[i]);
            }

            foreach (string name in cars)
            {
                Console.WriteLine(name);
            }

            Car btn = new Car("전기자동차");
            btn.Click += new Car.EventHandler(btn.Run);
            btn.Click += new Car.EventHandler(btn.Run);
            btn.OnClick();
        }
    }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lazyartisan.gitbook.io/note/main-page/books/c/40-47.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
