only for RuBoard - do not distribute or recompile |
A C# class can inherit from another class to extend or customize that class. A class can inherit only 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 { public static void Main( ) { URL u = new URL("http://microsoft.com"); u.Display( ); u.Navigate( ); } }
|
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("http://microsoft.com"); Location l = u; // upcast u = (URL)l; // downcast
If the downcast fails, an InvalidCastException is thrown.
The as operator allows a downcast that evaluates to null to be made if the downcast fails:
u = l as URL;
The is operator can test if an objectis or derives from a specified class (or implements an interface). It is often used to perform a test before a downcast:
if (l is URL) ((URL)l).Navigate( );
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 Section 2.10 later in this chapter).
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 Location's constructor public LocalFile(string name) : base(name) {} } class Test { static void Main( ) { URL u = new URL("http://www.microsoft.com"); LocalFile l = new LocalFile( "C:\\LOCAL\\README.TXT"); 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 later in this chapter):
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(7)); } ... }
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 abstractmembers, which are function members without implementation that are implicitly virtual. In earlier examples, we specified a Navigate method for the URL type and anExecute 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.
An overridden function member may seal its implementation so it cannot be overridden. In our previous example, we could have made the URL's implementation of Display sealed. This prevents a class that derives from URL from overriding Display, which provides a guarantee on the behavior of a URL.
public sealed override void Display( ) {...}
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, as is the case with the Math class of the FCL. Another effect of sealing a class is that it enables the compiler to seal 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( ) { Console.WriteLine("In B."); } } class D : B { public override void Foo( ) { Console.WriteLine("In D."); } } class N : D { public new void Foo( ) { // hides D's Foo Console.WriteLine("In N."); } } public static void Main( ) { 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 should 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 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:
public class Base { // written by the library people public virtual void Foo( ) {...} // added in latest update } public class Derived : Base { // written by you public void Foo( ) {...} // not intended as an override }
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 Derived's Foo method, even though Derived's Foo is unlikely to have been implemented according to the specification intended by the author of type Base. This can easily break your application. In C#, the flag for Derived's Foo will be false, so the runtime knows to treat Derived'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 something else.
only for RuBoard - do not distribute or recompile |