프로그래밍 언어의 공분산 및 분열
-
18-09-2019 - |
문제
언어 이론에서 공분산과 비조화의 개념을 설명 할 수 있습니까?
해결책
공분산 일부 컬렉션 클래스의 관점에서 매우 간단하고 최고의 생각입니다. List
. 우리는 할 수 있습니다 매개 변수화 그만큼 List
일부 유형 매개 변수가있는 클래스 T
. 즉, 목록에는 유형의 요소가 포함되어 있습니다 T
일부 T
. 목록은 다음과 같은 경우 공분산입니다
s는 t iff 목록의 하위 유형입니다 [s]는 목록 [t]의 하위 유형입니다.
(수학적 정의를 사용하는 곳 IFF 의미합니다 경우에만.)
그것은 List[Apple]
a List[Fruit]
. 수락하는 일상이있는 경우 a List[Fruit]
매개 변수로, 나는 a List[Apple]
, 그런 다음 이것을 유효한 매개 변수로 전달할 수 있습니다.
def something(l: List[Fruit]) {
l.add(new Pear())
}
컬렉션 수업 인 경우 List
돌연변이가 가능하면, 공분산은 우리의 일상이 위와 같이 다른 과일 (사과가 아닌)을 추가 할 수 있다고 가정 할 수 있기 때문에 의미가 없습니다. 따라서 우리는 만 좋아해야합니다 불변 공분산이 될 수있는 수집 수업!
다른 팁
다음은 C# 4.0에 새로운 분산 기능을 추가 한 방법에 대한 기사입니다. 바닥에서 시작하십시오.
http://blogs.msdn.com/ericlippert/archive/tags/covariance+and+contravariance/default.aspx
편의성을 위해 다음은 Eric Lippert의 모든 기사에 대한 주문 링크 목록이 있습니다.
그 사이에는 구별이 있습니다 공분산 그리고 비밀.
매우 대략적으로, 유형의 순서를 보존하면 조작은 공분산입니다. 반전 이 순서.
순서 자체는보다 일반적인 유형보다 더 큰 일반적인 유형을 나타 내기위한 것입니다.
다음은 C#이 공분산을 지원하는 상황의 한 예입니다. 첫째, 이것은 다양한 객체입니다.
object[] objects=new object[3];
objects[0]=new object();
objects[1]="Just a string";
objects[2]=10;
물론 결국에는 모두 파생되기 때문에 배열에 다른 값을 삽입 할 수 있습니다. System.Object
.NET 프레임 워크에서. 다시 말해, System.Object
매우 일반적인 것입니다 크기가 큰 유형. 이제 공분산이 지원되는 곳이 있습니다.
작은 유형의 값을 더 큰 유형의 변수에 할당
string[] strings=new string[] { "one", "two", "three" };
objects=strings;
변수 객체는 유형입니다 object[]
, 실제로 유형의 값을 저장할 수 있습니다. string[]
.
생각해보십시오. 어느 쪽이든, 그것은 당신이 기대하는 것이지만 다시는 그렇지 않습니다. 결국 string
~에서 얻다 object
, string[]
하지 않습니다 파생 object[]
. 이 예에서 공분산에 대한 언어 지원은 어쨌든 과제를 가능하게합니다. 이는 많은 경우에 찾을 수있는 것입니다. 변화 언어가보다 직관적으로 작동하게하는 기능입니다.
이 주제에 대한 고려 사항은 매우 복잡합니다. 예를 들어, 이전 코드를 기반으로 오류가 발생하는 두 가지 시나리오가 있습니다.
// Runtime exception here - the array is still of type string[],
// ints can't be inserted
objects[2]=10;
// Compiler error here - covariance support in this scenario only
// covers reference types, and int is a value type
int[] ints=new int[] { 1, 2, 3 };
objects=ints;
비밀의 작업의 예는 조금 더 복잡합니다. 이 두 수업을 상상해보십시오.
public partial class Person: IPerson {
public Person() {
}
}
public partial class Woman: Person {
public Woman() {
}
}
Woman
파생됩니다 Person
, 확실히. 이제이 두 가지 기능이 있다고 생각합니다.
static void WorkWithPerson(Person person) {
}
static void WorkWithWoman(Woman woman) {
}
함수 중 하나는 Woman
, 다른 하나는 더 일반적이며 Person
. 에 Woman
사물의 측면, 당신은 이제 이것들을 가지고 있습니다.
delegate void AcceptWomanDelegate(Woman person);
static void DoWork(Woman woman, AcceptWomanDelegate acceptWoman) {
acceptWoman(woman);
}
DoWork
a를 취할 수있는 함수입니다 Woman
그리고 또한 Woman
, 그런 다음 인스턴스를 통과합니다 Woman
대의원에게. 고려하다 다형성 여기에있는 요소 중. Person
~이다 더 큰 ~보다 Woman
, 그리고 WorkWithPerson
~이다 더 큰 ~보다 WorkWithWoman
.
WorkWithPerson
또한 고려됩니다 더 큰 ~보다 AcceptWomanDelegate
분산의 목적을 위해.
마지막 으로이 세 줄의 코드가 있습니다.
Woman woman=new Woman();
DoWork(woman, WorkWithWoman);
DoWork(woman, WorkWithPerson);
ㅏ Woman
인스턴스가 생성됩니다. 그런 다음 Dowork가 호출되어 Woman
인스턴스뿐만 아니라에 대한 참조 WorkWithWoman
방법. 후자는 분명히 대의원 유형과 호환됩니다 AcceptWomanDelegate
- 유형의 하나의 매개 변수 Woman
, 반환 유형 없음. 그러나 세 번째 줄은 약간 이상합니다. 방법 WorkWithPerson
a Person
매개 변수로서 Woman
, 요구에 따라 AcceptWomanDelegate
. 그럼에도 불구하고, WorkWithPerson
대의원 유형과 호환됩니다. 비밀 가능한 경우 더 큰 유형의 대표의 경우 WorkWithPerson
더 작은 유형의 변수에 저장 될 수 있습니다. AcceptWomanDelegate
. 다시 한 번 직관적 인 것입니다. 만약에 WorkWithPerson
어떤 것과도 일할 수 있습니다 Person
, a Woman
틀릴 수 없습니다, 오른쪽?
지금 까지이 모든 것이 제네릭과 어떤 관련이 있는지 궁금 할 것입니다. 대답은 분산이 제네릭에도 적용될 수 있다는 것입니다. 사용 된 이전 예제 object
그리고 string
배열. 여기서 코드는 배열 대신 일반 목록을 사용합니다.
List<object> objectList=new List<object>();
List<string> stringList=new List<string>();
objectList=stringList;
이것을 시도하면 이것이 C#의 지원되는 시나리오가 아니라는 것을 알게 될 것입니다. C# 버전 4.0 및 .NET Framework 4.0에서 제네릭의 분산 지원이 정리되었으며 이제 새로운 키워드를 사용할 수 있습니다. 안에 그리고 밖으로 일반 유형 매개 변수. 특정 유형 매개 변수에 대한 데이터 흐름 방향을 정의하고 제한 할 수있어 차이가 작동합니다. 그러나의 경우 List<T>
, 유형의 데이터 T
양방향으로 흐르기 - 유형에 메소드가 있습니다. List<T>
그 귀환 T
값과 그러한 값을받는 값.
이러한 방향 제한의 요점은 다음과 같습니다 의미가있는 곳에서 분산을 허용합니다, 그러나 문제를 방지하십시오 이전 배열 예 중 하나에서 언급 된 런타임 오류와 같습니다. 유형 매개 변수가 올바르게 장식 된 경우 안에 또는 밖으로, 컴파일러는 그 차이를 확인하고 허용하거나 허용 할 수 있습니다. 시간을 컴파일하십시오. Microsoft는 .NET Framework의 많은 표준 인터페이스에 이러한 키워드를 추가하기 위해 노력했습니다. IEnumerable<T>
:
public interface IEnumerable<out T>: IEnumerable {
// ...
}
이 인터페이스의 경우 유형의 데이터 흐름입니다 T
개체는 분명합니다. 이 인터페이스에서 지원하는 방법에서만 검색 할 수 있으며 전달되지 않습니다.. 결과적으로, List<T>
이전에 설명했지만 사용 시도 IEnumerable<T>
:
IEnumerable<object> objectSequence=new List<object>();
IEnumerable<string> stringSequence=new List<string>();
objectSequence=stringSequence;
이 코드는 버전 4.0이므로 C# 컴파일러에 허용됩니다. IEnumerable<T>
이로 인해 공분산입니다 밖으로 유형 매개 변수의 지정자 T
.
일반적인 유형으로 작업 할 때는 차이와 컴파일러가 예상대로 코드가 작동하도록하기 위해 다양한 종류의 속임수를 적용하는 방식을 알고 있어야합니다.
이 장에서 다루는 것보다 분산에 대해 더 많이 알아야 할 것이 있지만, 이는 모든 추가 코드를 이해하기에 충분합니다.
ref :
Bart de Smet은 공분산 및 불안정에 대한 훌륭한 블로그 항목을 가지고 있습니다. 여기.
C#과 CLR은 모두 대의원에게 방법을 바인딩 할 때 참조 유형의 공분산 및 대조 분산을 허용합니다. 공분산은 메소드가 대의원의 반환 유형에서 파생 된 유형을 반환 할 수 있음을 의미합니다. Contra-variance는 메소드가 대의원의 매개 변수 유형의 기반 인 매개 변수를 취할 수 있음을 의미합니다. 예를 들어, 다음과 같이 정의 된 대의원이 주어지면됩니다.
대의원 MyCallback (Filestream S);
프로토 타입으로 된 메소드에 바인딩 된이 대의원 유형의 인스턴스를 구성 할 수 있습니다.
이와 같이:
문자열 somemethod (stream s);
여기서 Somemethod의 리턴 유형 (String)은 대의원의 반환 유형 (개체)에서 파생 된 유형입니다. 이 공분산은 허용됩니다. Somemethod의 매개 변수 유형 (stream)은 Delegate의 매개 변수 유형 (filestream)의 기본 클래스 인 유형입니다. 이 콘트라 분산이 허용됩니다.
공분산 및 비분평은 값 유형이나 공허에 대해서만 참조 유형에 대해서만 지원됩니다. 예를 들어, 다음 방법을 MyCallback 대의원에게 바인딩 할 수 없습니다.
int32 someothermethod (스트림);
일부 이소 메드의 리턴 유형 (int32)은 MyCallback의 리턴 유형 (개체)에서 파생되지만 int32는 값 유형이기 때문에이 형태의 공분산이 허용되지 않습니다.
분명히, 값 유형과 공극이 공분산 및 콘트라 분산에 사용될 수없는 이유는 이러한 것들에 대한 메모리 구조가 다양하기 때문에 참조 유형의 메모리 구조는 항상 포인터이기 때문입니다. 다행히도 C# 컴파일러는 지원되지 않는 작업을 시도하면 오류가 발생합니다.