A C#
class can inherit from another class to extend
or customize that class. A class can only inherit from a single class
but can be inherited by many classes, thus forming a class hierarchy.
At the root of any class hierarchy is the
object
class,
which all objects implicitly inherit from. Inheriting from a class
requires specifying the class to inherit from in the class
declaration, using the C++ colon notation:
class Location { // Implicitly inherits from object string name; // The constructor that initializes Location public Location(string name) { this.name = name; } public string Name {get {return name;}} public void Display( ) { Console.WriteLine(Name); } } class URL : Location { // Inherit from Location public void Navigate( ) { Console.WriteLine("Navigating to "+Name); } // The constructor for URL, which calls Location's constructor public URL(string name) : base(name) {} }
URL
has all the members of
Location
, and a new member,
Navigate
:
class Test { static void Main( ) { URL u = new URL("http://microsoft.com"); u.Display( ); u.Navigate( ); } }
The specialized class and general class are referred to as either the derived class and base class or the subclass and superclass .
A class D may be implicitly upcast to the class B it derives from, and a class B may be explicitly downcast to a class D that derives from it. For instance:
URL u = new URL( ); Location l = u; // upcast u = (URL)l; // downcast
Polymorphism is the ability to perform the same operation on many types, as long as each type shares a common subset of characteristics. C# custom types exhibit polymorphism by inheriting classes and implementing interfaces (see the Section 2.10).
In the following example, the Show
method can
perform the operation Display
on both a URL
and
a LocalFile
, because both types inherit the
characteristics of Location
:
class LocalFile : Location { public void Execute( ) { Console.WriteLine("Executing "+Name); } // The constructor for LocalFile, which calls URL's constructor public LocalFile(string name) : base(name) {} } class Test { static void Main( ) { URL u = new URL( ); LocalFile l = new LocalFile( ); Show(u); Show(l); } public static void Show(Location loc) { Console.Write("Location is: "); loc.Display( ); } }
A key aspect of polymorphism is that each
type can implement a shared characteristic in its own way. One way to
permit such flexibility is for a base class to declare function
members as virtual
. Derived classes can provide
their own implementations for any function members marked
virtual
in the base class (see Section 2.10):
class Location { public virtual void Display( ) { Console.WriteLine(Name); } ... } class URL : Location { // chop off the http:// at the start public override void Display( ) { Console.WriteLine(Name.Substring(6)); } ... }
URL
now has a custom way of displaying itself. The
Show
method of the Test
class
in the previous section will now call the new implementation of
Display
. The signatures of the overridden method
and the virtual method must be identical, but unlike Java and C++,
the override
keyword is also required.
A
class can be declared abstract. An
abstract
class may have
abstract
members, which are
function members without implementation that are implicitly virtual.
In earlier examples, we specified a Navigate
method for the URL
type and an
Execute
method for
the LocalFile
type. You can, instead, declare
Location
an abstract
class with
an abstract
method called
Launch
:
abstract class Location { public abstract void Launch( ); } class URL : Location { public override void Launch( ) { Console.WriteLine("Run Internet Explorer..."); } } class LocalFile : Location { public override void Launch( ) { Console.WriteLine("Run Win32 Program..."); } }
A derived class must override all its inherited
abstract
members or must itself be declared
abstract
. An abstract
class
can’t be instantiated. For instance, if
LocalFile
doesn’t override
Launch
, LocalFile
itself must
be declared abstract
, perhaps to allow
Shortcut
and PhysicalFile
to
derive from it.
A class can
prevent other classes from inheriting from it by specifying the
sealed
modifier in the class declaration:
sealed class Math { ... }
The most common scenario for sealing a class is when that class
comprises only static members, such as is the case with the
Math
class of the base class library. Another
effect of sealing a class is that it enables the compiler to turn all
virtual method invocations made on that class into faster nonvirtual
method invocations.
Aside from its use for calling a constructor, the
new
keyword can also hide the data members,
function members, and type members of a base class. Overriding a
virtual method with the new
keyword hides, rather
than overrides, the base class implementation of the method:
class B { public virtual void Foo( ) {} } class D : B { public override void Foo( ) {} } class N : D { public new void Foo( ) {} // hides D's Foo } N n = new N( ); n.Foo( ); // calls N's Foo ((D)n).Foo( ); // calls D's Foo ((B)n).Foo( ); // calls D's Foo
A method declaration with the same signature as its base class must explicitly state whether it overrides or hides the inherited member.
In
C#, a method is compiled with a flag
that is true
if the method overrides a virtual
method. This flag
is important for versioning. Suppose that you write a class that
derives from a base class in the .NET Framework and then deploy your
application to a client computer. The client later upgrades the .NET
Framework, and the .NET base class now contains a
virtual
method that happens to match the signature
of one of your methods in the derived class:
class B { // written by the library people virtual void Foo( ) {...} // added in latest update } class D : B { // written by you void Foo( ) {...} }
In most object-oriented languages, such as Java, methods are not
compiled with this flag, so a derived class’s method with the
same signature is assumed to override the base
class’s virtual
method. This means a
virtual
call is made to type
D
’s Foo
method, even
though D
’s Foo
is
unlikely to have been implemented according to the specification
intended by the author of type B
. This can easily
break your application. In C#, the flag for
D
’s Foo
will be
false
, so the runtime knows to treat
D
’s Foo
as
new
, which ensures that your application will
function as it was originally intended. When you get the chance to
recompile with the latest framework, you can add the
new
modifier to Foo
, or perhaps
rename Foo
to something else.
18.226.96.155