Search
Close this search box.

C#/.NET Little Pitfalls: The Default is to Hide, Not Override

C# is a wonderful language for modern programming.  While everything in C# has a reason and a place, occasionally, there are things that can be confusing for a developer who isn’t aware of what is happening behind the scenes. This is my fourth post in the Little Pitfalls series where I explore these issues; the previous Little Pitfall post can be found here.

Today we are going to look at a potential pitfall that can bite developers who expect the default behavior of declaring the same method (with same signature) in a subclass to perform an override. 

In particular, if the developer came from the C++ world, this may run counter to their expectations.  While the C# compiler does a good job of warning you of this event, it is not an error that will break your build, so it’s worth noting and watching out for.

Overview: Hiding vs. Overriding

Note: even though I’m just covering methods in this post, properties can also be overridden and hidden with the same potential pitfall.

When you have a base-class you are going to inherit from, there are two basic choices for “replacing” the functionality of a base-class method in the sub-class: hiding and overriding.

Let’s look at overriding first because it is the behavior many people tend to expect.  To override behavior from a base-class method, the method must be marked virtual or abstract in the base-class.  The virtual keyword indicates the method may be overridden in the subclass and allows you to define a default implementation of the method, and the abstract keyword says that the method must be overridden in a concrete subclass and that there is no default implementation of the method..

For example, let’s take the classes below:

1 : public class A {
  2 :  // Must be marked virtual (if has body) or abstract (if no body)
       3 : public virtual void WhoAmI() {
    4 : Console.WriteLine("I am an A.");
    5:
  }
  6:
}
7 : 8 : public class B : A {
  9 :  // must be marked override to override original behavior
       10 : public override void WhoAmI() {
    11 : Console.WriteLine("I am a B.");
    12:
  }
  13:
}

In this example, B.WhoAmI() overrides the implementation of A.WhoAmI().  When overriding, the decision of what method to call is made at runtime, so if you have an object of type B held in a reference of type A, the B.WhoAmI() will still get called because it looks up the actual type of the object the reference refers to at runtime, and not the type of the reference itself, to determine which version of the method to call:

1 : public static void Main() 2 : {
  3 : B myB = new B();
  4 : A myBasA = myB;
  5 : 6 : myB.WhoAmI();  // I am a B
  7 : myBasA.WhoAmI();   // I am a B
  8:
}

Note that in the code above, even though reference myBasA is typed as A, the actual object being referred to is of type B, thus B.WhoAmI() will be called at runtime.

Hiding, however, takes a different approach.  In hiding what we do is create a new method with the exact same name and signature, but we (should) mark it as new.  In this case B’s implementation hidesA’s implementation:

1 : public class A {
  2 :  // Method to hide can be virtual or non-virtual (but not abstract)
       3 : public void WhoAmI() {
    4 : Console.WriteLine("I am an A.");
    5:
  }
  6:
}
7 : 8 : public class B : A {
  9 :  // SHOULD use new to explicitly state intention to hide original.
       10 : public new void WhoAmI() { 11 : Console.WriteLine("I am a B.");
  12:
}
13:
}

So now, with hiding, the method replaces the definition for class B, which sounds the same on the surface, but in the case of hiding, the method to call is determined at compile-time based on the type of the reference itself.  This means that the results of main from before are now:

1 : public static void Main() 2 : {
  3 : B myB = new B();
  4 : A myBasA = myB;
  5 : 6 : myB.WhoAmI();  // I am a B
  7 : myBasA.WhoAmI();   // I am an A
  8:
}

Notice that even though the object in both cases being referred to is type B, the version of WhoAmI() that is called depends solely on the type of the reference, not on the type of the object being referred to.  Thus it will be A.WhoAmI() that will be called here.

Both hiding and overriding are valid and useful ways to replace base class functionality, and when you’d use each depends on your design and situation. 

Little Pitfall: Hide is the default behavior

So all that discussion was mainly academic, right?  If so, where does the problem lie?  Well, the main thing to watch out for is that you aren’t required to use the override or new keyword when “replacing” a method in a subclass.  Both of those keywords are purely optional, even if the original method was marked as virtual or abstract

Consider this code example:

1 : public class A {
  2 :  // Method is marked virtual, which signals intent to be overridden
       3 : public virtual void WhoAmI() {
    4 : Console.WriteLine("I am an A.");
    5:
  }
  6:
}
7 : 8 : public class B : A {
  9 :  // Person who designed sub-class didn't use 'new' or 'override'
       // explicitly...
       10 : public void WhoAmI() {
    11 : Console.WriteLine("I am a B.");
    12:
  }
  13:
}

