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 Asset
s. 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
.
Implicitly upcast to a base class reference
Explicitly downcast to a subclass reference
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.
An upcast operation creates a base class reference from a subclass reference. For example:
Stock msft = new Stock(); // From previous example
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
variable a
is of type Asset
, even though it refers to an object of
type Stock
. To get to its SharesOwned
field, you must
downcast the Asset
to a Stock
.
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 runtime type checking (see Static and Runtime Type Checking).
The as
operator performs a downcast that evaluates to null
(rather than throwing an exception) if
the downcast fails:
Asset a = new Asset(); Stock s = a as Stock; // s is null; no exception thrown
This is useful when you’re going to subsequently test whether
the result is null
:
if (s != null)
Console.WriteLine (s.SharesOwned);
The as
operator cannot
perform custom conversions (see Operator Overloading) and it cannot do numeric
conversions.
The is
operator tests whether a reference conversion 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.Write (((Stock)a).SharesOwned);
The is
operator does not
consider custom or numeric conversions, but it does consider unboxing conversions (see
The object Type).
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:
publicabstract
class Asset { // Note empty implementation publicabstract
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)
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.