Inheritance allows you to define a new class that incorporates and extends an already declared class.
You can use an existing class, called the base class, as the basis for a new class, called the derived class. The members of the derived class consist of the following:
The members in its own declaration
The members of the base class
To declare a derived class, you add a class-base specification after the class name. The class-base specification consists of a colon, followed by the name of the class to be used as the base class. The derived class is said to directly inherit from the base class listed.
A derived class is said to extend its base class, because it includes the members of the base class plus any additional functionality provided in its own declaration.
A derived class cannot delete any of the members it has inherited.
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.
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 are declared in the base class or the derived class.
Main
creates an object of derived class OtherClass
.
The next two lines in Main
call Method1
in the base class, using Field1
from the base class, and then Field2
from the derived class.
The subsequent two lines in 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 one 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.
Figure 7-2 shows both forms of declaration for the same class.
Other important facts about class derivation are the following:
A class declaration can have only a single class listed in its class-base specification. This is called single inheritance.
Although a class can directly inherit from only a single base class, there is no limit to the level of derivation. That is, the class listed as the base class might be derived from another class, which is derived from another class, and so forth, until you eventually reach 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 will not show object
in the figures, since all classes are ultimately derived from it.
Although a derived class cannot delete any of the members it has inherited, it can hide them.
To hide an inherited data member, declare a new member of the same type and with the same name.
You can hide, or mask, an inherited function member by declaring in the derived class a new function member with the same signature. Remember that the signature consists of the name and parameter list, but does not include the return type.
To let the compiler know that you are purposely hiding an inherited member, use the new
modifier. Without it, the program will compile successfully, but the compiler will warn you that you are hiding an inherited member.
You can also hide static members.
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.
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.
Sometimes, your derived class might need to access a hidden inherited member. You can access a hidden base class member 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
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 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 will 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.
The first line declares and initializes variable derived
, which then contains a reference to an object of type MyDerivedClass
.
The second line declares a variable of the base class type, MyBaseClass
, and casts the reference in derived
to that type, giving a reference to the base class part of the object.
The reference to the base class part is stored in variable mybc
, on the left side of the assignment operator.
The reference to the base class part cannot "see" the rest of the derived class object, because it is "looking" at it through a reference to the base type.
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 that reference can see, producing different output strings.
This code produces the following output:
This is the derived class. This is 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:
The method in the derived class and the method in the base class each have the same signature and return type.
The method in the base class is labeled virtual
.
The method in the derived class is labeled 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.
When the 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
The method in the base class is marked as virtual
.
There is a matching override
method in the derived class.
Figure 7-8 illustrates this by showing the arrow coming out the back of the virtual Print
method and pointing at the override Print
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 information about the virtual
and override
modifiers is the following:
The overriding and overridden methods must have the same accessibility. In other words, the overridden method cannot be, for example, private
, and the overriding method public
.
You cannot override a method that is static
or is non-virtual.
Methods, properties, and indexers (which I covered in the preceding chapter), and another member type, called events (which I will cover later in the text), can all be declared virtual
and override
.
Overriding methods can occur between any levels of inheritance.
When you use a reference to the base class part of an object to call an overridden method, the method call is passed up the derivation hierarchy for execution, to the most-derived version of the method marked as override
.
If there are other declarations of the method at higher levels of derivation, which are not marked as 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 is 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
.
The first of the two statements calls the 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
.
The second statement, however, calls the 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 is passed up the inheritance hierarchy. This code produces the following output:
This is the second derived class. This is the second derived class.
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.
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.
To create the base class part of an object, a constructor for the base class is called as part of the process of creating the instance.
Each class in the inheritance hierarchy chain executes its base class constructor before it executes its own constructor body.
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(). { ... }
The order of construction is shown in Figure 7-11. 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.
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:
The first form uses the keyword base
and specifies which base class constructor to use.
The second form uses the keyword 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
.
The constructor initializer specifies that the base class constructor to use is the one that has two parameters; the first parameter is a string
, and the second parameter is an int
.
The parameters in the base parameter list must match the intended base constructor's parameter list, in type and order.
When you declare a constructor without a constructor initializer, it is a shortcut for the form with a constructor initializer consisting of base()
, as illustrated in Figure 7-12. The two forms are semantically equivalent.
Another form of constructor initializer instructs the constructor to use a different constructor in the same class. For example, the following shows a constructor for class MyClass
, which uses the constructor from the same class, but with two parameters, supplying a default parameter as the second one.
A class can be seen and accessed by other classes in the system. This section covers the accessibility of classes. Although I will use classes in the explanations and examples since that is what we've covered so far in the text, the accessibility rules also apply to the other types I will 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
.
A class marked 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:
A class marked internal
can only be seen by classes within its own assembly.
This is the default accessibility level, so unless you explicitly specify the modifier public
in the class declaration, code outside the assembly cannot access the class.
You can explicitly declare a class as internal by using the 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 is marked internal
. Class OtherClass
, however, is visible to the classes on the left, because it is marked public
.
So far, I have been declaring derived classes in the same assembly where the base class is declared. 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:
The base class must be declared public
, so that it can be accessed from outside its assembly.
You must include a reference in your Visual Studio project to the assembly containing the base class.
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.
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:
It is declared in a source file called Assembly1.cs
, and inside a namespace declared as BaseClassNS
.
It is declared public
, so that it can be accessed from other assemblies.
It contains a single member, a method called 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
Class accessibility was covered earlier in the chapter. 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 class members.
Each member declared in a class is visible to various parts of the system, depending on the access modifier assigned to it in the 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 will 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:
All members explicitly declared in a class's declaration are visible to each other, regardless of their accessibility specification.
Inherited members are not explicitly declared in a class's declaration, so, as you shall see, inherited members might or might not be visible to members of a derived class.
There are five member access levels:
public
private
protected
internal
protected internal
You must specify member access levels on a per-member basis. If you don't specify an access level for a member, its implicit access level is private
.
A member cannot be more accessible than its class. That is, if a class has an accessibility level limiting it to the assembly, individual members of the class cannot be seen outside the assembly, regardless of their access modifiers.
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:
Whether the class is derived from the class being declared
Whether a class is in the same assembly as 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:
In the same assembly and derived from it (bottom right)
In the same assembly but not derived from it (bottom left)
In a different assembly and derived from it (top right)
In a different assembly and not derived from it (top left)
These characteristics are used to define the five access levels.
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.
The private
access level is the most restrictive.
A 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.
A 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.
The protected
access level is like the private
access level, except that it 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.
Members marked internal
are visible to all the classes in the assembly, but not to classes outside the assembly, as shown in Figure 7-19.
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.
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 |
---|---|
| Accessible only within the class |
| Accessible to all classes within this assembly |
| Accessible to all classes derived from this class |
| Accessible to all classes that are either derived from this class or declared within this assembly |
| Accessible to any class |
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. Non-derived means classes not derived from the class declaring the member. A check in a cell means that that category of class can access members with the corresponding modifier.
An abstract member is a function member that is designed to be overridden. An abstract member has the following characteristics:
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.
Other important facts about abstract members are the following:
Abstract methods, although they must be overridden by a corresponding method in a derived class, cannot use the virtual
modifier in addition to the abstract
modifier.
As with virtual methods, the implementation of an abstract method in a derived class must specify the override
modifier.
Abstract members can be declared only in abstract classes, which we will look at in the next section.
Table 7-3 compares and contrasts virtual members and abstract members.
Table 7-3. Comparing Virtual and Abstract Members
Virtual Member | Abstract Member | |
---|---|---|
Keyword |
|
|
Implementation body | Has an implementation body | No implementation body—semicolon instead |
Overridden in a derived class | Can be overridden—using | Must be overridden—using |
Types of members | Methods | Methods |
Properties | Properties | |
Events | Events | |
Indexers | Indexers |
An abstract class can be used only as the base class of another class. Abstract classes are designed to be inherited from.
You cannot create instances of an abstract class.
An abstract classis declared using the abstract
modifier.
An abstract class can contain abstract members, but that is not a requirement. The members of an abstract class can be any combination of abstract members and normal members with implementations.
An abstract class can itself be derived from another abstract class. For example, the following code shows one abstract class derived from another.
abstract class AbClass // Abstract class { ... } abstract class MyAbClass : AbClass // Abstract class derived from { // an abstract class ... }
Any class derived from an abstract class must implement all the abstract members of the class by using the 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
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. The opposite is true of a sealed class.
A sealed class can be used only as a stand-alone class—it cannot be used as a base class.
A sealed class is labeled with the 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.
The important things to know about static classes are the following:
The class itself must be marked static
.
All the members of the class must be static.
The class can have a static constructor, but not an instance constructor. You cannot create an instance of the class.
You cannot inherit from static classes—they're sealed.
You access the members 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 of 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 you have the source code and can modify the class, you could, of course, just add the new method to the class.
If, however, you can't modify the class—for example, if the class is in a third-party class library—then, as long as it isn't sealed, you could use it as a base class and implement the additional method in a class derived from it.
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. It 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. Extension methods allow you to use the second form, even though the first form would be the normal way of writing the invocation.
ExtendMyData.Average( md ) // Static invocation form md.Average(); // Instance invocation form
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:
The extension method must be declared static
.
The class in which the extension method is declared must also be declared static
.
The extension method must contain as its first parameter type the keyword this
, followed by the name of the class it is extending.
Figure 7-21 illustrates 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-21 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
An external method is a method that does not have an implementation in the declaration. Often the implementation is in a language other than C#.
External methods are marked with the extern
modifier and do not have an implementation in the class declaration. The implementation is replaced by a semicolon.
Connecting the declaration with the implementation is implementation-dependent, but is often done using the DllImport
attribute. Attributes are covered in detail in Chapter 24.
For example, the following code uses an external method, GetCurrentDirectory
, whose implementation is the Win32 system call for getting a string that contains the current directory.
using System; using System.Text; using System.Runtime.InteropServices; namespace ExternalMethod { class MyClass { [DllImport("kernel32", SetLastError=true)] public static extern int GetCurrentDirectory(int a, StringBuilder b); } class Program { static void Main( ) { const int MaxDirLength = 250; StringBuilder sb = new StringBuilder(); sb.Length = MaxDirLength; MyClass.GetCurrentDirectory(MaxDirLength, sb); Console.WriteLine(sb); } } }
This code produces the following output:
C:BookProgramsExternalMethodExternalMethodinDebug
3.144.110.155