# C#의 공변성(Covariance) 및 반공변성(Contravariance) | Steven Giesel

> 본 글은 Steven Giesel님의 [**Covariance and Contravariance in C#**](https://steven-giesel.com/blogPost/cda93276-c9ab-42c3-8288-a9b3e5c8aa5c)**글을 번역한 것입니다.**

---

.NET Framework 예제를 사용하여 C#의 공변성 및 반공변성에 대해 이야기해 보겠습니다!

공변성과 반공변성은 제네릭을 다룰 때 C#에서 필수적인 개념으로, 제네릭 유형을 할당할 때 더 유연하게 사용할 수 있도록 해줍니다. 이제 프레임워크 자체에서 바로 예제를 살펴보겠습니다!

또한 좀 더 자세히 살펴보고 일반 제약 조건과 반공변성과 같은 것들 간의 차이점에 대해 이야기하겠습니다.

## 공변성

메서드가 제네릭 타입에 지정된 것보다 더 파생된 타입을 반환하도록 허용합니다. 즉, 기본 클래스가 예상되는 곳에서 파생 클래스를 사용할 수 있습니다.

예: `IEnumerable<out T>` 인터페이스. `IEnumerable<T>`는 공변이므로 `Dog`가 `Animal`의 서브클래스인 경우 `IEnumerable<Animal>`이 예상되는 곳에서 `IEnumerable<Dog>`를 사용할 수 있습니다.

```csharp
IEnumerable<Dog> dogs = new List<Dog> { new Dog(), new Dog() };
IEnumerable<Animal> animals = dogs; // 공변 할당
```

## 반공변성

제네릭 유형에 지정된 것보다 더 일반적인(덜 파생된) 유형을 사용할 수 있습니다. 즉, 파생 클래스가 예상되는 곳에 기본 클래스를 사용할 수 있습니다.

예: `IComparer<in T>` 인터페이스. `IComparer<T>`는 반공변이므로 `Animal`이 `Dog`의 기본 클래스인 경우 `IComparer<Dog>`가 예상되는 곳에서 `IComparer<Animal>`을 사용할 수 있습니다.

```csharp
public class AnimalComparer : IComparer<Animal>
{
    public int Compare(Animal x, Animal y) { /*...*/ }
}

IComparer<Dog> dogComparer = new AnimalComparer(); // 반공변 할당
```

어떤 코드가 반공변성을 더 쉽게 사용할 수 있을까요? 이 경우 제네릭을 사용하거나 기본 유형을 직접 사용하지 않는 이유는 무엇일까요?

반공변성은 특히 델리게이트와 인터페이스로 작업할 때 재사용 가능하고 유지 관리가 용이한 컴포넌트를 더 많이 생성할 수 있도록 하여 코드를 간소화합니다. 제네릭만으로는 유형 안전성을 제공할 수 있지만, 반공변성은 추가적인 유연성을 제공합니다.

기본 유형을 직접 사용하는 다음 예제를 살펴보겠습니다.

```csharp
public class Animal { }
public class Dog : Animal { }

public class AnimalHandler
{
    public void Handle(Animal animal) { /*...*/ }
}

public class DogHandler : AnimalHandler
{
    public void Handle(Dog dog) { /*...*/ }
}
```

이제 강아지 목록과 `AnimalHandler`를 받는 메서드가 있다고 가정해 보겠습니다.

```csharp
public void ProcessDogs(List<Dog> dogs, AnimalHandler handler)
{
    foreach (var dog in dogs)
    {
        handler.Handle(dog);
    }
}
```

이 방법은 `AnimalHandler` 인스턴스에서는 잘 작동하지만, `DogHandler`를 사용하려면 `ProcessDogs` 메서드를 수정해야 합니다. 이렇게 하면 긴밀한 결합이 발생하고 재사용성이 떨어집니다.

이제 인터페이스와 반공변성을 사용해 보겠습니다.

```csharp
public interface IHandler<in T>
{
    void Handle(T item);
}

public class AnimalHandler : IHandler<Animal>
{
    public void Handle(Animal animal) { /*...*/ }
}

public class DogHandler : IHandler<Dog>
{
    public void Handle(Dog dog) { /*...*/ }
}
```

반공변성을 사용하면 `ProcessDogs` 메서드가 대신 `IHandler<Dog>`를 받을 수 있습니다.

```csharp
public void ProcessDogs(List<Dog> dogs, IHandler<Dog> handler)
{
    foreach (var dog in dogs)
    {
        handler.Handle(dog);
    }
}
```

이제 둘 다 `IHandler<Dog>`와 호환되므로 `AnimalHandler` 또는 `DogHandler`를 `ProcessDogs`에 전달할 수 있습니다. 이렇게 하면 느슨한 결합이 촉진되어 코드의 재사용성과 유지보수성이 향상됩니다.

내 IHandler를 이렇게 정의하면 다음과 같습니다.

```csharp
public interface IHandler<TAnimal> where TAnimal : Animal
```

그러면 같은 효과를 얻을 수 있지 않을까요?

예, 제네릭 유형에 제약 조건을 사용하면 비슷한 목표를 달성할 수 있습니다. 그러나 제약 조건을 사용하는 것과 반공변성을 사용하는 것의 차이점을 이해하여 필요에 가장 적합한 접근 방식을 선택하는 것이 중요합니다.

제약 조건으로 IHandler&lt;TAnimal&gt;을 정의합니다.

```csharp
public interface IHandler<TAnimal> where TAnimal : Animal
{
    void Handle(TAnimal item);
}
```

파생된 각 유형에 대해 특수 핸들러를 구현할 수 있습니다.

```csharp
public class AnimalHandler : IHandler<Animal>
{
    public void Handle(Animal animal) { /*...*/ }
}

public class DogHandler : IHandler<Dog>
{
    public void Handle(Dog dog) { /*...*/ }
}
```

그러나 `ProcessDogs` 메서드는 다음과 같이 정의해야 합니다.

```csharp
public void ProcessDogs<TAnimal>(List<TAnimal> animals, IHandler<TAnimal> handler) where TAnimal : Animal
{
    foreach (var animal in animals)
    {
        handler.Handle(animal);
    }
}
```

`AnimalHandler`또는 `DogHandler`를 사용하여 `ProcessDogs`를 호출할 수 있습니다.

```csharp
ProcessDogs(new List<Dog> { new Dog(), new Dog() }, new AnimalHandler());
ProcessDogs(new List<Dog> { new Dog(), new Dog() }, new DogHandler());
```

그러나 가장 큰 차이점은 `ProcessDogs` 메서드가 이제 제네릭이며 제네릭 유형 제약 조건에서 유형 안전성과 유연성이 제공된다는 점입니다. 반면, 반공변성은 제네릭 메서드 없이도 인터페이스 정의에서 직접 유연성을 제공합니다.

## 결론

공변성과 반공변성은 보다 유연하고 재사용 가능한 코드를 작성하는데 도움이 됩니다!

[![Buy Me a Coffee at ko-fi.com](https://cdn.ko-fi.com/cdn/kofi2.png?v=3 align="left")](https://ko-fi.com/U7U8BDPWR)

---

[https://steven-giesel.com/blogPost/cda93276-c9ab-42c3-8288-a9b3e5c8aa5c](https://steven-giesel.com/blogPost/cda93276-c9ab-42c3-8288-a9b3e5c8aa5c)
