Inheritance

A class can inherit from another class to extend or customize the original class. Inheriting from a class lets you reuse the functionality in that class instead of building it from scratch. A class can inherit from only a single class, but can itself be inherited by many classes, thus forming a class hierarchy. In this example, we start by defining a class called Asset:

public class Asset { public string Name; }

Next, we define classes called Stock and House, which will inherit from Asset. Stock and House get everything an Asset has, plus any additional members that they define:

public class Stock : Asset   // inherits from Asset
{
  public long SharesOwned;
}

public class House : Asset   // inherits from Asset
{
  public decimal Mortgage;
}

Here’s how we can use these classes:

Stock msft = new Stock { Name="MSFT",
                         SharesOwned=1000 };

Console.WriteLine (msft.Name);         // MSFT
Console.WriteLine (msft.SharesOwned);  // 1000

House mansion = new House { Name="Mansion",
                            Mortgage=250000 };

Console.WriteLine (mansion.Name);      // Mansion
Console.WriteLine (mansion.Mortgage);  // 250000

The subclasses, Stock and House, inherit the Name property from the base class, Asset.

Subclasses are also called derived classes.

References are polymorphic. This means a variable of type x can refer to an object that subclasses x. For instance, consider the following method:

public static void Display (Asset asset)
{
  System.Console.WriteLine (asset.Name);
}

This method can display both a Stock and a House, since they are both Assets. Polymorphism works on the basis that subclasses (Stock and House) have all the features of their base class (Asset). The converse, however, is not true. If Display was rewritten to accept a House, you could not pass in an Asset.

An object reference can be:

Upcasting and downcasting between compatible reference types performs reference conversions: a new reference is created that points to the same object. An upcast always succeeds; a downcast succeeds only if the object is suitably typed.

A function marked as virtual can be overridden by subclasses wanting to provide a specialized implementation. Methods, properties, indexers, and events can all be declared virtual:

public class Asset
{
  public string Name;
  public virtual decimal Liability { get { return 0; } }
}

A subclass overrides a virtual method by applying the override modifier:

public class House : Asset
{
  public decimal Mortgage;

  public override decimal Liability
    { get { return Mortgage; } }
}

By default, the Liability of an Asset is 0. A Stock does not need to specialize this behavior. However, the House specializes the Liability property to return the value of the Mortgage:

House mansion = new House { Name="Mansion",
                            Mortgage=250000 };
Asset a = mansion;
Console.WriteLine (mansion.Liability);  // 250000
Console.WriteLine (a.Liability);        // 250000

The signatures, return types, and accessibility of the virtual and overridden methods must be identical. An overridden method can call its base class implementation via the base keyword (see The base Keyword).

A class declared as abstract can never be instantiated. Instead, only its concrete subclasses can be instantiated.

Abstract classes are able to define abstract members. Abstract members are like virtual members, except they don’t provide a default implementation. That implementation must be provided by the subclass, unless that subclass is also declared abstract:

public abstract class Asset
{
  // Note empty implementation
  public abstract decimal NetValue { get; }
}

Subclasses override abstract members just as though they were virtual.

A base class and a subclass may define identical members. For example:

public class A      { public int Counter = 1; }
public class B : A  { public int Counter = 2; }

The Counter field in class B is said to hide the Counter field in class A. Usually, this happens by accident, when a member is added to the base type after an identical member was added to the subtype. For this reason, the compiler generates a warning, and then resolves the ambiguity as follows:

  • References to A (at compile time) bind to A.Counter.

  • References to B (at compile time) bind to B.Counter.

Occasionally, you want to hide a member deliberately, in which case you can apply the new modifier to the member in the subclass. The new modifier does nothing more than suppress the compiler warning that would otherwise result:

public class A     { public     int Counter = 1; }
public class B : A { public new int Counter = 2; }

The new modifier communicates your intent to the compiler—and other programmers—that the duplicate member is not an accident.

An overridden function member may seal its implementation with the sealed keyword to prevent it from being overridden by further subclasses. In our earlier virtual function member example, we could have sealed House’s implementation of Liability, preventing a class that derives from House from overriding Liability, as follows:

public sealed override decimal Liability { get { ... } }

You can also seal the class itself, implicitly sealing all the virtual functions, by applying the sealed modifier to the class itself.

The base keyword is similar to the this keyword. It serves two essential purposes: accessing an overridden function member from the subclass, and calling a base class constructor (see next section).

In this example, House uses the base keyword to access Asset’s implementation of Liability:

public class House : Asset
{
  ...  public override decimal Liability
  {
    get { return base.Liability + Mortgage; }
  }
}

With the base keyword, we access Asset’s Liability property nonvirtually. This means we will always access Asset’s version of this property—regardless of the instance’s actual runtime type.

The same approach works if Liability is hidden rather than overridden. (You can also access hidden members by casting to the base class before invoking the function.)

A subclass must declare its own constructors. For example, if we define Baseclass and Subclass as follows:

public class Baseclass
{
  public int X;
  public Baseclass () { }
  public Baseclass (int x) { this.X = x; }
}
public class Subclass : Baseclass { }

the following is illegal:

Subclass s = new Subclass (123);

Subclass must “redefine” any constructors it wants to expose. In doing so, it can call any of the base class’s constructors with the base keyword:

public class Subclass : Baseclass
{
  public Subclass (int x) : base (x) { ... }
}

The base keyword works rather like the this keyword, except that it calls a constructor in the base class. Base class constructors always execute first; this ensures that base initialization occurs before specialized initialization.

If a constructor in a subclass omits the base keyword, the base type’s parameterless constructor is implicitly called (if the base class has no parameterless constructor, the compiler generates an error).

Inheritance has an interesting impact on method overloading. Consider the following two overloads:

static void Foo (Asset a) { }
static void Foo (House h) { }

When an overload is called, the most specific type has precedence:

House h = new House (...);
Foo(h);                      // Calls Foo(House)

The particular overload to call is determined statically (at compile time) rather than at runtime. The following code calls Foo(Asset), even though the runtime type of a is House:

Asset a = new House (...);
Foo(a);                      // Calls Foo(Asset)

Note

If you cast Asset to dynamic (see Dynamic Binding (C# 4.0)), the decision as to which overload to call is deferred until runtime and is based on the object’s actual type.