If you want to declare that a class derives from another class, use the following syntax:
class MyDerivedClass : MyBaseClass
II functions and data members here
This syntax is very similar to C++ and Java syntax. However, C++ programmers, who will be used to the concepts of public aPldprivate inheritance, should note that C# does not support private inheritance, hence the absence of a public or private qualifier on the base class name. Supporting private inheritance would have complicated the hl”gllage for very littie gain. In practice, private inheritance is used extremely rarely in C++ anyPJOy.
If a class (or a struct) also derives from interfaces, the list of base class and interfaces is separated by commas:
public class MyDerivedCl’ass : MyBaseClass, IInterfacel, IInterface2.
For a struct, the syntax is as follows:
public struct ‘MyDerivedStruct IInterfacel, IInterface2.
If you do not specify a base class in a class definition, the C# compiler will assume that System. Object is the base class. Hence, the following two pieces of code yield the same result:
For the sake of simplicity, the second form is more common. Because C# supports the object keyword, which serves as a pseudonym for the System. Object class, you can also write:
If you want to reference the Object class, use the object keyword, which is recognized by intelligent. editors such as Visual Studio .NET and thus facilitates editing your code.
By declaring a base dass function as virtual, you allow the function to be overridden in any derived classes:
It is also permitted to declare a property as virtual. For a virtual or overridden property, the syntax is the same as for a non virtual property, with the exception of the keyword virtual, which is added to the definition. The syntax looks like this:
public virtual string ForeName
get return fName;}
set fName = value;}
private string foreName;
For simplicity, the following discussion focuses mainly on methods, but it applies equally well to properties.
The concepts behind virtual functions in C# are identical to standard OOP concepts. You can override a virtual function in a derived class, and when the method is called, the appropriate method for the type of object is invoked. In C#, functions are not virtual by default but (aside from constructors).can be explicitly declared as virtual. This follows the C++ methodology: for performance reasons, functions are not virtual unless indicated. In Java, by contrast, all functions are virtual. C# does differ from C++ syntax, though, because it requires you to 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.’;
This syntax for method overriding removes potential runtime bugs that can easily occur in C++, when a method signature in a derived class unintentionally differs slightly from the base version, resulting in the method failing to override the base version. TnC#, this is picked up as a compile-time error because the compiler would see a function marked as overide but no base method for it to override.
Neither member fields nor static functions car declared. virtuaI’. The concept simply wouldn’t make sense for any class member other than an inst function member.
In most cases, you would want to override methods rather than hide them; by hiding them you risk calling the wrong method for a given class instance. However, as showre in the following example,
C# syntax is designed to ensure that the developer is warned at compile time about this potential problem, thus making it safer to hide methods if that is your intention. This also has versioning benefits for developers of class libraries.
Suppose that you have a class called HisBaseClass:
At some point in the future you write a derived class that adds some functionality to HisBaseClass. In particular, you add a method called MyGroovyMethod ( ) , which is not present in the base class:
One year later, you decide to extend the functionality of the base class. By coincidence, you add a method that is also called MyGroovyMethod ( } and that has the same name and signature as yours, but probably doesn’t do the same thing. When you compile your code using the new version of the base class, you have a potential clash because your program won’t know which method to calL It’s all perfectly legal in C#, but because your MyGroovyMethod () is not intended to be related in any way to the base class MyGroovyMethod ( ) , the result is that rurining this code does not YIeld the result you want. Fortunately, C# has been designed to cope very well with these types of conflicts.
In these situations, C# generates a compilation warning that reminds ou to use the new keyword t d~are thaf you intend to hide a method, like this.
However, because your version of MyGroovyMethod () is not declared as new, the compiler will pick up on the fact that it’s hiding a base class method without being instructed to do so and will generate a warning. (this applies whether or not you declared MyGroovyMethod () as virtual). If you want, you can rename your version of the method. This is the recommended course of action because it will . eliminate future confusion. However, if you decide not to rename your method for whatever reason (for example, if you’ve published your software as a library for other companies, so you can’t change the names of methods), 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 MyDeri vedClass (or a further derived class).
Your existing code cannot access this method through a reference to HisBaseClass; it would generate a compilation error when compiled against the earlier version of HisBaseClass. The problem can happen in only client code you have yet to write. C# arranges things so that you get a warning that a potential problem might occur in future code – 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 His~aseClass in anyfuture code you add. However, 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: For example, if you want a method in a derived class to return 90 percent of the value returned by the base class method, you can use the following syntax:
Java uses a similar syntax, with the exception that Java uses the keyword super rather than base. C++ has no similar keyword but instead requires specification of the class name (CustomerAccount: :
CalculatePrice (»). Any equivalent to base in C++ would have been ambiguous because C++ supports multiple inheritance .
Note that you can use the base. <MethodName>() yntax to call any method in the base class – you don’t have to call it from inside an override of the same method.
Abstract Classes and Functions
C++ developers will notice some syntacn cal differences in C# here. C# does not support the =0 syntax to declare abstract functions. In C#, this syntax would be misleading because it is allowed in member fields in class declarations to supply initial values:
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 correct term to use is abstract.
Sealed Classes and Methods
C# allows classes and methods to be declared.as sealed.. inMrit fan that date. In the aile of a method, this means it you cant overide
Java developers will recognize sealed as the C# equivalent ofjava’s final.
The most likely situation in which you’ll mark a class or method as sealed will be if the class or method is internal to the operation of the library, class, or other classes that you are writing, so that you ensure that any attempt to override some of its functionality will lead to instability in the code. 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 because 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 .NETbase class library fr~quently uses sealed classes in order to make these classes inaccessible to third-party developers who might want to derive their own classes from them. For example, string is a sealed class. Declaring a method as sealed serves a similar purpose as for a class:
In order to use the sealed keyword on a method or property, it must have first been overridden from a base class. H you do not want a method or property in a base class overridden, then don’t mark it as
Constructors of Derived Classes
An interesting question arises as to what happens when yuu start defining your own constructors for classes that are part of a hierarchy, Inherited from other classes that may also have custom constructors.
ssume that you have not defined any explicit constructors for any of your classes. This means that the ompiler supplies default zeroing-out constructors for all your classes. There is actually quite a lot going on under the hood when that happens, but the compiler is able to arrange it so that things work out nicely throughout the class hierarchy and every field in every class gets initialized to whatever its default value is. When you add a constructor of your own, however, you are effectively taking control of construction. This has implications right down through the hierarchy of derived classes, and you have to make sure that you don’t inadvertently do anything to prevent construction through the hierarchy from taking place smoothly.
You might be wondering why there is any special problem with derived classes. The reason is that when you create an instance of a derived class, there is actually more than one constructor at work. The constructor of the class you instantiate isn’t by itself sufficient to initialize the class – the constructors of the base classes must also be called. That’s why we’ve been talking about construction through the hierarchy.
To see why base class constructors must be called, you’re going to develop an example based on a cell phone company called MortimerPhones. The example contains an abstract base class, GenericCustomer, which represents any customer. There is also a (non-abstract) class, Nevermore60Customer, that represents any customer on a particular rate called the Nevermore60 rate. All customers have a name, represented by a private field. Under the Nevermore60 rate, the first few minutes of the customer’s call time are charged at a higher rate, necessitating the need for the field highCostMinutesUsed, ~hich details how many of these higher-cost minutes each customer has used up. The class definitions look like this:
We won’t worry about what other methods might be implemented in these classes, because we are concentrating solely on the construction process here. And if you download the sample code for this chapter, you’ll find that the class definitions include only the constructors.
lake a look at what happens when you use the new operator to instantiate a Nevermore60Customer: GenericCustomer customer = new Nevermore60Customer ()
Clearly, both of the member fields name and highCostMinutesUsed must be initialized when customer is instantiated. If you don’t supply constructors of your own, but rely simply on the default constructors, then you’d expect name to be initialized to the null reference, and highCostMinutesUsed initialized to zero. Let’s look in a bit more detail at how this actually happens.
The highCostMinutesUsed field presents no problem: the default Nevermore60Customer constructor supplied by the compiler will initialize this field to zero
What about name? Looking at the class definitions, it’s clear that the Nevermore60Customer constructor can’t initialize this value. This field is declared as private, which means that derived classes don’t have access to it. So, the default Nevermore60Customer constructor simply won’t know that this field exists. The only code items that have that knowledge are other members of GenericCustomer. This means that if name is going to be initialized, that’ll have to be done by some constructor in GenericCustomer. No matter how big your class hierarchy is~this same reasoning applies right down to the ultimate base class, Sys tem. Obj ect.
Now that you have an understanding of the issues involved, you can look ‘It what actually happens whenever a derived class is instantiated. Assuming that default constructors are used throughout, thecompiler first grabs the constructor of the class it is trying to instantiate, in this case Nevermore60Customer. The first thing that the default Nevermore60Customer constructor does is attempt to run the default constructor for the immediate base class, GenericCustomer. The GenericCustomer constructor attempts to run the constructor for its immediate base class, System. Object. System. Object doesn’t have any base classes, so its constructor just executes and returns control to the GenericCustomer constructor. That constructor now executes, initializing name to null, before returning control to the N~vermore60Ct:stomer constructor. That constructor in turn executes, initializing highCostMinutesUsed to zero, and exits. At this point, the Nevermore60Customer instance has been successfully constructed and initialized.
The net result of all this is that the constructors are called in order of Sys tern. Obj ec t first, then progressing down the hierarchy until the compiler reaches the class being instantiated. Notice also that in this process, each constructor handles initialization of the fields in its own class. That’s how it should normally work, and when you start adding your own constructors you should try to stick to that principle.
Notice the order in which this happens. It’s always the base class constructors that get called first. This means that there are no problems with a constructor for a derived class invoking any base class methods, properties, and any other members that it has access to, because it can be confident that the base class has already been constructed and its fields initialized. It also means that if the derived class doesn’t like the way that the base class has been initialized, it can change the initial values of the data, provided that it has access to do so. However, good programming practice almost invariably means you’ll try to prevent that situation from occurring if you can, and you will trust the base class constructor to deal with its own fields.
Now that you know how the process of construction works, you can start fiddling with it by adding your own constructors.
Adding A constructor in Hierarchy
Adding this code will work fine. Nevermore60Customer still has its default constructor, so the sequence of events described earlier will proceed as before, except that the compiler will use the custom GenericCustomer constructor instead of generating a default one, so the name field will always be initialized to • <no name>’ as required.
Notice that in your constructor you’ve added a call to the base class constructor before the GenericCustomer constructor is executed, using a syntax similar to that used earlier when we discussed how to get different overloads of constructors to call each other. The only difference is that this time you use the base keyword instead of this to indicate that it’s a constructor to the base class rather than a constructor to the current class you want to call. There are no parameters in the brackets after the base keyword – that’s impo~t because it means you are not passing any parameters to the base constructor, so the compiler will have to look for a parameterless constructor to call. The result of all this is that the compiler will inject code to call the System. Obj ect constructor, just as would happen by default anyway. In fact, you can omit that line of code and write the following.
If the compiler doesn’t see any reference to another constructor before the opening curly brace, it assumes that you intended to call the base class constructor; this fits in with the way that default constructors work.
The base and this keywords are the only keywords allowed in the line that calls another constructor. Anything else causes a compilation error. Also note that only one other constructor can be specified.
So far, this code works fine. One way to mess up the progression through the hierarchy of constructors, however, is to declare a constructor as private:
If you try this, you’ll find you get an interesting compilation error, which could really throw you if you don’t understand how construction down a hierarchy works:
Wrox. ProCSharp. GenericCustomer ()’ is inaccessible due to its protection level
The interesting thing is that the error occurs not in the Gener,icCustomer class, but in the derived class, Nevermore60Customer. What’s happened is that the compiler has tried to generate a default constructor for Nevermore60Customer but has not been able to because the default constructor is supposed to . invoke the no-parameter Gener i cCus tomer constructor. By declaring that constructor as pri va te, you’ve made it inaccessible to the derived class. A similar error occurs if you supply a constructor to GenericCustomer, which takes parameters, but at the same time you fail to supply a no-parameter
constructor. In this case, the compiler will not generate a default constructor for GenericCustomer, so when it tries to generate the default constructors for any derived class, it will again find that it can’t because a no-parameter base class constructor is not available. A workaround would be to add your own constructors to the derived classes, even if you don’t actually need to do anything in these constructors, so that the compiler doesn’t try to generate any default constructors for them.
Now that you have all the theoretical background you need, you’re ready to move on to an example of Now you can neatly add constructors to a hierarchy of classes. In the next section, you start adding constructors that take parameters to the MortimerPhones example.
Adding Constructors with Parameters to a Hierarchy
You’re going to start with a one-parameter constructor for GenericCustomer, which specifies that customers can be instantiated only when they supply their names:
abstract class GenericCustomer
private string name;
public GenericCustomer(string name)
this.name = name;
So far, so good. However, as mentioned previously, this will cause a compilation error when the compiler tries to create a default constructor for any derived classes because the default compiler-generated constructors for Nevermore60Customer will try to call a no-parameter GenericCustomer constructor, and GenericCustomer does not possess such a constructor. Therefore, you’ll need to supply your own constructors to the derived classes to avoid a compilation error:
Now instantiation of Nevermore6 OCustomer objects can occur only when a string containing the customer’s name is supplied, which is what you want anyway. The interesting thing is what the Nevermore60Customer constructor does with this string. Remember that it can’t initialize the name field itself because it has no access to private fields in its base class. Instead, it passes the name through to the base class for the GenericCustomer constructor to handle. It does this by specifying that the base class constructor to be executed first is the one that takes the name as a parameter. Other than that, it doesn’t take any action of its own.
Next, you’re going to investigate what happens if you have different overloads of the constructor as well as a class hierarchy to deal with. To this end, assume that Nevermore60 customers may have been referred to MortimerPhones by a friend as part of one of those sign-up-a-friend-and-get-a-discount offers. This means that when you construct a Nevermor-=60Customer, you may need to pass in the referrer’s name as well. In real life, the constructor would have to do something complicated with the name, such as process the discount, but here you’ll just store the referrer’s name in another field.
The Nevermore60Customer definition will now look like this:’
class Nevermore60Customer : GenericCustomer public Nevermore60Customer(string name, string referrerName)
: base (name)
Tile constructor takes the name and passes it to the GenericCustomer constructor for procesning. referrerName is the variable that is your responsibility here, so the constructor deals with that parameter in its main body.
However, not all Nevermore60Customers will have a referrer, so you still need a constructor that doesn’t require this parameter (or a constructor that gives you a default value for it). In fact, you will specify that if there is no referrer, then the referrerName field should be set to “<None>”, using the following one-parameter constructor:
You now have all your constructors set up correctly. It’s instructive to examine the chain of events that now occurs when you execute a line like this:
GenericCustomer customer = new Nevermore60Customer(“Arabel Jones”);
The compiler sees that it needs a one-parameter constructor that takes one string, so the constructor it will identify is the last one that you’ve defined:
When you instantiate customer, this constructor will be called. It immediately transfers control to the ‘corresponding Nevermore60Customer two-parameter constructor, passing it the values’ ArabelJones”, and “<None>’. Looking at the code for this constructor, you see that it in turn immediately passes control to the one-parameter GenericCustomer constructor, giving it the string’ ArabelJones’, and in turn that constructor passes control to the System. Object default constructor. Only now do the constructors execute. First, the Sys tern. Obj ect constructor executes. Next comes the GenericCus tomer constructor, which initializes the name field. Then the Nevermore60Customer two-parameter constructor gets control back, and sorts out initializing the ref errerName to •<None>’ . Finally, the Nevermore6 OCustomer oneparameter constructor gets to execute; this constructor doesn’t do anything else.
As you can see, this is a very neat and well-designed process. Each constructor handles ulitialization of the variables that are obviously its responsibility, and, in the process, your class is correctly instantiated and prepared for use. If you follow the same principles when you write your own constructors for your classes, you should find that even the most complex classes get initialized smoothly and without any problems.