为什么ref参数不是逆变的?

时间:2009-08-28 12:43:34

标签: c# ref contravariance

这有效:

EndPoint endPoint = new IPEndPoint(_address, _port);
_socket.ReceiveFrom(buffer, 0, 1024, SocketFlags.None, ref endPoint);

但这不是:

IPEndPoint endPoint = new IPEndPoint(_address, _port);
_socket.ReceiveFrom(buffer, 0, 1024, SocketFlags.None, ref endPoint);

(注意endPoint的类型)

这看起来很奇怪。为什么ref关键字会破坏参数的逆转?

3 个答案:

答案 0 :(得分:17)

因为在方法签名中,endPoint参数声明为EndPoint,而不是IPEndPoint;无法保证该方法不会将endPoint设置为另一种EndPoint,而IPEndPoint不能分配给FooEndPoint变量。

例如,假设您有一个继承自EndPoint的{​​{1}}类,以及一个带有Foo参数的ref EndPoint方法:

public class FooEndPoint : EndPoint
{
   ...
}

public void Foo(ref EndPoint endPoint)
{
    ...
    endPoint = new FooEndPoint();
    ...
}

如果您能够将IPEndPoint传递给该方法,FooEndPointendPoint参数的分配将在运行时失败,因为FooEndPoint不是一个IPEndPoint

答案 1 :(得分:2)

因为ReceiveFrom方法可以创建新的EndPoint,但不能创建IPEndPoint。此参数有两种方式,因此类型需要完全匹配。

答案 2 :(得分:0)

refout参数的矛盾之处在于它们是引用。这意味着,当您将普通参数传递给方法时,CLR会解析对该值的引用,并将该值作为参数传递,而当参数为refout时,CLR会传递参数整个引用作为参数。 .NET中的引用类型为WeakReference<T>,并且您将引用包装在其中。
赋予WeakReference<T>的通用参数是您引用的类型,例如:

public void Test<T>(ref T reference)
{
    WeakReference<T> weakReference = __makeref(reference);
}

现在,我将进一步介绍另一个示例:

public void Test(ref string reference)
{
    WeakReference<string> weakReference = __makeref(reference);
}

这是另外一个:

public void Test(ref string reference)
{
    WeakReference<object> weakReference = __makeref(reference);
}

后面的示例将给您一个编译器错误:如您所见,泛型参数不是协变的。因此,当您将参数的引用包装到WeakReference<T>时,T必须恰好是该参数的类型。

即使您没有使用__makeref,您也可以理解CLR的内部行为就是这样。
此外,在较低级别中,引用是包含值的类型和值本身的结构。因此,如果您可以将引用转换为其任何超类型,则将更改引用的类型,当您尝试获取其值时,它会失败,因为值的类型因引用类型的不同而不同内。