Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the use case for this inheritance idiosyncrasy?

When inheriting an inherited class, the new / override behaviour is not what I would expect:

$ cat Program.cs
using System;

class A {
    public virtual void SayHi() {
        Console.WriteLine("From A");
    }
}
class B : A { 
    public new virtual void SayHi()  {
        Console.WriteLine("From B");
    }
}
class C : B { 
    public override void SayHi() {
        Console.WriteLine("From C");
    }
}

public class Program {
    public static void Main() {
        A p = new C();
        p.SayHi();
    }
}

$ ./Program.exe 
From A

As class C overrides the sayHi() method I would expect the output to be From C. Why does the B class's new modifier take precedence here? What is the use case for that? Especially as it breaks the obvious use case of having C really override A.

Note that the above code was run on Mono 2.10 running on a Debian-derived distro. But I have confirmed the same behaviour using the C# compiler in MS Visual Studio.

like image 776
dotancohen Avatar asked Jun 14 '12 18:06

dotancohen


1 Answers

The new modifier causes member hiding, which breaks the polymorphic relationship in your class hierarchy. The SayHi method of B is treated as distinct (not an override) from A’s (thus the choice of the word “new” as keyword). C’s method then overrides B’s, not A’s (which remains hidden).

Therefore, when you call SayHi on a C instance through an A reference, the runtime would resolve it against the A type, not the C type (within which SayHi is a “new” method inherited from B).

If, on the other hand, you were to run:

B p = new C();
p.SayHi();

…you would get the expected polymorphic result:

From C

Edit: Since you requested a use-case, here’s one. Before the introduction of generics in .NET Framework 2.0, member hiding was sometimes used as a means of altering the return types of inherited methods in derived classes (something you can't do when overriding) in order to return more specific types. For example:

class ObjectContainer
{
    private object item;

    public object Item 
    {
        get { return item; }
        set { item = value; }
    }
}

class StringContainer : ObjectContainer
{
    public new virtual string Item
    {
        get { return base.Item as string; }
        set { base.Item = value as string; }
    }
}

class QuotedStringContainer : StringContainer
{
    public override string Item
    {
        get { return "\"" + base.Item + "\""; }
    }
}

The Item property of the ObjectContainer class returns a plain object. However, in StringContainer, this inherited property is hidden to return a string instead. Thus:

ObjectContainer oc = new StringContainer();
object o  = oc.Item;   // Valid, since ObjectContainer.Item is resolved
string s1 = oc.Item;   // Not valid, since ObjectContainer.Item is still resolved
string s2 = ((StringContainer)oc).Item;   
                       // Valid, since StringContainer.Item is now resolved

The QuotedStringContainer class overrides the Item property of StringContainer, inheriting its string return type; however, it is still hidden from the object-returning Item property of ObjectContainer. If it were not this way, there would be no way of reconciling their disparate return types…

ObjectContainer oc = new QuotedStringContainer();
object o  = oc.Item;   // Valid, since ObjectContainer.Item is resolved
string s1 = oc.Item;   // Not valid, since ObjectContainer.Item is still resolved
string s2 = ((StringContainer)oc).Item;   
                       // Valid, since QuotedStringContainer.Item is now resolved
                       // (polymorphism!)
string s3 = ((QuotedStringContainer)oc).Item;   
                       // Valid, since QuotedStringContainer.Item is now resolved
like image 187
Douglas Avatar answered Sep 28 '22 02:09

Douglas