2009-07-30 2 views
112

다음을 수행하십시오.'ref'와 'out'이 다형성을 지원하지 않는 이유는 무엇입니까?

class A {} 

class B : A {} 

class C 
{ 
    C() 
    { 
     var b = new B(); 
     Foo(b); 
     Foo2(ref b); // <= compile-time error: 
        // "The 'ref' argument doesn't match the parameter type" 
    } 

    void Foo(A a) {} 

    void Foo2(ref A a) {} 
} 

위의 컴파일 시간 오류가 발생하는 이유는 무엇입니까? 이 문제는 refout 인수 모두에서 발생합니다.

답변

155

=============

업데이트 :

Why do ref and out parameters not allow type variation?

이 이상 블로그 페이지를 참조하십시오 :이 블로그 항목에 대한 기준으로이 답변을 사용 이 문제에 대한 논평. 좋은 질문에 감사드립니다.

=============

은의 당신이 명백한 하위 클래스 관계 클래스 Animal, Mammal, Reptile, Giraffe, TurtleTiger을 가지고 가정하자.

이제 void M(ref Mammal m) 메서드가 있다고 가정합니다. M은 모두 m을 읽고 쓸 수 있습니다.


당신은 M에 유형 Animal의 변수를 전달할 수 있습니까?

아뇨 변수는 Turtle 포함 할 수 있지만, M 그것은 단지 포유류 포함되어 있다고 가정합니다. TurtleMammal이 아닙니다.

결론 1 : ref 매개 변수를 "더 크게"만들 수 없습니다. (이 포유 동물보다 더 많은 동물이기 때문에 변수가 더 많은 일을 포함 할 수 있기 때문에 "더 큰"지고 있습니다.)


당신은 M에 유형 Giraffe의 변수를 전달할 수 있습니까?

번호 Mm 쓸 수 있고, MTigerm에 쓰기 할 수 있습니다. 이제 실제로 Giraffe 유형의 변수에 Tiger을 넣었습니다.

결론 2 : ref 매개 변수를 "작게"만들 수 없습니다.


이제 N(out Mammal n)을 고려하십시오.

당신은 N에 유형 Giraffe의 변수를 전달할 수 있습니까?

번호 Nn 쓸 수 있고, NTiger를 작성 할 수 있습니다.

결론 3 : out 매개 변수를 "작게"만들 수 없습니다.


당신은 N에 유형 Animal의 변수를 전달할 수 있습니까?

흠.

글쎄, 왜 안 되니? Nn에서 읽을 수 없습니다. 쓰기 만 가능합니다. 맞습니까? Animal 유형의 변수에 Tiger을 쓰면 모두 설정됩니다. 맞습니까?

틀린. 규칙은 "Nn에만 쓸 수 있습니다"가 아닙니다.

규칙은 간단히 다음과 같습니다

1) N 일반적으로 nN 전에 반환에 기록한다. (N가 발생하는 경우, 모든 베팅은 꺼져 있습니다.)

2) Nn에서 뭔가를 읽기 전에 n에 뭔가를 쓰고있다. 이벤트의 순서를 허용

는 :

  • 유형 Animal의 필드 x를 선언합니다.
  • 매개 변수를 N으로 전달하십시오.
  • Nx의 별명 인 nTiger을 씁니다.
  • 다른 스레드에서는 누군가 xTurtle을 씁니다.
  • Nn의 내용을 읽으려고 시도하고 유형의 변수라고 생각하면 Turtle을 찾습니다.

분명히 우리는이를 불법으로 만들고 싶습니다.

결론 4 : out 매개 변수를 "더 크게"만들 수 없습니다.


최종 결론 : 어느 refout 매개 변수가 유형에 따라 다를 수 있습니다. 그렇지 않으면 확인 가능한 유형 안전을 중단해야합니다.

기본 유형 이론의 이러한 문제에 관심이 있으시면 my series on how covariance and contravariance work in C# 4.0을 읽는 것이 좋습니다.

+6

+1. 문제를 분명하게 보여주는 실제 수업의 예를 사용하여 훌륭한 설명을합니다 (예 : A, B, C로 설명하면 문제가 발생하지 않는 이유를 설명하기가 더 어려워집니다). –

+4

나는이 사고 과정을 읽는 것을 겸손하게 느낀다. 나는 책으로 돌아 가야한다고 생각한다. –

+0

이 경우, 추상 클래스 변수를 인수로 사용하지 않고 파생 클래스 객체를 전달합니다 !! –

27

두 경우 모두 ref/out 매개 변수에 값을 할당 할 수 있어야합니다.

