공변성(covariance)과 반변성(contravariance)은 원래 수학·물리학에서 유래한 용어이지만, 프로그래밍에서는 주로 타입 치환(type substitution) 관계를 설명할 때 사용됩니다. 간단히 말해 공변성은 하위 타입이 상위 타입의 위치에 안전하게 들어갈 수 있는 성질을 말하고, 반변성은 그 반대 방향의 치환을 허용하는 성질을 말합니다.
면책 조항 이 글은 작성자의 한국어 실력이 충분치 않아 주로 기계 번역(예: 구글 번역기)을 활용해 작성되었습니다. 그로 인해 문법이나 어휘가 부정확할 수 있습니다. 읽으시면서 어색한 부분을 발견하시면 알려주시면 감사하겠습니다. 추후 다시 검토해 수정하겠습니다.
프로그래밍에서는 이 개념들이 SOLID 원칙 중 하나인 리스코프 치환 원칙(Liskov Substitution Principle)과 관련이 있습니다.
간단히 말해, 공변성은 하위 타입(subtype)이 상위 타입(supertype)의 위치에 안전하게 대체될 수 있는 성질을 말합니다. 예를 들어 고양이(Cat)를 동물(Animal)으로 취급하는 업캐스팅(upcasting)이 대표적인 사례입니다.

반면 반변성은 상위 타입의 참조를 하위 타입이 기대하는 위치에 사용할 수 있게 하는 성질입니다. 다운캐스팅은 항상 안전하지 않을 수 있으므로 주의해야 합니다.
기본 소개
Animal과 Cat 클래스를 예로 들어보겠습니다.
class Animal {
public void Eat() => Console.WriteLine("nom, nom");
}
class Cat : Animal {
public void Meow() => Console.WriteLine("meow");
}다음과 같이 Animal과 Cat을 인스턴스화하고 참조할 수 있습니다.
Animal x = new Animal();
Animal y = new Cat();x와 y는 둘 다 Eat 메서드를 호출할 수 있습니다. 그러나 Meow 메서드는 Cat 클래스에만 있으므로, Animal 타입으로 참조할 경우 Meow에 접근할 수 없습니다.
x.Meow(); // 컴파일 오류
y.Meow(); // 컴파일 오류직접 Cat 타입으로 참조하는 경우에만 Meow 메서드에 접근할 수 있습니다.
Cat z = new Cat();
z.Meow();제네릭
제네릭 타입의 경우, 변이(variance)는 주로 타입을 서로 대체(substitution)할 수 있는 방식과 관련있습니다.
제네릭 매개변수 T가 공변(out)으로 표시된 인터페이스는 주로 T를 반환하는(생성하는) 용도로만 사용되어야 한다는 의미입니다. C#에서는 out 키워드로 표시합니다.
// 공변성
interface IProducer<out T> {
T Produce();
}반대로 in으로 표시된 반공변(contravariant) 인터페이스는 메서드가 T 타입의 매개변수를 받지만 T를 반환하지 않아야 한다는 제약을 뜻합니다. C#에서는 in 키워드로 표시합니다.
// 반변성
interface IConsumer<in T> {
void Consume(T obj);
}공변성
예를 들어 IProducer<Animal> 타입의 프로듀서는 Produce()가 Animal을 반환하므로, 반환값을 Cat 변수에 바로 할당할 수 없습니다.
IProducer<Animal> producer;
Animal a = producer.Produce();
Cat b = producer.Produce(); // 오류반대로 IProducer<Cat>인 경우, Produce()는 Cat을 반환하며 이를 Animal 타입의 변수에도 할당할 수 있습니다.
IProducer<Cat> producer;
Animal c = producer.Produce();
Cat d = producer.Produce(); // 문제 없음이는 하위 타입이 상위 타입의 위치에 들어가도 안전하다는 의미이며, 이를 공변성이라고 합니다.
Cat : Animal ==> IProducer<Cat> : IProducer<Animal>반변성
IConsumer<Animal>는 Animal뿐 아니라 Cat도 소비할 수 있습니다.
IConsumer<Animal> consumer;
consumer.Consume(new Animal());
consumer.Consume(new Cat()); // 문제 없음반대로 IConsumer<Cat>는 Cat만 안전하게 소비할 수 있고 Animal을 전달하면 오류가 납니다.
IConsumer<Cat> consumer;
consumer.Consume(new Cat());
consumer.Consume(new Animal()); // 오류따라서 반변성 관계는 다음과 같이 표현됩니다.
Cat : Animal ==> IConsumer<Animal> : IConsumer<Cat>무변성
제네릭 매개변수에 in 또는 out과 같은 변이 표시가 없는 경우, 해당 타입은 무변성(invariant)입니다. 즉, 하위/상위 타입 간의 치환을 허용하지 않습니다.