Classes and Inheritance

 
Chapter 3 - Object-Oriented C#
bySimon Robinsonet al.
Wrox Press 2002
  

We have seen the use of classes in Chapter 2, but to in order to get our bearings we'll briefly recap. Classes are defined in C# using the following syntax:

   class MyClass     {     private int someField;     public string SomeMethod(bool parameter)     {     }     }   

Classes contain members a member is the term used to refer to any data or function that is defined in the class. We use the term function to refer to any member that contains code this includes methods , properties, constructors, and operator overloads.

All C# classes are reference types. This means that when you declare a variable of a class type, all you are getting is a variable (memory location) that can in principle store a reference to an instance of that class. You also need to instantiate an object myObject using the new operator:

   MyClass myObject;     myObject = new MyClass();   

You can, in fact, declare and initialize an instance at the same time:

   MyClass myObject = new MyClass();   

Consider the following line:

   MyClass myObjectRef = myObject;   

Here myObjectRef will also refer to the same MyClass() instance as myObject . Methods against this instance may be called through either of these variables .

Single Implementation Inheritance

C# supports single inheritance of classes. In other words, a class may derive directly from one other class. The syntax for this is as follows .

   class MyDerivedClass : MyBaseClass     {     // functions and data members here     }   

This syntax differs from C++ only to the extent that there is no access modifier describing the inheritance. C# does not support the C++ concepts of public and private inheritance doing so would complicate the language. In practice private inheritance is used extremely rarely in C++ anyway.

To be more exact, we should say that a class must derive from one other class. C# supports the concept of a universal base class, System.Object is the base class.

Method Overloading

C# supports method overloading several versions of the method that have different signatures (name, number of parameters, and parameter types), but does not support default parameters in the way that, say, C++ or VB do. In order to overload methods, you simply declare the methods with the same name but different numbers or types of parameters:

   class ResultDisplayer     {     void DisplayResult(string result)     {     // implementation     }     void DisplayResult(int result)     {     // implementation     }     }   

Because C# does not directly support optional parameters, you will need to use method overloading to achieve the same effect:

   class MyClass     {     int DoSomething(int x)   // want 2nd parameter with default value 10     {     DoSomething(x, 10);     }     int DoSomething(int x, int y)     {     // implementation     }     }   

As in any language, method overloading carries with it the potential for subtle runtime bugs if the wrong overload is called. In the next chapter we will discuss how to code defensively against these problems. For now, we'll point out that C# does place some minimum differences on the parameters of overloaded methods.

  • It is not sufficient for two methods to differ only in their return type

  • It is not sufficient for two methods to differ only by virtue of a parameter having been declared as ref or out

Method Overriding and Hiding

By declaring a base class function as virtual , we allow the function to be overridden in any derived classes:

   class MyBaseClass     {     public virtual string VirtualMethod()     {     return "This method is virtual and defined in MyBaseClass";     }     }   

This means that we can create a different implementation of VirtualMethod() (with the same method signature) in a class derived from MyBaseClass , and when we call this method on an instance of the derived class, the derived class's method is called, not the base class's method. In C#, functions are not virtual by default, but (aside from constructors) may be explicitly declared as virtual. This follows the C++ methodology, in which for performance reasons, functions are not virtual unless explicitly indicated, and stands in contrast to Java, in which all functions are virtual. C# differs from C++ syntax, however, because it requires you to explicitly declare when a derived class's function overrides another function, using the override keyword:

   class MyDerivedClass : MyBaseClass     {     public override string VirtualMethod()     {     return "This method is an override defined in MyDerivedClass";     }     }   

Neither member fields nor static functions can be declared as virtual . It's easy to see why: a virtual member must have a signature and be associated with a particular object and the only members that satisfy both requirements are instance functions.

This approach to method overriding removes potential run-time bugs where a method signature in a derived class unintentionally differs slightly from the base version, resulting in the method failing to override the base version. In C# this will be picked up as a compile-time warning.

If a method with the same signature is declared in both base and derived classes, but the methods are not declared as virtual and override respectively, then the derived class version is said to hide the base class version. The result is that which version of a method gets called depends on the type of the variable used to reference the instance, not the type of the instance itself.

In most cases you would want to override methods rather than hide them, because hiding them gives a strong risk of the "wrong" method being called for a given class instance. However, C# syntax is designed to ensure that the developer is warned at compile time about this potential problem. This also has versioning benefits for developers of class libraries.

Imagine that someone has written a class. Let's call it HisBaseClass :

   class HisBaseClass     {     // various members     }   

At some point in the future you write a derived class of your own, which adds some functionality to HisBaseClass . In particular, you add a method called MyGroovyMethod() , which is not present in the base class:

   class MyDerivedClass: HisBaseClass     {     public int MyGroovyMethod()     {     // some groovy implementation     }     }   

Now one year later, the author of the base class decides to extend its functionality. By coincidence , he adds a method that is also called MyGroovyMethod() , which has the same name and signature as yours, but probably doesn't do the same thing. When you next compile your code using the new version of the base class, you have a potential clash about which method should be called. It's all perfectly legal C#, but since your MyGroovyMethod() is not intended to be related in any way to the base class MyGroovyMethod() the result of running this code probably won't be what you wanted. This sort of thing doesn't happen very often, but it does happen. Fortunately C# has been designed in such a way that it copes very well with the situation.

In the first place, you get warned about the problem. In C#, we should use the new keyword to declare that we intend to hide a method, like this:

 class MyDerivedClass : HisBaseClass {   public new int MyGroovyMethod()   {       // some groovy implementation    } } 

However, your version of MyGroovyMethod() wasn't declared as new , so the compiler will pick up on the fact that it's hiding a base class method, but not declared explicitly to do so and generate a warning (this applies whether or not you declared MyGroovyMethod() as virtual ). If you wish, you can react to the warning by renaming your version of the method. If you can do so, that's probably the best course of action, since it'll save a lot of confusion. However, the great thing is that if you do decide that renaming your method isn't practical (for example, you've published your software as a library for other companies so you can't change any names of methods) and so you leave it, all your existing client code will still run correctly, picking up your version of MyGroovyMethod() . That's because any existing code that accesses this method must be doing so through a reference to MyDerivedClass (or a further derived class).

Any existing code cannot access this method through a reference to HisBaseClass , because that would have generated a compilation error when compiled against the earlier version of HisBaseClass . The problem can only happen in any future client code that is written. C# arranges things so that you get a warning that a potential problem might occur in future code and you will need to pay attention to this warning, and take care not to attempt to call your version of MyGroovyMethod() through any reference to HisBaseClass in any future code you add, but all your existing code will still work fine. It may be a subtle point, but it's quite an impressive example of how C# is able to cope with different versions of classes.

Calling Base Versions of Functions

C# has a special syntax for calling base versions of a method from a derived class. To do this, you write base. < MethodName > () . For example, suppose a method in a derived class should return 90% of the value returned by the base class method:

 class CustomerAccount {    public virtual decimal CalculatePrice()    {       // implementation    } }   class GoldAccount : CustomerAccount     {     public override decimal CalculatePrice()     {     return base.CalculatePrice() * 0.9M;     }     }   

This syntax is similar to that in Java, although Java uses the keyword super rather than base . C++ has no similar keyword but instead requires explicit specification of the class name. Any equivalent to base in C++ would have been ambiguous since C++ allows multiple inheritance.

Note that you can use the base. < MethodName > () syntax to call any method in the base class you don't have to be calling it from an override of the same method.

Abstract Classes and Functions

C# allows both classes and functions to be declared as abstract . An abstract class cannot be instantiated , while an abstract function does not have an implementation, and must be overridden in any non-abstract derived class. Obviously, an abstract function is automatically virtual (though you don't need to supply the virtual keyword as well in fact it's regarded as a syntax error if you do). If any class contains any abstract functions, then that class is also abstract and must be declared as such.

   abstract class Building     {     public abstract decimal CalculateHeatingCost();    // abstract method     }   

C++ developers will notice some syntactical differences in C# here. The =0 syntax has gone. In C#, this syntax would be misleading, since = < value > is allowed on member fields in class declarations to supply initial values:

 abstract class Building {   private bool damaged = false;   // field   public abstract decimal CalculateHeatingCost();   // abstract method } 

C++ developers should also note the slightly different terminology: In C++, abstract functions are often described as pure virtual; in the C# world, the only term to use is abstract.

Sealed Classes and Methods

C# allows classes and methods to be declared as sealed . For the case of a class, this means that you can't inherit from that class. For the case of a method, it means that you can't override that method further.

   sealed class FinalClass     {     // etc     }     class DerivedClass : FinalClass       // wrong. Will give compilation error     {     // etc     }   

Java developers will recognize sealed as the C# equivalent of Java's final .

The most likely situation when you'll mark a class or method as sealed will be if it is very much internal to the operation of the library, class or other classes that you are writing, so you are fairly sure that any attempt to override some of its functionality will cause problems. You might also mark a class or method as sealed for commercial reasons, in order to prevent a third party from extending your classes in a manner that is contrary to the licensing agreements. In general, however, you should be careful about marking a class or member as sealed , since by doing so you are severely restricting how it can be used. Even if you don't think it would be useful to inherit from a class or override a particular member of it, it's still possible that at some point in the future someone will encounter a situation you hadn't anticipated in which it is useful to do so. The .NET base class library makes quite a bit of use of sealed classes where Microsoft does not intend anyone to derive from the class. For example, string is a sealed class.

Declaring a method as sealed serves a similar purpose, although it's likely to be only rarely that you will want to declare a method as sealed.

   class MyClass     {     public sealed override void FinalMethod()     {     // etc.     }     }     class DerivedClass : MyClass     {     public override void FinalMethod()      // wrong. Will give compilation error     {     }     }   

Note that it does not make sense to use the sealed keyword on a method unless that method is itself an override of another method in some base class. If you are defining a new method and you don't want anyone else to override it, then do not declare it as virtual in the first place. If, however, you have overridden a base class method then the sealed keyword provides a way of ensuring that the override you supply to a method is a "final" override in the sense that no-one else can override it again.

Access Modifiers

In common with other object-oriented languages, C# has a number of accessibility modifiers, which determine which other code is allowed to be aware of the existence of a given member of a class. We have already encountered public , private, and protected . C# actually has five such modifiers. The complete list is as follows.

Accessibility

Description

public

The variable or method can be accessed from anywhere as a field of the type to which it belongs

internal

The variable or method can only be accessed from the same assembly

protected

The variable or method can only be accessed from within the type to which it belongs, or from types derived from that type

protected internal

The variable or method can be accessed from the current assembly, or from types derived from the current type (that is, from anywhere that could access it if it were declared as protected or internal )

private

The variable or method can only be accessed from within the type to which it belongs

Of these, internal and protected internal are the ones that are new to C# and the .NET Framework. internal acts in much the same way as public , but access is confined to other code in the same assembly in other words, code that is being compiled at the same time in the same program. You can use internal to ensure all the other classes that you are writing have access to a particular member, but at the same time hiding it from other code written by other organizations. protected internal combines protected and internal , but in an OR sense, not an AND sense. A protected internal member can be seen by any code in the same assembly. But it can also be seen by any derived classes, even those in other assemblies.

  


Professional C#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net