Sunday, May 17, 2009

Polymorphism, why do you fail me?

Polymorphism, it's the cornerstone of object oriented programming. We couldn't live without it. So then, tell me why this rather trivial case fails to compile.

public class B : A { }

public void Foo (ref A bar) { }

public void Baz ()
{
B b = new B ();
Foo (ref b);
}


The issue is hat you can't pass a 'B' parameter type by ref where a 'ref A' is expected. Why is this? What case could possibly fail if this was allowed?

11 comments:

Anonymous said...

public void Foo (ref A bar)
{
bar = new A();
}

If you call Foo with an B-Instance, it's like:
B b = new A();
And that is no allowed?!
I'm not sure, maybe I'm talking bullshit. :|

Ed Ropple said...

I think the anonymous poster there has it right. You could conceivably assign a base class to a derived class pointer. Such way lies madness and exceptions and ow.

Alan said...

Hah, that's a very good point. Now why did neither of us think of that before I blogged the question!

Anonymous said...

Try this:

B b = new B();
A a = (A)b;
Foo(ref a);

Should work.

Zaiden said...

I guess that the problem is because the compiler is treating ref types as invariant types, instead of covariant as you - and we all - want.

But I'm just guessing here...

Anonymous said...

The point to ref and out is that the called method can change the object reference, as the first anonymous poster pointed out was possible.

The ramifications, though:

// C is a sibling to B
class C : A {}

public void Bar(ref A bar)
{
b = new C();
// should be valid, as C is a
// subclass of A.
}

thus allowing the bizarro (and invalid):

public void Qux()
{
B b = new B();
Bar (ref b);
}

If the above were possible, a 'B' instance variable would in fact be referencing a type that cannot be a 'B' -- a 'C'!

This is, of course, bad.

It is for similar reasons that (without covariant/contravariant support) an IEnumerable[string] isn't implicitly convertable to an IEnumerable[object], and an IList[string] will never be implicitly convertable to an IList[object].

Zaiden said...

Basically, the same reason behind the invariant types on 2.0 Generics.

Great explanation jonmpryor!

Anonymous said...

public void Baz ()
{
B b = new B ();
Foo (ref b);
}

This wont work as Foo() requires an A-type. But as B is a child of A you can try:

public void Baz ()
{
A b = new B ();
Foo (ref b);
}

This will work and you don't need any casts

Hari said...
This comment has been removed by the author.
Hari said...

* A normal (in) parameter allows arguments from subclasses, but more importantly, disallows superclass arguments

* A return "parameter" allows assignment to "arguments" from superclasses, but more importantly, disallows assignment to subclass "arguments"

* An out parameter is like a return value, and theoretically (not in C#) can accept arguments from superclasses

* A ref parameter is in/out and thus allows neither subclasses nor superclasses

So, your question could've been: why doesn't C# allow superclasses in out parameters?

* firstly, it introduces complications in the overload resolution mechanism for relatively low benefit

* secondly, there's no way to distinguish, in a type-safe manner, between ref and out parameters at the CIL level. Yes, you can attach an [Out] attribute, but this doesn't enforce type-safety.

sports handicapping services said...

Fantastic post.I like your article.Very informative post.

Hit Counter