Why are ref parameters not contravariant?
-
20-09-2019 - |
Question
This works:
EndPoint endPoint = new IPEndPoint(_address, _port);
_socket.ReceiveFrom(buffer, 0, 1024, SocketFlags.None, ref endPoint);
But this does not:
IPEndPoint endPoint = new IPEndPoint(_address, _port);
_socket.ReceiveFrom(buffer, 0, 1024, SocketFlags.None, ref endPoint);
(Note the type of endPoint)
Which seems odd. Why does the ref keyword break parameter contravariance?
Solution
Because in the method signature, the endPoint
parameter is declared as EndPoint
, not IPEndPoint
; there is no guarantee that the method won't set endPoint
to another kind of EndPoint
, which would not be assignable to a IPEndPoint
variable.
For instance, assume you have a FooEndPoint
class that inherits from EndPoint
, and a Foo
method that takes a ref EndPoint
parameter :
public class FooEndPoint : EndPoint
{
...
}
public void Foo(ref EndPoint endPoint)
{
...
endPoint = new FooEndPoint();
...
}
If you were able to pass a IPEndPoint
to that method, the assigment of a FooEndPoint
to the endPoint
parameter would fail at runtime, because a FooEndPoint
is not a IPEndPoint
OTHER TIPS
Because the method ReceiveFrom can create a new EndPoint - but not IPEndPoint. This parameter works kind of in two ways, so the type needs to match exactly.
The contraviariance is lose on ref
and out
parameters because they are references. That means when you pass a normal parameter to a method, CLR will solve that reference to a value and will pass that value as an argument, whereas when a parameter is either ref
or out
CLR will pass the whole reference as an argument. The reference type in .NET is WeakReference<T>
and you're wrapping your reference in that.
The generic argument given to WeakReference<T>
is the type your reference is, for example:
public void Test<T>(ref T reference)
{
WeakReference<T> weakReference = __makeref(reference);
}
Now, I'm going deeper with another example:
public void Test(ref string reference)
{
WeakReference<string> weakReference = __makeref(reference);
}
And this is one more:
public void Test(ref string reference)
{
WeakReference<object> weakReference = __makeref(reference);
}
The latter example will give you a compiler error: as you can see, generic parameters are not contravariant. Therefore, when you wrap the reference of the parameter to a WeakReference<T>
, T
must be exactly the type of that parameter.
Even if you're not using __makeref
, you could understand that the internal behavior of CLR is such a thing.
Furthermore, in a lower level references are structures containing the type of the value and the value itself. So, if you could cast a reference to any of its super types, you'll change the type of the reference and when you try to get its value, it fails because the type of the value is different by the type of the reference it's in.