왜 'Ref'와 'out'이 다형성을 지원하지 않습니까?
-
05-07-2019 - |
문제
다음을 수행하십시오.
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) {}
}
위의 컴파일 타임 오류가 발생하는 이유는 무엇입니까? 이것은 둘 다에서 발생합니다 ref
그리고 out
논쟁.
해결책
=============
업데이트 :이 답변을이 블로그 항목의 기초로 사용했습니다.
REF 및 OUT 매개 변수가 유형 변동을 허용하지 않는 이유는 무엇입니까?
이 문제에 대한 자세한 내용은 블로그 페이지를 참조하십시오. 위대한 질문에 감사드립니다.
=============
수업이 있다고 가정 해 봅시다 Animal
, Mammal
, Reptile
, Giraffe
, Turtle
그리고 Tiger
, 명백한 서브 클래스 관계와 함께.
이제 방법이 있다고 가정 해 봅시다 void M(ref Mammal m)
. M
읽고 쓸 수 있습니다 m
.
변수 유형을 전달할 수 있습니까?
Animal
에게M
?
아니요. 그 변수는 a를 포함 할 수 있습니다 Turtle
, 하지만 M
포유류 만 포함한다고 가정합니다. ㅏ Turtle
아닙니다 Mammal
.
결론 1: ref
매개 변수는 "더 큰"것을 만들 수 없습니다. (포유류보다 더 많은 동물이 있으므로 변수는 더 많은 것을 포함 할 수 있기 때문에 "더 커집니다.)
변수 유형을 전달할 수 있습니까?
Giraffe
에게M
?
아니. M
글을 쓸 수 있습니다 m
, 그리고 M
글을 쓰고 싶을 수도 있습니다 Tiger
~ 안으로 m
. 이제 당신은 a를 넣었습니다 Tiger
실제로 유형 인 변수로 Giraffe
.
결론 2: ref
매개 변수는 "더 작게"만들 수 없습니다.
이제 고려하십시오 N(out Mammal n)
.
변수 유형을 전달할 수 있습니까?
Giraffe
에게N
?
아니. N
글을 쓸 수 있습니다 n
, 그리고 N
글을 쓰고 싶을 수도 있습니다 Tiger
.
결론 3: out
매개 변수는 "더 작게"만들 수 없습니다.
변수 유형을 전달할 수 있습니까?
Animal
에게N
?
흠.
글쎄, 왜 안돼? N
읽을 수 없습니다 n
, 그것은 단지 그것에 쓸 수 있습니까? 당신은 a Tiger
변수 유형으로 Animal
그리고 당신은 모두 설정되어 있습니까?
잘못된. 규칙은 "" "N
쓸 수 있습니다 n
".
규칙은 간단히 다음과 같습니다.
1) N
편지를 써야합니다 n
~ 전에 N
정상적으로 반환됩니다. (만약에 N
던지기, 모든 베팅은 꺼져 있습니다.)
2) N
무언가를 써야합니다 n
그것이 무언가를 읽기 전에 n
.
이 일련의 이벤트를 허용합니다.
- 필드를 선언하십시오
x
유형의Animal
. - 통과하다
x
로서out
매개 변수N
. N
aTiger
~ 안으로n
, 그것은 별명입니다x
.- 다른 스레드에서 누군가가 a
Turtle
~ 안으로x
. N
내용을 읽으려고 시도합니다n
, 그리고 발견 aTurtle
그것이 생각하는 것은 유형의 변수입니다Mammal
.
분명히 우리는 그것을 불법으로 만들고 싶습니다.
결론 4: out
매개 변수는 "더 큰"것으로 만들 수 없습니다.
마지막 결론: 어느 것도 아니다 ref
...도 아니다 out
매개 변수는 해당 유형을 변경할 수 있습니다. 그렇지 않으면 검증 가능한 유형 안전을 깨는 것입니다.
기본 유형 이론의 이러한 문제가 관심을 보이면 읽기를 고려하십시오. C# 4.0에서 공분산 및 비밀화가 어떻게 작동하는지에 대한 나의 시리즈.
다른 팁
두 경우 모두 ref/out 매개 변수에 값을 할당 할 수 있어야합니다.
B를 참조로 FOO2 메소드로 전달하려고하면 FOO2에서 A = New A ()를 주장하려고하면 유효하지 않습니다.
당신이 쓸 수없는 것과 같은 이유 :
B b = new A();
당신은 고전적인 OOP 문제로 어려움을 겪고 있습니다 공분산 (및 비밀), 참조 위키 백과:이 사실이 직관적 인 기대를 무시할 수 있듯이, 수학적으로도 파생 된 클래스를 기본 클래스 대신에 변호 가능한 (할당 가능한) 인수 (및 동일한 이유로 항목이 할당 할 수있는 컨테이너)를 대신하여 여전히 존중하는 것은 불가능합니다. Liskov의 원칙. 그것이 기존의 답변에서 스케치되어 왜이 위키 기사와 링크에서 더 깊이 탐구됩니다.
전통적으로 정적으로 TypeSafe를 유지하면서 그렇게하는 것으로 보이는 OOP 언어는 "속임수"(숨겨진 동적 유형 검사를 삽입하거나 모든 소스의 컴파일 시간 시험이 필요함); 근본적인 선택은 다음과 같습니다.이 공분산을 포기하고 실무자의 당황을 받아들이거나 (C#이 여기에서와 같이) 동적 타이핑 접근법 (최초의 OOP 언어, SmallTalk, DID)으로 이동하거나 불변 (단일-로 이동합니다. 과제) 데이터는 기능 언어와 같은 데이터 (불변성 하에서 공분산을 지원할 수 있으며, 변이 가능한 데이터 세계에서 제곱 서브 클래스 사각형을 가질 수 없다는 사실과 같은 다른 관련 퍼즐을 피할 수 있음).
고려하다:
class C : A {}
class B : A {}
void Foo2(ref A a) { a = new C(); }
B b = null;
Foo2(ref b);
유형 안전성을 위반합니다
다른 응답은이 행동의 배후에 대한 추론을 간결하게 설명했지만,이 특성에 대해 실제로해야한다면 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 {}
}
기부하기 때문에 Foo2
ㅏ ref B
기형 객체가 발생합니다 Foo2
채우는 방법 만 알고 있습니다 A
부분의 B
.
컴파일러가 당신이 당신의 의도가 무엇인지 알 수 있도록 객체를 명시 적으로 캐스팅하기를 원한다고 말하지 않습니까?
Foo2(ref (A)b)
안전 관점에서 의미가 있지만, 컴파일러가 오류 대신 경고를 한 경우 선호했을 것입니다. 예를 들어
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
):
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();
}
당신은 an을 채웠습니다 OracleConnection
당신의 과잉 SqlConnection
.
내 경우 내 기능은 객체를 받아 들였고 아무것도 보낼 수 없었기 때문에 단순히
object bla = myVar;
Foo(ref bla);
그리고 그것은 작동합니다
내 foo는 vb.net에 있고 내부 유형을 확인하고 많은 논리를 수행합니다.
내 대답이 중복되면 사과하지만 다른 사람들은 너무 길다면 사과드립니다.