'ref'와 'out'이 다형성을 지원하지 않는 이유는 무엇입니까?


124

다음을 수행하십시오.

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인수 모두에서 발생합니다 .

답변:


169

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

업데이트 :이 블로그 항목의 기초 로이 답변을 사용했습니다.

ref 및 out 매개 변수가 유형 변형을 허용하지 않는 이유는 무엇입니까?

이 문제에 대한 자세한 설명은 블로그 페이지를 참조하십시오. 좋은 질문에 감사드립니다.

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

의 당신이 수업을 가정하자 Animal, Mammal, Reptile, Giraffe, TurtleTiger, 명백한 서브 클래스의 관계.

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


당신은 유형의 변수를 전달할 수 AnimalM?

아니요. 해당 변수는를 포함 할 수 Turtle있지만 M포유류 만 포함한다고 가정합니다. A TurtleMammal.

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


당신은 유형의 변수를 전달할 수 GiraffeM?

번호 M에 기록 할 수있는 m, 그리고 M된 기록 할 수 있습니다 Tiger로를 m. 이제 Tiger실제로 유형 인 변수 에을 넣었습니다 Giraffe.

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


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

당신은 유형의 변수를 전달할 수 GiraffeN?

번호 N에 쓸 수 nN를 작성 할 수 있습니다 Tiger.

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


당신은 유형의 변수를 전달할 수 AnimalN?

흠.

글쎄, 왜 안돼? N에서 읽을 수없고 n쓸 수만 있습니다. 당신은 쓰기 Tiger유형의 변수에 Animal모든 설정이, 맞아?

잘못된. 규칙은 " N쓰기 만 가능 " 이 아닙니다 n.

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

1) 정상적으로 반환 되기 전에 N써야 합니다. ( 드로우면 모든 베팅이 해제됩니다.)nNN

2)에서 N무언가를 n읽기 전에 무언가를 써야합니다 n.

이는 다음과 같은 일련의 이벤트를 허용합니다.

  • x유형 의 필드 를 선언하십시오 Animal.
  • 패스 xout하는 매개 변수 N.
  • N기록 Tigern대한 별칭이다 x.
  • 다른 스레드에서 누군가가 기록 Turtle에를 x.
  • N의 내용을 읽으려고 시도하고 유형의 변수라고 생각 n하는 a Turtle를 발견합니다 Mammal.

분명히 우리는 그것을 불법으로 만들고 싶습니다.

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


최종 결론 : 매개 변수 유형이 다를 수 없습니다 . 그렇지 않으면 검증 가능한 유형 안전성을 깨는 것입니다.refout

기본 유형 이론의 이러한 문제에 관심이 있다면 C # 4.0에서 공분산 및 반공 변성이 작동하는 방식에 대한 시리즈를 읽어보십시오 .


6
+1. 문제를 명확하게 보여주는 실제 클래스 예제를 사용한 훌륭한 설명 (예 : A, B 및 C로 설명하면 작동하지 않는 이유를 설명하기가 더 어려워집니다).
Grant Wagner

4
나는이 사고 과정을 읽고 겸손하다고 느낀다. 책으로 돌아가는 게 좋을 것 같아요!
Scott McKenzie

이 경우에는 Abstract 클래스 변수를 인수로 사용하고 파생 클래스 개체를 전달할 수 없습니다 !!
Prashant Cholachagudda

그래도 out매개 변수를 "더 크게"만들 수없는 이유 는 무엇입니까? 설명한 시퀀스는 out매개 변수 변수 뿐만 아니라 모든 변수에 적용 할 수 있습니다 . 또한 독자의 요구에 인수 값을 캐스팅하기 Mammal로 그가 액세스를 시도하기 전에 Mammal그는 사려가 아닌 경우는 실패 할 수 있습니다 물론
astef

29

두 경우 모두 ref / out 매개 변수에 값을 할당 할 수 있어야하기 때문입니다.

