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.

Note

A subclass is also called a derived class. A base class is also called a superclass.

Polymorphism

References are polymorphic, which means a reference to a base class can refer to an instance of a subclass. 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 because they are both Assets:

	Stock msft    = new Stock ... ;
	House mansion = new House ... ;

	Display (msft);
	Display (mansion);

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 modified to accept a House, you could not pass in an Asset.

Casting

An object reference can be:

  • Implicitly upcast to a base class reference

  • Explicitly downcast to a subclass reference

Casting only affects references; the object itself is not converted or altered. An upcast always succeeds; a downcast succeeds only if the object is suitably typed.

Upcasting

An upcast operation creates a base class reference from a subclass reference. For example:

	Stock msft = new Stock ...;
	Asset a = msft;                     // upcast

After the upcast, variable a still references the same Stock object as variable msft. The object being referenced is not itself altered or converted:

	Console.WriteLine (a == msft);        // True

Although a and msft refer to the identical object, a has a more restrictive view on that object:

	Console.WriteLine (a.Name);         // OK
	Console.WriteLine (a.SharesOwned);  // Error

The last line generates a compile-time error because the reference a is of type Asset, even though it refers to an object of type Stock. To get to its SharedOwned field, you must downcast the Asset to a Stock.

Downcasting

A downcast operation creates a subclass reference from a base class reference. For example:

	Stock msft = new Stock( );
	Asset a = msft;                      // upcast
	Stock s = (Stock)a;       // downcast
	Console.WriteLine (s.SharesOwned);   // <No error>
	Console.WriteLine (s == a);          // true
	Console.WriteLine (s == msft);       // true

As with an upcast, only references are affected—not the underlying object. A downcast requires an explicit cast because it can potentially fail at runtime:

	House h = new House( );
	Asset a = h;          // Upcast always succeeds
	Stock s = (Stock)a;   // Downcast fails: a is not a Stock

If a downcast fails, an InvalidCastException is thrown. This is an example of dynamic type checking (we will elaborate on this concept in the upcoming “Static and Dynamic Type Checking” section).

The as operator

The as operator performs a downcast that evaluates to null if the downcast fails (rather than throwing an exception):

	Asset a = new Asset();
	Stock s = a as Stock;      // s is null

The is operator

The is operator tests whether a downcast would succeed; in other words, whether an object derives from a specified class (or implements an interface). It is often used to test before downcasting:

	if (a is Stock)
	  Console.WriteLine (((Stock)a).SharesOwned);

Virtual Function Members

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 Stock : Asset { public long SharesOwned; }
	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 Hous specializes the Liability property to return the value of the Mortgage:

	House mansion = new House { Name="Mansion",
	                            Mortgage=250000 };
	Asset a = mansion;
	decimal d2 = mansion.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 (we will cover this shortly, in the upcoming “The base Keyword” section).

Abstract Classes and Abstract Members

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, which 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; }
	}

	public class Stock : Asset
	{
	  public long SharesOwned;
	  public decimal CurrentPrice;
	  // Override like a virtual method.
	  public override decimal NetValue
	  {
	    get { return CurrentPrice * SharesOwned; }
	  }
	}

Hiding Inherited Members

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 accidentally, 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.

Note

C# overloads the new keyword to have independent meanings in different contexts. Specifically, the new operator is different from the new member modifier.

Sealing Functions and Classes

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 { return Mortgage; } }

You can also seal the class itself, implicitly sealing all the virtual functions, by applying the sealed modifier to the class itself. Sealing a class is more common than sealing a function member.

The base Keyword

The base keyword is similar to the this keyword. It serves two essential purposes:

  • Accessing an overridden function member from the subclass

  • Calling a base class constructor (see the 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; }
	  }
	}

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.)

Constructors and Inheritance

A subclass must declare its own constructors. For example, if we define 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 hence redeclare any constructors it wants to expose. In doing so, however, 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.

Implicit calling of the parameterless base class constructor

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, subclasses are forced to use the base keyword in their constructors.

Constructor and field initialization order

When an object is instantiated, initialization takes place in the following order:

From subclass to base class:

  • Fields are initialized.

  • Constructor arguments are evaluated.

From base class to subclass:

  • Constructor bodies execute.

Overloading and Resolution

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 dynamically (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)
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset