“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