Method Hiding Apologia

Method Hiding Apologia

  • Comments 25

Here's some back-and-forth from an email conversation I had with a user a while back.

Why should one avoid method hiding? 

If there were no advantages and only disadvantages then we would not have added it to the language in the first place. C# implements hiding because hiding is frequently useful. 
 
I therefore deny the premise of the question. One should not avoid method hiding if it is the right thing to do.

But I find method hiding confusing. It lets derived types appear to break the contracts of base types. If a derived type D hides a method M on base class B because D.M does something different than B.M, shouldn't it have a different name?

That sounds remarkably like a good answer to your original question.

However, method hiding is for exactly those times when you need to have two things to have the same name but different behaviour.
 
Obviously that is not a pleasant situation to be in. I agree with you that it is almost always preferable to have different things have different names. However, I can think of a few situations in which it's desirable. Consider this real-world example:
 
interface IEnumerable<T> : IEnumerable {
  new IEnumerator<T> GetEnumerator();
}
 
Is there a better name than GetEnumerator? That's what it does: it gets an enumerator. And in this case, you want to suppress the usage of the base type's non-generic GetEnumerator as much as possible; this version is intended to fully replace the non-generic version.

Of course, if C# had method return type covariance, this would not have to be a new method. That gives us a larger good reason for method hiding; it allows for something like return type covariance in a language that does not have such a feature.

I suppose that makes sense. Could you give some other examples of valid usage?

Sure, but I will have to digress in a prolix manner first.

We want to write computer programs which solve real-world problems, and therefore we want to design languages which enable developers to model their real-world problems in natural, flexible and intuitive ways. We want to take problem solving techniques which work well in the non-computer realm and enable developers to use the real-world intuitions and skills they’ve learned in their program.

One of the techniques that we developed millennia ago to solve problems is organization of objects into hierarchies. Hierarchies are often imperfect – there are the occasional platypuses which crop up and resist easy classification. But hierarchies are such a powerful and useful tool that we use them, despite their flaws, to help organize the world’s data and solve real problems.

Thus, it should be clear that class-based inheritance exists in programming languages because we wish to enable developers to naturally and easily write programs which model problems solved by hierarchies.

So here’s the rub: in a world where objects fit into a hierarchy, who gets to decide how to manipulate that object based on its position in the hierarchy?  Does the object get to decide (at runtime), or does the code doing the manipulation get to decide (at compile time)? 

The former is called “virtual dispatch”, the latter “non-virtual dispatch”.  Which you choose to use depends entirely upon the problem being modeled. It’s not like one of them is absolutely morally better than the other. The better one is the one which models the real-world problem better.

Many problems – probably the majority of problems in this space – are best modeled by letting the object decide how it is to be treated.  When you call Feed on an instance of Animal, let the instance decide what happens based on whether the instance is a Squid or a Zebra. Do a virtual dispatch at runtime. But it would be an error to say that because most problems are modeled this way, that the language ought not to allow modeling the problem any other way. Sometimes it is best for the caller to decide how the object is treated, because the caller has more information.

For example, when I was a teenager my father owned a restaurant. This was at the time that the Conservative government in Canada introduced a highly unpopular value-added sales tax on goods and services called, unimaginatively enough, the Goods and Services Tax.  The GST rules were roundly criticized as being insanely complicated. No government wants to be known as “the government that increased the price of food for poor people”, so grocery items were exempted from the GST. But what qualified as a “grocery item”? That had a whole other complex set of rules.

A cake sold in a grocery store, no GST. The exact same cake, sliced and plated in my father’s restaurant, GST. The exact same cake, NOT sliced, sold whole in the restaurant, no GST. A muffin in a grocery store, no GST. The same muffin in my father’s restaurant, GST. A box of six muffins sold in the restaurant, no GST.

I gather that in the last twenty years the tax has been rationalized somewhat. That’s not my point. My point is that this is a case where what the object is (virtual dispatch) is only one factor in how it behaves with respect to taxes. An equally important factor is how the object is being classified right now (non-virtual dispatch).

How might we model this in a programming language? There are lots of ways, and we could certainly argue about which is best. C# allows you the flexibility to come up with a variety of different designs and decide for yourself which models your problem best. One way in which we could reasonably model this problem would be to have a hierarchy:

abstract class Food {
    public decimal TaxRate { get { return 7.0m;} }
}
abstract class Grocery : Food {
    new public decimal TaxRate { get { return 0.0m; } }
}
class Cake : Grocery {
    new public decimal TaxRate { get { return 7.0m; } }
}

And so on. We could continue to complexify this to model the tax rules better. Now when I have a variable of type Cake, it’s tax rate is 7%. When I have the same cake stuck into a variable of type Grocery, its tax rate is 0%. That is, what the object “really” is at runtime is less relevant to the problem at hand then how I am presently classifying it.