So the question is, does B.WhoAmI() hide, or override A.WhoAmI()?  The base class implementation was clearly marked virtual.  In C++, if the base-class method is marked as virtual, then the sub-class method will be an override and you need not repeat the virtual keyword. 

This is not true in C#, however, the default behavior is to implicitly hide (not override) if no keyword explicitly says otherwise.  This gives us the following results:

   1: public static void Main()
   2: {
   3:     B myB = new B();
   4:     A myBasA = myB;
   5:  
   6:     myB.WhoAmI();     // I am a B
   7:     myBasA.WhoAmI();  // I am an A
   8: }

This can trip up C++ developers who don’t know about this default behavior difference between the two languages.

To be fair, C# does give a compiler warning if you are not explicit, asking you politely to explicitly specify either override or new, but it doesn’t require you to do so:

‘B.WhoAmI()’ hides inherited member ‘A.WhoAmI()’. Use the new keyword if hiding was intended.

Note: Remember, don’t ignore your warnings!  As they say, a warning is an error waiting to happen.  If you really want motivation to clean up warnings in your code, go into your project settings and enable “Treat warnings as errors” on the Build tab.

Side Note: Implementing interfaces using hiding or overriding

If your class implements an interface, and you want that interface behavior to be overridable, make sure you mark the base class that implements the interface’s methods as virtual or abstract.

Let me illustrate with a quick example.  Let’s say that you’re building a messaging library to abstract your messaging provider implementation away from your projects.  So in your code-base, you create an AbstractMessageConsumer that contains the basic message consumer functionality, and then inheriting from that you create a TopicMessageConsumer which adds to that the specifics for consuming from a pub/sub topic.

Now, obviously these type of classes are each going to contain some core resources that need to be managed and cleaned up (like connections to the underlying message source).  So you decide to implement the IDisposable interface. 

Because it’s highly possible if we’re using a factory pattern that we’ll be referring to concrete implementations of a message consumer as an AbstractMessageConsumer, we’d want to implement IDisposable in such a way that the Dispose() will call the appropriate method based on the concrete class and not just the one defined in AbstractMessageConsumer.

So we may do something like this:

1 : public abstract class AbstractMessageConsumer : IDisposable 2 : {
  3 : public virtual void Dispose() 4 : {
    5 :  // dispose of any resources in the abstract base here...
         6:
  }
  7:
}
8 : 9 : public sealed class TopicMessageConsumer : AbstractMessageConsumer 10
    : {
  11 : public override void Dispose() 12 : {
    13 :  // dispose of any resources just in the topic message consumer here...
          14 : 15
        :  // then dispose of the base by invoking the base class definition.
           16 : base.Dispose();
    17:
  }
  18:
}

By defining Dispose() as virtual in AbstractMessageConsumer, we allow the actual definition of Dispose() to be used to be resolved at run-time, thus we will be assured that the correct version will be called.

So, what if we hadn’t done this, and would have instead defined our classes like this:

1 : public abstract class AbstractMessageConsumer : IDisposable 2 : {
  3 :  // note not virtual...
       4 : public void Dispose() {
    ...
  }
  5:
}
6 : 7 : public sealed class TopicMessageConsumer : AbstractMessageConsumer 8 : {
  9 : public void Dispose() {
    ...
  }
  10:
}

Then if we would have tried to call Dispose() on an AbstractMessageConsumer reference to a TopicMessageConsumer, we would have gotten the wrong Dispose()!

1 : AbstractMessageConsumer mc = new TopicMessageConsumer();
2 : 3 :       // Because TopicMessageConsumer only hides Dispose(),
              // AbstractMessageConsumer's
         4 :  // Dipose() method is the one called here.
              5 : mc.Dispose();

So remember, if you implement an interface and want that implemented behavior to be overridable in a sub-class, make sure you mark your interface implementation methods (and/or properties) as virtual or abstract and then correctly override them in a sub-class.

Note: If you have no intention of allowing your class to be inherited from, consider making the class sealed to prevent accidental hiding problems from occurring.

Summary

Hiding has some very handy uses and is a valuable part of the C# toolbox.  That said, it can be confusing for a developer who doesn’t know that the implicit behavior in C# is to hide and not to override.

To help make sure you always get the correct behavior you want, you can:

  • Enable “Treat warnings as errors” option so that you will not be surprised by a missed warning, or at least get in the habbit of checking warnings after builds.
  • When implementing an interface where the behavior of the implementation is overridable, make the interface implementation virtual.
  • Always avoid the implicit hide behavior – make your definition explicit by using the override or new keywords so that your intention is clear.
This article is part of the GWB Archives. Original Author: James Michael Hare

Related Posts