b를 참조로 Foo2 메서드에 전달하려고 시도하고 Foo2에서 a = new A ()를 지정하려고하면 이것은 유효하지 않습니다.
쓸 수없는 같은 이유 :

B b = new A();

+1 바로 요점으로 그 이유를 완벽하게 설명합니다.
Rui Craveiro

10

당신의 고전적인 OOP 문제로 어려움을 겪고있는 공분산 (및 contravariance)을 참조 위키 백과 :이 사실만큼 직관적 인 기대를 무시 할 수 있습니다, 그것은 가변 (할당) 인수에 대한 기본 것들 대신에 파생 클래스의 대체를 허용하는 수학적으로 불가능하다 (그리고 또한 동일한 이유로 항목을 할당 할 수있는 컨테이너) Liskov의 원칙을 준수 합니다. 그 이유는 기존 답변에 스케치되어 있으며 이러한 위키 기사와 링크에서 더 깊이 탐구했습니다.

전통적으로 정적으로 유형 안전을 유지하면서 그렇게하는 것처럼 보이는 OOP 언어는 "속임수"(숨겨진 동적 유형 검사를 삽입하거나 검사하기 위해 모든 소스의 컴파일 타임 검사를 요구함)입니다. 근본적인 선택은이 공분산을 포기하고 실무자의 당혹감을 받아들이거나 (C #이 여기에서하는 것처럼) 동적 타이핑 접근 방식 (최초의 OOP 언어 인 Smalltalk처럼)으로 이동하거나 불변으로 이동 (단일- 할당) 데이터 (불변성 아래에서 공분산을 지원할 수 있으며 가변 데이터 세계에서 Square 하위 클래스 Rectangle을 가질 수 없다는 사실과 같은 다른 관련 퍼즐도 피할 수 있음).


4

치다:

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

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

B b = null;
Foo2(ref b);

형식 안전성을 위반합니다.


거기에 문제가있는 var로 인해 "b"의 불분명 한 추론 유형이 더 많습니다.

6 행에서 => B b = null을 의미한다고 생각합니다.
Alejandro Miralles

@amiralles-네, var완전히 틀 렸습니다. 결정된.
Henk Holterman 2015 년

4

다른 응답이이 동작의 이유를 간결하게 설명했지만, 이러한 성격의 무언가를 실제로 수행해야하는 경우 Foo2를 다음과 같이 일반적인 메서드로 만들어 유사한 기능을 수행 할 수 있다는 점을 언급 할 가치가 있다고 생각합니다.

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= no compile error!
    }

    void Foo(A a) {}

    void Foo2<AType> (ref AType a) where AType: A {}  
}

2

을 제공 Foo2하면의 일부 를 채우는 방법 만 알기 ref B때문에 잘못된 개체가 생성되기 때문 입니다 .Foo2AB


0

컴파일러가 당신의 의도가 무엇인지 확실히 알 수 있도록 객체를 명시 적으로 캐스팅하기를 원한다고 말하는 것이 아닌가요?

Foo2(ref (A)b)

그렇게 할 수 없습니다. "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);
}

이것은 컴파일되지 않지만 작동합니까?


0

유형에 대한 실용적인 예를 사용하면 다음과 같이 표시됩니다.

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

이제 조상 ( :) 을 취하는 함수가 있습니다 Object.

void Foo2(ref Object connection) { }

그게 무슨 문제일까요?

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

당신은 Bitmap당신의 SqlConnection.

그것은 좋지 않습니다.


다른 사람과 함께 다시 시도하십시오.

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

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

당신은 박제 OracleConnection당신의 이상 정상을 SqlConnection.


0

제 경우에는 함수가 객체를 받았는데 아무것도 보낼 수 없었기 때문에 간단히

object bla = myVar;
Foo(ref bla);

그리고 그것은 작동합니다

My Foo는 VB.NET에 있으며 내부 유형을 확인하고 많은 논리를 수행합니다.

내 답변이 중복되지만 다른 답변이 너무 길면 사과합니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.