One might make the argument that this is a violation of the Single Responsibility Principle, that the tax logic should be in a class of its own, that groceryness should be a flag and not a part of the type system, that really what we need is both a Food and a TransactionContext hierarchy, blah blah blah. I am sure that we could come up with a completely different hierarchy of objects which did not require hiding, and that other hierarchy might even be “better” in some ways. We seem to be badly conflating mechanism with policy here, which is worrisome.

But that's not my point. My point is that in C# we want to give you the flexibility to model problems this way if you choose to, if you believe that this is the best way to model them. And I think that’s a good thing.

I wrote a couple of articles about how we designed this same feature into JScript .NET. It is a language greatly complicated by its dynamic nature, so the design decisions became correspondingly harder. See

http://blogs.msdn.com/ericlippert/archive/2004/06/07/150367.aspx
http://blogs.msdn.com/ericlippert/archive/2004/06/08/151209.aspx

The comments are particularly useful in these articles as well.

  • I dunno for me this method hiding just gets confusing and starts to be spaghetti like.

    For example using the following code you can see the method is not really hidden I mean it makes sense why it isn't but if you were to blow this out on a few classes that inherit from each other then you can really make quite a confusing mess of things.

      class Program

       {

           static void Main(string[] args)

           {

               A a = new A();

               B b = new B();

               C c = new C();

               D d = new D();

               a.Foo();

               b.Foo();

               c.Foo();

               d.Foo();

               a = new B();

               a.Foo();

               a = new C();

               a.Foo();

               a = new D();

               a.Foo();

           }

       }

       class A

       {

           public void Foo() {Console.WriteLine("A Foo");}

       }

       class B : A

       {

           public new void Foo() { Console.WriteLine("B Foo"); }

       }

       class C : B

       {

           public new void Foo() { Console.WriteLine("C Foo"); }

       }

       class D : C

       {

           public new void Foo() { Console.WriteLine("D Foo"); }

       }

    Running that gives you

    A Foo

    B Foo

    C Foo

    D Foo

    A Foo

    A Foo

    A Foo

    It also doesn't seem to be able to go in reverse like a D can be an A. To me this is just not... I don;t know the word I am looking for. maybe clean, kind of confusing, I mean I know what I did because I wrote al the classes but you give someone just D and they do not know about B or C and well just could lead to many problems.

  • Hi Eric,

    As I see it, method hiding is the approach developers take when they can't do the "return type covariance" dance. It's nice to able to hide methods and as such I think it's definately important to C#, but unless the developer careful about the implementation, nasty side effects may show up.

    Calling the hidden method on a base type reveals the underlying implementation. You might say that this is a desired design, but currently this is not an intuitive or natural approach to designing an API I think.

    C# needs return type covariance (the following C# implemented for illustrative purposes). Looking forward to this feature in C# 4.0 (or C# 3.0 SP1).

    // Lackof return type covariance

    class A {

      public A Parent { get { return this.parent; }

    }

    class B : A {

      public new B Parent { get { return base.A as B; } }

    }

    // Return type covariance plays the nice game

    class A {

      public virtual A Parent { get { return this.parent; }

    }

    class B : A {

      override public B Parent { get { return base.A as B; } }

    }

  • Another place where method hiding is important is versioning.

    If a base class adds a new method (and maybe calls it internally); method hiding allows code compiled against the old version of the class to still run without unintended consequences. It also allows the code to be recompiled without changing behavior - you get a compiler warning because you're hiding without the "new" keyword, but your program still works if you choose to ignore the warning.

  • > C# needs return type covariance

    Needs?  It's managed to become a successful, useful language without return type covariance for the last three releases over almost a decade.

    > Looking forward to this feature in C# 4.0 (or C# 3.0 SP1).

    I'm afraid that you're going to be disappointed.

  • Eric,

    Well, I'm perhaps a bit biased!

    Don't get me wrong; C# is the best thing since sliced bread, but stating the language has been succesful without return type covariance for three releases doesn't mean it's an un-important feature. You're asking for comments, and return type covariance definitely relates to method hiding.

  • Well, don't get me wrong -- I would find return type covariance useful as well. That doesn't mean that its ever going to happen.

    We have no shortage of features -- we made a list of several dozens of possible features for future versions, and the classified them into "bad idea", "not worth doing", "useful", "great", and "must have for the next version".  

    Then we cut all but the last category.  Then we cut about half of that.  Return type covariance did not make that first cut, much less the second.

    Merely being useful is nowhere _near_ to our bar for actually designing, specifying, implementing, testing, documenting and maintaining a feature.

  • Eric,

    Thanks for the sharing some insights on your teams decisions. It's this kind of information that makes it easier to uderstand the design decisions that went into the language. I spend many hours with C# every day and have been looking for an educated answer to why return type covariance was left out of the specs. Now, I don't have extensive experience in the field of language design, but it just "felt" like a "useful" feature that didn't "seem" to have any unintentional side effects on the current specifications.

    I stated that this feature in particular is "useful", but I might have used a different adjective. Regardless, consider this a vote on the feature.

  • Indeed, lots of people want this feature. C++ has it. I can see how it would be handy.

    A point against it is the fact that the CLR does not natively support it; in order to make it work, the C# compiler would need to... you guessed it. Generate a hiding method with a different signature and then having the virtual method call it. Essentially, all the compiler would be doing is exactly what you now have to do in order to get something that looks like that, just hiding the indirection and the cost from you.

    That's not in itself a bad thing -- lots of "high level" features are things that you could plumb together yourself -- anonymous functions, iterators, "using" blocks, etc.  But it does illustrate that this would be solely a syntactic sugar, not really much of a fundamentally new feature.

  • Since we don't have return type covariance, we use method hiding to change the return type. Here's a rather silly example:

    public class TimeKeeper {

    public virtual string WhatTimeIsIt() {

    return DateTime.Now.ToShortTimeString();

    }

    }

    public class UniversalTimeKeeper : TimeKeeper {

    public new DateTime WhatTimeIsIt() {

    return DateTime.Now.ToUniversalTime();

    }

    }

    Note: the derived class does two things here: it changes both the return type AND the value.

    Now we have a problem: suppose someone used a

    TimeKeeper value = new UniversalTimeKeeper();

    string time = value.WhatTimeIsIt();

    we would want him to get the universal time, since this is a UniversalTimeKeeper after all and that's what virtual methods are for.

    But we did not override the function!

    If there is no return type covariance, there could at least be a syntax to do both an override and a hide:

    public class UniversalTimeKeeper : TimeKeeper {

    public override string WhatTimeIsIt() {

    return DataTime.Now.ToUniversalTime().ToShortTimeString();

    }

    public new DateTime WhatTimeIsIt() {

    return DataTime.Now.ToUniversalTime();

    }

    }

    TimeKeeper t1 = new UniversalTimeKeeper();

    UniversalTimeKeeper t2 = new UniversalTimeKeeper();

    t1.WhatTimeIsIt(); // calls overriden method

    t2.WhatTimeIsIt(); // calls new method

    I know. This is a little awkuard and potentially confusing. Does anyone have a better idea?

  • @Anders:

    I have somehow 'solved' your problem with covariance a few times using generics. A little more precisely I introduced an generic parameter describing the type of the derived class.

    In your example:

    class A<TSelf>

    where TSelf : A<TSelf>

    {

     public TSelf Parent { get { return this.parent; } }

    }

    and

    class B : A<B>

    {

    }

    now B has somehow magically get a parent of Type B.

    Although I used this technique a few times, I still don't know whether this is actually a good thing to do or a clear abuse of generics. Anyone got an opinion about that?

  • I was surprised that this doesn't work:

    public class Base

    {

    public virtual Base MakeAnother() { return new Base(); }

    }

    public class Derived : Base

    {

    override Base Base.MakeAnother() { return this.MakeAnother(); }

    public new Derived MakeAnother() { return new Derived(); }

    }

    As configurator suggests, I'm trying to do an override and a hide. I thought you might be able to do an "explicit" override, in the same way you can explicitly implement an interface. From what Eric says ("generate a hiding method with a different signature and then having the virtual method call it") I guess that you have to make some arbitrary change to the parameter list of the new MakeAnother() introduced by Derived, which seems worse than coming up with a new name for the method.

  • Rather than place the links to the most recent C# team content directly in Community Convergence, I have

  • Frederick,

    The main problem with your proposal is that is an evolutionary dead end with respect to covariance.  That is to say this approach only allows a one level inheritance heirarchy that supports covariance.  To achieve covariance again on a class C: B you would have to again engage in method hiding.

    It also isn't enforcing what you think it might be.  For example consider:

    class D: A<D> { }

    class E: A<D> { }

    You probably didn't want class E to be allowable.  But that is just a matter of disciplined coding by the consumer of class A.

    I must however admit I have often desired a built-in psuedo-type called Self for use with covariance.

  • Jeff,

    I don't know for sure if that's the point you made, but you can allow multiple levels of hierarchy:

    public abstract class A<TSelf> {...}

    public abstract class B<TSelf> : A<TSelf> {...}

    public class B : B<B> {...}

    public class C : B<C>

    and so on.

    Or did you meant the problem that you can cast an B<B> to A<B> but not to A<A> (or in the above example C to B)?

  • Frederick,

    Yes, you could extend the inheritance heirarchy in that fashion but it would require that you declare the abstract version of each class you wanted to be extensible.  This seems much the same amount of work as just using method hiding in the first place (I suppose if you had lots of covariant methods this would be a convenient time saver).

    Yes, not being able to cast C to B is one problem.  You could get around that by making all methods that take A or B as parameters generic (i.e. public void DoSomething<Self>(B<Self>)) but that also seems like a lot of work to maintain polymorphic behaviour.

    The other problem I was referring to was lets say you had:

    class Animal<Self> where Self: Animal, new()

    {

    public Self Reproduce() { return new Self(); }

    }

    class Cat: Animal<Cat> { }

    class Dog: Animal<Cat> { }

    Now we have a dog that gives birth to a cat when it reproduces!! I don't think this is the type of covariance you wanted to enable.

Page 1 of 2 (25 items) 12