“Why can’t I pass a reference to a derived class to a function that takes a reference to a base class by reference?” That’s a confusing question, but it’s phrased that way because the simpler phrasing is wrong!
Ths misleading simplified phrasing of the question is “Why can’t I pass a reference to a derived class to a function that takes a base class by reference?” And in fact the answer is “You can!”
class Base { } class Derived : Base { } class Program { static void f(Base b) { } public static void Main() { Derived d = new Derived(); f(d); } }
Our call to f
passes a reference to the
derived class
to a function that takes a reference to the base class.
This is perfectly fine.
When people ask this question, they are typically wondering about passing a reference to the base class by reference. There is a double indirection here. You are passing a reference to a variable, and the variable is a reference to the base class. And it is this double reference that causes the problem.
class Base { } class Derived : Base { } class Program { static void f(ref Base b) { } public static void Main() { Derived d = new Derived(); f(ref d); // error } }
Adding the ref
keyword to the parameter results
in a compiler error:
error CS1503: Argument '1': cannot convert from 'ref Derived' to 'ref Base'
The reason this is disallowed is that it would allow you to violate the type system. Consider:
static void f(ref Base b) { b = new Base(); }
Now things get interesting.
Your call to f(ref d)
passes a reference to a
Derived
by reference.
When the f
function modifies its formal parameter b
,
it’s actually modifying your variable d
.
What’s worse, it’s putting a Base
in it!
When f
returns, your variable d
,
which is declared as being a reference to a Derived
is actually a reference to the base class Base
.
At this point everything falls apart.
Your program calls some method like d.OnlyInDerived()
,
and the CLR ends up executing a method on an object that doesn’t
even support that method.
You actually knew this; you just didn’t know it. Let’s start from the easier cases and work up. First, passing a reference into a function:
void f(SomeClass s); ... T t = new T(); f(t);
The function f
expects to receive a reference to a
SomeClass
, but you’re passing a reference to a T
.
When is this legal?
“Duh.
T
must be SomeClass
or a class derived
from SomeClass
.”
What’s good for the goose is good for the gander.
When you pass a parameter as ref
,
it not only goes into the method, but it also comes out.
(Not strictly true
but close enough.)
You can think of it as a bidirectional parameter to the function call.
Therefore, the rule “If a function expects a reference to a class,
you must provide a reference to that class or a derived class”
applies in both directions.
When the parameter goes in, you must provide a reference to that
class or a derived class.
And when the parameter comes out,
it also must be a reference to that class or a derived class
(because the function is “passing the parameter” back to you, the caller).
But the only time that S
can be T
or a subclass,
while simultaneously having
T
be S
or a subclass
is when S
and T
are the same thing.
This is just the law of antisymmetry for partially-ordered sets:
“if a ≤ b
and b ≤ a,
then a = b.”
0 comments