b를 Foo2 메소드에 참조로 전달하려고 시도하면 Foo2에서 = 새 A()를 얻으 려 시도 할 때 유효하지 않습니다. 당신이 쓸 수
같은 이유 : Foo2BA 부분을 채우는 방법을 알고 있기 때문에

B b = new A(); 
+1

4 초 Ninj'd! :) – CannibalSmith

+0

+1 스트레이트 포인트로하고 완벽하게 이유를 설명합니다. –

2

Foo2을 제공하기 때문에 ref B가 조작 된 개체가 발생할 것입니다.

4

이 고려 :

class C : A {} 
class B : A {} 

void Foo2(ref A a) { a = new C(); } 

B b = null; 
Foo2(ref b); 

그것은 유형의 안전을 당신은 공분산 (및 contravariance)의 고전적인 OOP 문제로 어려움을 겪고있는

+0

거기에 문제가있는 var로 인해 "b"가 유추 된 유형이 더 명확하지 않습니다. –

+0

줄 6에서 당신은 => B b = null을 의미한다고 생각합니다; –

+0

@amiralles - 네,'var'은 완전히 잘못되었습니다. 결정된. –

9

을 위반, wikipedia를 참조하십시오이 사실은 무시 할 수만큼 직관적 인 기대에 따라, Liskov's principle을 여전히 존중하면서 변경 가능한 (할당 가능한) 인수 (및 동일한 이유로 항목이 할당 가능한 컨테이너)에 대해 기본 클래스 대신 파생 클래스를 대체하는 것을 수학적으로 허용하는 것은 수학적으로 불가능합니다. 왜 그렇게 기존의 답변에서 스케치하고, 위키 기사 및 링크에서 더 깊이 탐구.

전통적으로 정적 유형 안전성이 유지되는 것처럼 보이는 OOP 언어는 숨겨진 동적 유형 검사를 삽입하거나 검사 할 모든 소스의 컴파일 타임 검사가 필요합니다. 근본적인 선택은이 공분산을 포기하고 실무자들의 당황 (C#이 여기에있는 것처럼)을 받아들이거나 다이나믹 타이핑 접근법 (스몰 토크, 최초의 OOP 언어로서)으로 이동하거나 불변의 할당) 데이터는 함수 언어와 마찬가지로 (변경 불가능한 경우 공분산을 지원할 수 있으며 변경 가능한 데이터 영역에서 Square 서브 클래스 Rectangle을 가질 수 없다는 사실과 같은 다른 관련 퍼즐을 피할 수 있습니다.

0

컴파일러가 명시 적으로 객체를 캐스팅하여 의도가 무엇인지 확신 할 수 있는지 알려주지 않습니까?

Foo2(ref (A)b) 
+0

"ref 또는 out 인수는 할당 가능한 변수 여야합니다." –

0

는 안전의 관점에서 의미가 만들지 만, 컴파일러는 오류 대신 경고를 준 경우 참조에 의해 전달 polymoprhic 객체의 합법적 인 용도가 있기 때문에 나는 그것을 선호하는 것입니다. 예 :

class Derp : interfaceX 
{ 
    int somevalue=0; //specified that this class contains somevalue by interfaceX 
    public Derp(int val) 
    { 
    somevalue = val; 
    } 

} 


void Foo(ref object obj){ 
    int result = (interfaceX)obj.somevalue; 
    //do stuff to result variable... in my case data access 
    obj = Activator.CreateInstance(obj.GetType(), result); 
} 

main() 
{ 
    Derp x = new Derp(); 
    Foo(ref Derp); 
} 

이 코드는 컴파일되지 않지만 작동합니까?

SqlConnection connection = new SqlConnection(); 
Foo(ref connection); 

을 그리고 지금 당신은 조상 (Object) 소요 함수가 있습니다 : 당신이 당신의 유형에 대한 실용적인 예제를 사용하는 경우

0

, 당신은 그것을 볼 수

void Foo2(ref Object connection) { } 

무엇이 잘못되었을 수 있습니까?

void Foo2(ref Object connection) 
{ 
    connection = new Bitmap(); 
} 

당신은 당신의 SqlConnectionBitmap을 할당 할 수 있었다.

괜찮습니다.


다른 사람들과 다시 시도 :

SqlConnection conn = new SqlConnection(); 
Foo2(ref conn); 

void Foo2(ref DbConnection connection) 
{ 
    conn = new OracleConnection(); 
} 

당신은 OracleConnection 이상 탑 당신의 SqlConnection의 인형.