Class Inheritance
Accessing the Inherited Members
Hiding Members of a Base Class
Base Access
Using References to a Base Class
Constructor Execution
Inheritance Between Assemblies
Member Access Modifiers
Abstract Members
Abstract Classes
Sealed Classes
External Methods
Inheritance allows you to define a new class that incorporates and extends an already declared class.
For example, the following shows the declaration of a class called OtherClass
, which is derived from a class called SomeClass
:
Figure 7-1 shows an instance of each of the classes. Class SomeClass
, on the left, has one field and one method. Class OtherClass
, on the right, is derived from SomeClass
and contains an additional field and an additional method.
Figure 7-1. Base class and derived class
Inherited members are accessed just as if they had been declared in the derived class itself. (Inherited constructors are a bit different—I'll cover them later in the chapter.) For example, the following code declares classes SomeClass
and OtherClass
, which were shown in Figure 7-1. The code shows that all four members of OtherClass
can be seamlessly accessed, regardless of whether they're declared in the base class or the derived class.
Main
creates an object of derived class OtherClass
.Main
call Method1
in the base class, using Field1
from the base class and then Field2
from the derived class.Main
call Method2
in the derived class, again using Field1
from the base class and then Field2
from the derived class. class SomeClass // Base class
{
public string Field1 = "base class field ";
public void Method1( string value ) {
Console.WriteLine("Base class -- Method1: {0}", value);
}
}
class OtherClass: SomeClass // Derived class
{
public string Field2 = "derived class field";
public void Method2( string value ) {
Console.WriteLine("Derived class -- Method2: {0}", value);
}
}
class Program
{
static void Main() {
OtherClass oc = new OtherClass();
oc.Method1( oc.Field1 ); // Base method with base field
oc.Method1( oc.Field2 ); // Base method with derived field
oc.Method2( oc.Field1 ); // Derived method with base field
oc.Method2( oc.Field2 ); // Derived method with derived field
}
}
This code produces the following output:
Base class -- Method1: base class field
Base class -- Method1: derived class field
Derived class -- Method2: base class field
Derived class -- Method2: derived class field
All classes, except special class object
, are derived classes, even if they don't have a class-base specification. Class object
is the only class that is not derived, since it is the base of the inheritance hierarchy.
Classes without a class-base specification are implicitly derived directly from class object
. Leaving off the class-base specification is just shorthand for specifying that object
is the base class. The two forms are semantically equivalent, as shown in Figure 7-2.
Figure 7-2. The class declaration on the left implicitly derives from class object, while the one on the right explicitly derives from object. The two forms are semantically equivalent.
Other important facts about class derivation are the following:
object
.Base class and derived class are relative terms. All classes are derived classes, either from object
or from another class—so generally when we call a class a derived class, we mean that it is immediately derived from some class other than object
. Figure 7-3 shows a simple class hierarchy. After this, I won't show object
in the figures, since all classes are ultimately derived from it.
Figure 7-3. A class hierarchy
Although a derived class cannot delete any of the members it has inherited, it can hide them.
new
modifier. Without it, the program will compile successfully, but the compiler will warn you that you are hiding an inherited member.The following code declares a base class and a derived class, each with a string
member called Field1
. The keyword new
is used to explicitly tell the compiler to mask the base class member. Figure 7-4 illustrates an instance of each class.
Figure 7-4. Hiding a member of a base class
In the following code, OtherClass
derives from SomeClass
but hides both its inherited members. Note the use of the new
modifier. The code is illustrated in Figure 7-5.
This code produces the following output:
OtherClass.Method1: OtherClass Field1
Figure 7-5. Hiding a field and a method of the base class
If your derived class absolutely must access a hidden inherited member, you can access it by using a base access expression. This expression consists of the keyword base
, followed immediately by a period and the name of the member, as shown here:
For example, in the following code, derived class OtherClass
hides Field1
in its base class but accesses it by using a base access expression.
This code produces the following output:
Field1 -- In the derived class
Field1 -- In the base class
If you use this feature frequently, you might want to revaluate the design of your classes. Generally there are more elegant designs, but the feature is there if there's a situation where nothing else will do.
An instance of a derived class consists of an instance of the base class, plus the additional members of the derived class. A reference to the derived class points to the whole class object, including the base class part.
If you have a reference to a derived class object, you can get a reference to just the base class part of the object by casting the reference to the type of the base class by using the cast operator. The cast operator is placed in front of the object reference and consists of a set of parentheses containing the name of the class being cast to. Casting is covered in detail in Chapter 18.
The next few sections cover accessing an object by using a reference to the base class part of the object. We'll start by looking at the two lines of code that follow, which declare references to objects. Figure 7-6 illustrates the code and shows the parts of the object seen by the different variables.
derived
, which then contains a reference to an object of type MyDerivedClass
.MyBaseClass
, and casts the reference in derived
to that type, giving a reference to the base class part of the object.
mybc
, on the left side of the assignment operator. MyDerivedClass derived = new MyDerivedClass(); // Create an object.
MyBaseClass mybc = (MyBaseClass) derived; // Cast the reference.
Figure 7-6. Reference derived can see the entire MyDerivedClass object, while mybc can only see the MyBaseClass part of the object.
The following code shows the declaration and use of these two classes. Figure 7-7 illustrates the object and references in memory.
Main
creates an object of type MyDerivedClass
and stores its reference in variable derived
. Main
also creates a variable of type MyBaseClass
and uses it to store a reference to the base class portion of the object. When the Print
method is called on each reference, the call invokes the implementation of the method that the reference can see, producing different output strings.
This code produces the following output:
This is the derived class.
This is the base class.
Figure 7-7. A reference to the derived class and the base class
In the previous section, you saw that when you access an object of a derived class by using a reference to the base class, you get the members from the base class. Virtual methods allow a reference to the base class to access “up into” the derived class.
You can use a reference to a base class to call a method in the derived class, if the following are true:
virtual
.override
.For example, the following code shows the virtual
and override
modifiers on the methods in the base class and derived class:
Figure 7-8 illustrates this set of virtual
and override
methods. Notice how the behavior differs from the previous case, where I used new
to hide the base class members.
Print
method is called by using the reference to the base class (mybc
), the method call is passed up to the derived class and executed, because
virtual
.override
method in the derived class.virtual
Print
method and pointing at the override
Print
method.Figure 7-8. A virtual method and an override method
The following code is the same as in the previous section, but this time, the methods are labeled virtual
and override
. This produces a result that is very different from that of the previous example. In this version, calling the method through the base class invokes the method in the derived class.
This code produces the following output:
This is the derived class.
This is the derived class.
Other important things to know about the virtual
and override
modifiers are the following:
private
, and the overriding method public
.static
or is nonvirtual.virtual
and override
.Overriding methods can occur between any levels of inheritance.
override
.override
—they are not invoked.For example, the following code shows three classes that form an inheritance hierarchy: MyBaseClass
, MyDerivedClass
, and SecondDerived
. All three classes contain a method named Print
, with the same signature. In MyBaseClass
, Print
is labeled virtual
. In MyDerivedClass
, it's labeled override
. In class SecondDerived
, you can declare method Print
with either override
or new
. Let's look at what happens in each case.
class MyBaseClass // Base class
{
virtual public void Print()
{ Console.WriteLine("This is the base class."); }
}
class MyDerivedClass : MyBaseClass // Derived class
{
override public void Print()
{ Console.WriteLine("This is the derived class."); }
}
class SecondDerived : MyDerivedClass // Most-derived class
{
... // Given in the following pages
}
If you declare the Print
method of SecondDerived
as override
, then it will override both the less-derived versions of the method, as shown in Figure 7-9. If a reference to the base class is used to call Print
, it gets passed all the way up the chain to the implementation in class SecondDerived
.
The following code implements this case. Notice the code in the last two lines of method Main
.
Print
method by using a reference to the most-derived class—SecondDerived
. This is not calling through a reference to the base class portion, so it will call the method implemented in SecondDerived
.Print
method by using a reference to the base class—MyBaseClass
.The result is that regardless of whether Print
is called through the derived class or the base class, the method in the most-derived class is called. When called through the base class, it's passed up the inheritance hierarchy. This code produces the following output:
This is the second derived class.
This is the second derived class.
Figure 7-9. Execution is passed to the top of the chain of multiple levels of override.
If instead you declare the Print
method of SecondDerived
as new
, the result is as shown in Figure 7-10. Main
is the same as in the previous case.
class SecondDerived : MyDerivedClass
{
new public void Print()
{
Console.WriteLine("This is the second derived class.");
}
}
class Program
{
static void Main() // Main
{
SecondDerived derived = new SecondDerived(); // Use SecondDerived.
MyBaseClass mybc = (MyBaseClass)derived; // Use MyBaseClass.
derived.Print();
mybc.Print();
}
}
The result is that when method Print
is called through the reference to SecondDerived
, the method in SecondDerived
is executed, as you would expect. When the method is called through a reference to MyBaseClass
, however, the method call is passed up only one level, to class MyDerived
, where it is executed. The only difference between the two cases is whether the method in SecondDerived
is declared with modifier override
or modifier new
.
This code produces the following output:
This is the second derived class.
This is the derived class.
Figure 7-10. Hiding the overridden methods
In the previous few sections, you've seen how the virtual
/override
designations work on methods. These work exactly the same way with properties, events, and indexers. For example, the following code shows a read-only property named MyProperty
using virtual
/override
.
class MyBaseClass
{
private int _myInt = 5;
virtual public int MyProperty
{
get { return _myInt; }
}
}
class MyDerivedClass : MyBaseClass
{
private int _myInt = 10;
override public int MyProperty
{
get { return _myInt; }
}
}
class Program
{
static void Main()
{
MyDerivedClass derived = new MyDerivedClass();
MyBaseClass mybc = (MyBaseClass)derived;
Console.WriteLine( derived.MyProperty );
Console.WriteLine( mybc.MyProperty );
}
}
This code produces the following output:
10
10
In the preceding chapter, you saw that a constructor executes code that prepares a class for use. This includes initializing both the static and instance members of the class. In this chapter, you saw that part of a derived class object is an object of the base class.
For example, the following code shows a declaration of class MyDerivedClass
and its constructor. When the constructor is called, it calls the parameterless constructor MyBaseClass()
before executing its own body.
class MyDerivedClass : MyBaseClass
{
MyDerivedClass() // Constructor uses base constructor MyBaseClass().
{
...
}
Figure 7-11 shows the order of construction. When an instance is being created, one of the first things that is done is the initialization of all the instance members of the object. After that, the base class constructor is called. Only then is the body of the constructor of the class itself executed.
Figure 7-11. Order of object construction
For example, in the following code, the values of MyField1
and MyField2
would be set to 5
and 0
, respectively, before the base class constructor is called.
class MyDerivedClass : MyBaseClass
{
int MyField1 = 5; // 1. Member initialized
int MyField2; // Member initialized
public MyDerivedClass() // 3. Body of constructor executed
{
...
}
}
class MyBaseClass
{
public MyBaseClass() // 2. Base class constructor called
{
...
}
}
Caution Calling a virtual method in a constructor is strongly discouraged. The virtual method in the base class would call the override method in the derived class while the base class constructor is being executed. But that would be before the derived constructor's body is executed. It would, therefore, be calling up into the derived class before the class is completely initialized.
By default, the parameterless constructor of the base class is called when an object is being constructed. But constructors can be overloaded, so a base class might have more than one. If you want your derived class to use a specific base class constructor other than the parameterless constructor, you must specify it in a constructor initializer.
There are two forms of constructor initializer:
base
and specifies which base class constructor to use.this
and specifies which other constructor from this class should be used.A base class constructor initializer is placed after a colon following the parameter list in a class's constructor declaration. The constructor initializer consists of the keyword base
and the parameter list of the base constructor to call.
For example, the following code shows a constructor for class MyDerivedClass
.
string
and the second parameter is an int
.When you declare a constructor without a constructor initializer, it's a shortcut for the form with a constructor initializer consisting of base()
, as illustrated in Figure 7-12. The two forms are semantically equivalent.
Figure 7-12. Equivalent forms of a constructor
The other form of constructor initializer instructs the construction process (actually, the compiler) to use a different constructor from the same class. For example, the following shows a constructor with a single parameter for class MyClass
. That single-parameter constructor, however, uses a constructor from the same class, but with two parameters, supplying a default parameter as the second one.
Another situation where this comes in particularly handy is where you have several constructors for a class, and they have common code that should always be performed at the beginning of the object construction process. In this case, you can factor out that common code and place it in a constructor that is used as a constructor initializer by all the other constructors. As a matter of fact, this is a suggested practice since it reduces code duplication.
You might think that you could just declare another method that performs those common initializations and have all the constructors call that method. This isn't as good for several reasons. The first is that the compiler can optimize certain things when it knows a method is a constructor. The second is that there are some things that can be done only in a constructor and not elsewhere. For example, in the previous chapter you learned that readonly
fields can be initialized only inside a constructor. You will get a compiler error if you attempt to initialize a readonly
field in any other method, even if that method is called by a constructor only.
Going back to that common constructor, if it can stand on its own as a valid constructor that initializes everything in the class that needs to be initialized, then it's perfectly fine to leave it as a public
constructor.
What if, however, it doesn't completely initialize an object? In that case, you mustn't allow that constructor to be callable from outside the class, since it would then create incompletely initialized objects. To avoid that problem, you can declare the constructor private
instead of public
, as shown in the following code:
class MyClass
{
readonly int firstVar;
readonly double secondVar;
public string UserName;
public int UserIdNumber;
private MyClass( ) // Private constructor performs initializations
{ // common to the other constructors.
firstVar = 20;
secondVar = 30.5;
}
public MyClass( string firstName ) : this() // use constructor initializer
{
UserName = firstName;
UserIdNumber = -1;
}
public MyClass( int idNumber ) : this( ) // use constructor initializer
{
UserName = "Anonymous";
UserIdNumber = idNumber;
}
}
A class can be seen and accessed by other classes in the system. This section explains the accessibility of classes. Although I'll use classes in the explanations and examples since that's what we've covered so far in the text, the accessibility rules also apply to the other types I'll cover later.
The term visible is sometimes used for the term accessible. They can be used interchangeably. There are two levels of class accessibility: public
and internal
.
public
can be accessed by code from any assembly in the system. To make a class visible to other assemblies, use the public
access modifier, as shown here:internal
can only be seen by classes within its own assembly.
public
in the class declaration, code outside the assembly cannot access the class.internal
access modifier.Figure 7-13 illustrates the accessibility of internal
and public
classes from outside the assembly. Class MyClass
is not visible to the classes in the assembly on the left, because it's marked internal
. Class OtherClass
, however, is visible to the classes on the left, because it's marked public
.
Figure 7-13. Classes from other assemblies can access public classes but cannot access internal classes.
So far, I've been declaring derived classes in the same assembly that contains the base class. But C# also allows you to derive a class from a base class defined in a different assembly. To do this, the following must be true:
public
so that it can be accessed from outside its assembly.To make it easier to refer to the classes and types in the other assembly, without using their fully qualified names, place a using
directive at the top of the source file, with the namespace containing the classes or types you want to access.
Note Adding a reference to the other assembly and adding a using
directive are two separate things. Adding the reference to the other assembly tells the compiler where the required types are defined. Adding the using
directive allows you to reference other classes without having to use their fully qualified names. Chapter 10 covers this in detail.
For example, the following two code segments, from different assemblies, show how easy it is to inherit a class from another assembly. The first code listing creates an assembly that contains the declaration of a class called MyBaseClass
, which has the following characteristics:
Assembly1.cs
and inside a namespace declared as BaseClassNS
.public
so that it can be accessed from other assemblies.PrintMe
, that just writes out a simple message identifying the class.The second assembly contains the declaration of a class called DerivedClass
, which inherits from MyBaseClass
, declared in the first assembly. The source file is named Assembly2.cs
. Figure 7-14 illustrates the two assemblies.
DerivedClass
has an empty body but inherits method PrintMe
from MyBaseClass
.Main
creates an object of type DerivedClass
and calls its inherited method PrintMe
.This code produces the following output:
I am MyBaseClass
Figure 7-14. Inheriting across assemblies
The previous two sections explained class accessibility. With class accessibility, there are only two modifiers—internal
and public
. This section covers member accessibility. Class accessibility describes the visibility of a class; member accessibility describes the visibility of the members of a class object.
Each member declared in a class is visible to various parts of the system, depending on the access modifier assigned to it in its class declaration. You've seen that private
members are visible only to other members of the same class, while public
members can be visible to classes outside the assembly as well. In this section, we'll look again at the public
and private
access levels, as well as the three other levels of accessibility.
Before looking at the specifics of member accessibility, there are some general things we need to cover first:
public
private
protected
internal
protected internal
private
.public
.The member access modifiers in a class's declaration specify which other types can and cannot access which members of the class. For example, the following declaration shows members declared with the five access levels.
public class MyClass
{
public int Member1;
private int Member2;
protected int Member3;
internal int Member4;
protected internal int Member5;
...
The access levels are based on two characteristics with regard to the class being declared:
These two characteristics yield four groups, as illustrated in Figure 7-15. In relation to the class being declared, another class can be any of the following:
These characteristics are used to define the five access levels.
Figure 7-15. Areas of accessibility
The public
access level is the least restrictive. All classes both inside and outside the assembly have free access to the member. Figure 7-16 illustrates the accessibility of a public
class member of MyClass
.
To declare a member public, use the public
access modifier, as shown.
Figure 7-16. A public member of a public class is visible to all classes in the same assembly or other assemblies.
The private
access level is the most restrictive.
private
class member can be accessed only by members of its own class. It cannot be accessed by other classes, including classes that are derived from it.private
member can, however, be accessed by members of classes nested in its class. Nested classes are covered in Chapter 25.Figure 7-17 illustrates the accessibility of a private member.
Figure 7-17. A private member of any class is visible only to members of its own class (or nested classes).
The protected
access level is like the private
access level, except that it also allows classes derived from the class to access the member. Figure 7-18 illustrates protected accessibility. Notice that even classes outside the assembly that are derived from the class have access to the member.
Figure 7-18. A protected member of a public class is visible to members of its own class or classes derived from it. The derived classes can even be in other assemblies.
Members marked internal
are visible to all the classes in the assembly but not to classes outside the assembly, as illustrated in Figure 7-19.
Figure 7-19. An internal member of a public class is visible to members of any class in the same assembly but not to classes outside the assembly.
Members marked protected internal
are visible to all the classes that inherit from the class and also to all classes inside the assembly, as shown in Figure 7-20. Notice that the set of classes allowed access is the combined set of classes allowed by the protected
modifier plus the set of classes allowed by the internal
modifier. Notice that this is the union of protected
and internal
—not the intersection.
Figure 7-20. A protected internal member of a public class is visible to members of classes in the same assembly or to members of classes derived from that class. It's not visible to classes in other assemblies that are not derived from the class.
The following two tables summarize the characteristics of the five member access levels. Table 7-1 lists the modifiers and gives an intuitive summary of the effects of the modifier.
Table 7-1. Member Access Modifiers
Modifier | Meaning |
private |
Accessible only within the class |
internal |
Accessible to all classes within this assembly |
protected |
Accessible to all classes derived from this class |
protected internal |
Accessible to all classes that are either derived from this class or declared within this assembly |
public |
Accessible to any class |
Figure 7-21 shows the relative accessibility of the five member access modifiers.
Figure 7-21. Relative accessibility of the various member access modifiers
Table 7-2 lists the access modifiers down the left side of the table and the categories of classes across the top. Derived refers to classes derived from the class declaring the member. Nonderived means classes not derived from the class declaring the member. A check in a cell means that the category of class can access members with the corresponding modifier.
Table 7-2. Summary of Member Accessibility
Classes in Same Assembly | Classes in Different Assembly | |||
Non-Derived | Derived | Non-Derived | Derived | |
private |
||||
internal |
||||
protected |
||||
protected internal |
||||
public |
An abstract member is a function member that is designed to be overridden. An abstract member has the following characteristics:
abstract
modifier.For example, the following code from inside a class definition declares two abstract members: an abstract method called PrintStuff
and an abstract property called MyProperty
. Notice the semicolons in place of the implementation blocks.
Abstract members can be declared only in abstract classes, which we'll look at in the next section. Four type of member can be declared as abstract:
Other important facts about abstract members are the following:
virtual
modifier in addition to the abstract
modifier.override
modifier.Table 7-3 compares and contrasts virtual members and abstract members.
Table 7-3. Comparing Virtual and Abstract Members
Virtual Member | Abstract Member | |
Keyword | virtual |
abstract |
Implementation body | Has an implementation body | No implementation body—semicolon instead |
Overridden in a derived class | Can be overridden—using override |
Must be overridden—using override |
Types of members | Methods Properties Events Indexers |
Methods Properties Events Indexers |
Abstract classes are designed to be inherited from. An abstract class can be used only as the base class of another class.
abstract
modifier. abstract class AbClass // Abstract class
{
...
}
abstract class MyAbClass : AbClass // Abstract class derived from
{ // an abstract class
...
}
override
keyword, unless the derived class is itself abstract.The following code shows an abstract class called AbClass
with two methods.
The first method is a normal method with an implementation that prints out the name of the class. The second method is an abstract method that must be implemented in a derived class. Class DerivedClass
inherits from AbClass
and implements and overrides the abstract method. Main
creates an object of DerivedClass
and calls its two methods.
This code produces the following output:
I am AbClass
I am DerivedClass
The following code shows the declaration of an abstract class that contains data members as well as function members. Data members cannot be declared as abstract
.
abstract class MyBase // Combination of abstract and non-abstract members
{
public int SideLength = 10; // Data member
const int TriangleSideCount = 3; // Data member
abstract public void PrintStuff( string s ); // Abstract method
abstract public int MyInt { get; set; } // Abstract property
public int PerimeterLength( ) // Regular, non-abstract method
{ return TriangleSideCount * SideLength; }
}
class MyClass : MyBase
{
public override void PrintStuff( string s ) // Override abstract method
{ Console.WriteLine( s ); }
private int _myInt;
public override int MyInt // Override abstract property
{
get { return _myInt; }
set { _myInt = value; }
}
}
class Program
{
static void Main( string[] args )
{
MyClass mc = new MyClass( );
mc.PrintStuff( "This is a string." );
mc.MyInt = 28;
Console.WriteLine( mc.MyInt );
Console.WriteLine( "Perimeter Length: {0}", mc.PerimeterLength( ) );
}
}
This code produces the following output:
This is a string.
28
Perimeter Length: 30
In the previous section, you saw that an abstract class must be used as a base class—it cannot be instantiated as a stand-alone class object. The opposite is true of a sealed class.
sealed
modifier.For example, the following class is a sealed class. Any attempt to use it as the base class of another class will produce a compile error.
A static class is a class where all the members are static. Static classes are used to group data and functions that are not affected by instance data. A common use of a static class might be to create a math library containing sets of mathematical methods and values.
The important things to know about static classes are the following:
static
.You access the members of a static class just as you would access any static member, by using the class name and the member name.
The following code shows an example of a static class:
This code produces the following output:
3 is odd is True.
3 * 2 = 6.
So far in this text, every method you've seen has been associated with the class in which it is declared. The extension method feature introduced in C# 3.0 extends that boundary, allowing you to write methods associated with classes other than the class in which they are declared.
To see how you might use this feature, take a look at the following code. It contains class MyData
, which stores three values of type double
, and contains a constructor and a method called Sum
, which returns the sum
of the three stored values.
class MyData
{
private double D1; // Fields
private double D2;
private double D3;
public MyData(double d1, double d2, double d3) // Constructor
{
D1 = d1; D2 = d2; D3 = d3;
}
public double Sum() // Method Sum
{
return D1 + D2 + D3;
}
}
This is a pretty limited class, but suppose it would be more useful if it contained another method, which returned the average of the three data points. With what you know so far about classes, there are several ways you might implement the additional functionality:
If, however, you don't have access to the code or the class is sealed or there is some other design reason that neither of these solutions will work, then you will have to write a method in another class that uses the publicly available members of the class.
For example, you might write a class like the one in the following code. The code contains a static class called ExtendMyData
, which contains a static method called Average
, which implements the additional functionality. Notice that the method takes an instance of MyData
as a parameter.
This code produces the following output:
Average: 4
Although this is a perfectly fine solution, it would be more elegant if you could call the method on the class instance itself, rather than creating an instance of another class to act on it. The following two lines of code illustrate the difference. The first uses the method just shown—invoking a static method on an instance of another class. The second shows the form we would like to use—invoking an instance method on the object itself.
ExtendMyData.Average( md ) // Static invocation form
md.Average(); // Instance invocation form
Extension methods allow you to use the second form, even though the first form would be the normal way of writing the invocation.
By making a small change in the declaration of method Average
, you can use the instance invocation form. The change you need to make is to add the keyword this
before the type name in the parameter declaration as shown following. Adding the this
keyword to the first parameter of the static method of the static class changes it from a regular method of class ExtendMyData
into an extension method of class MyData
. You can now use both invocation forms.
The important requirements for an extension method are the following:
static
.static
.this
, followed by the name of the class it is extending.Figure 7-22 illustrates the structure of an extension method.
Figure 7-22. The structure of an extension method
The following code shows a full program, including class MyData
and extension method Average
declared in class ExtendMyData
. Notice that method Average
is invoked exactly as if it were an instance member of MyData
! Figure 7-22 illustrates the code. Classes MyData
and ExtendMyData
together act like the desired class, with three methods.
This code produces the following output:
Sum: 12
Average: 4
18.119.162.49