# Chapter 13 Interfaces
# Class and Interface Inheritance
In the Microsoft .NET Framework, there is a class called
System.Object
that defines four public instance methods:ToString
,Equals
,GetHashCode
, andGetType
. This class is the root or ultimate base class of all other classes—all classes will inherit Object’s four instance methods. This also means that code written to operate on an instance of the Object class can actually perform operations on an instance of any class.
Because someone at Microsoft has implemented Object’s methods, any class derived from Object is actually inheriting the following:
The method signatures This allows code to think that it is operating on an instance of the Object class, when in fact, it could be operating on an instance of some other class.
The implementation of these methods This allows the developer defining a class derived from Object not to be required to manually implement Object’s methods.
In the CLR, a class is always derived from one and only one class (that must ultimately be derived from Object). This base class provides a set of method signatures and implementations for these methods. And a cool thing about defining a new class is that it can become the base class for another class defined in the future by some other developer—all of the method signatures and their implementations will be inherited by the new derived class.
The CLR also allows developers to define an interface, which is really just a way to give a name to a set of method signatures. These methods do not come with any implementation at all. A class inherits an interface by specifying the interface’s name, and the class must explicitly provide implementations of the interface’s methods before the CLR will consider the type definition to be valid. Of course, implementing interface methods can be tedious, which is why I referred to interface inheritance as a scaled-down mechanism to achieve multiple inheritance. The C# compiler and the CLR actually allow a class to inherit several interfaces, and of course, the class must provide implementations for all of the inherited interface methods.
One of the great features of class inheritance is that it allows instances of a derived type to be substituted in all contexts that expect instances of a base type. Similarly, interface inheritance allows instances of a type that implements the interface to be substituted in all contexts that expect instances of the named interface type. We will now look at how to define interfaces to make our discussion more concrete.
💡小结:CLR 不支持多继承(因此所有托管编程语言也支持不了)。CLR 只是通过接口提供了 “缩水版” 的多继承。由于 Microsoft 的开发团队已实现了 Object 的方法,所以从 Object 派生的任何类实际都继承了 Object 的方法签名和方法实现。除了基类,CLR 还允许开发人员定义接口,它实际只是对一组方法签名进行了统一命名。这些方法不提供任何实现。类通过指定接口名称来继承接口,而且必须显式实现接口方法,否则 CLR 会认为此类型定义无效。C# 编译器和 CLR 允许一个类继承多个接口。当然,继承的所有接口方法都必须实现。
# Defining an Interface
As mentioned in the previous section, an interface is a named set of method signatures. Note that interfaces can also define events, parameterless properties, and parameterful properties (indexers in C#) because all of these are just syntax shorthands that map to methods anyway, as shown in previous chapters. However, an interface cannot define any constructor methods. In addition, an interface is not allowed to define any instance fields.
Although the CLR does allow an interface to define static methods, static fields, constants, and static constructors, a Common Language Specification (CLS)–compliant interface must not have any of these static members because some programming languages aren’t able to define or access them. In fact, C# prevents an interface from defining any of these static members.
In C#, you use the interface keyword to define an interface, giving it a name and its set of instance method signatures. Here are the definitions of a few interfaces defined in the Framework Class Library (FCL).
public interface IDisposable { | |
void Dispose(); | |
} | |
public interface IEnumerable { | |
IEnumerator GetEnumerator(); | |
} | |
public interface IEnumerable<out T> : IEnumerable { | |
new IEnumerator<T> GetEnumerator(); | |
} | |
public interface ICollection<T> : IEnumerable<T>, IEnumerable { | |
void Add(T item); | |
void Clear(); | |
Boolean Contains(T item); | |
void CopyTo(T[] array, Int32 arrayIndex); | |
Boolean Remove(T item); | |
Int32 Count { get; } // Read-only property | |
Boolean IsReadOnly { get; } // Read-only property | |
} |
To the CLR, an interface definition is just like a type definition. That is, the CLR will define an internal data structure for the interface type object, and reflection can be used to query features of the interface type. Like types, an interface can be defined at file scope or defined nested within another type. When defining the interface type, you can specify whatever visibility/accessibility (public, protected, internal, etc.) you want.
By convention, interface type names are prefixed with an uppercase I, making it easy to spot an interface type in source code. The CLR does support generic interfaces (as you can see from some of the previous examples) as well as generic methods in an interface. I will discuss some of the many features offered by generic interfaces later in this chapter and in Chapter 12, “Generics,” in which I cover generics more broadly.
An interface definition can “inherit” other interfaces. However, I use the word inherit here rather loosely because interface inheritance doesn’t work exactly like class inheritance. I prefer to think of interface inheritance as including the contract of other interfaces. For example, the
ICollection
interface definition includes the contracts of theIEnumerable
andIEnumerable
interfaces.
This means that:
Any class that inherits the
ICollection
interface must implement all of the methods defined by theICollection
,IEnumerable
, andIEnumerable
interfaces.Any code that expects an object whose type implements the
ICollection
interface can assume that the object’s type also implements the methods of theIEnumerable
andIEnumerable
interfaces.
💡小结:除了定义方法之外,接口还能定义事件、无参属性和有参属性(C# 的索引器)。如前所述,所有这些东西本质上都是方法,它们只是语法上的简化。不过,接口不能定义任何构造器方法,也不能定义任何实例字段。虽然 CLR 允许接口定义静态方法、静态字段、常量和静态构造器,但符合 CLS 标准的接口绝不允许,因为有的编程语言不能定义或访问它们。事实上,C# 禁止接口定义任何一种这样的静态成员。C# 用 interface 关键字定义接口。要为接口指定名称和一组实例方法签名。在 CLR 看来,接口定义就是类型定义。也就是说,CLR 会为接口类型对象定义内部数据结构,同时可通过反射机制来查询接口类型的功能。和类型一样,接口可在文件范围中定义,也可嵌套在另一个类型中。定义接口类型时,可指定你希望的任何可见性 / 可访问性(public,protected,internal 等)。接口可以将其他接口的协定(contract)包括到新接口中。例如, ICollection<T>
接口定义就包含了 IEnumerable<T>
和 IEnumerable
两个接口的协定。
# Inheriting an Interface
In this section, I’ll show how to define a type that implements an interface, and then I’ll show how to create an instance of this type and use the object to call the interface’s methods. C# actually makes this pretty simple, but what happens behind the scenes is a bit more complicated. I’ll explain what is happening behind the scenes later in this chapter.
The
System.IComparable
interface is defined (in MSCorLib.dll) as follows.
public interface IComparable<in T> { | |
Int32 CompareTo(T other); | |
} |
The following code shows how to define a type that implements this interface and also shows code that compares two Point objects.
using System; | |
// Point is derived from System.Object and implements IComparable<T> for Point. | |
public sealed class Point : IComparable<Point> { | |
private Int32 m_x, m_y; | |
public Point(Int32 x, Int32 y) { | |
m_x = x; | |
m_y = y; | |
} | |
// This method implements IComparable<T>.CompareTo() for Point | |
public Int32 CompareTo(Point other) { | |
return Math.Sign(Math.Sqrt(m_x * m_x + m_y * m_y) | |
- Math.Sqrt(other.m_x * other.m_x + other.m_y * other.m_y)); | |
} | |
public override String ToString() { | |
return String.Format("({0}, {1})", m_x, m_y); | |
} | |
} | |
public static class Program { | |
public static void Main() { | |
Point[] points = new Point[] { | |
new Point(3, 3), | |
new Point(1, 2), | |
}; | |
// Here is a call to Point's IComparable<T> CompareTo method | |
if (points[0].CompareTo(points[1]) > 0) { | |
Point tempPoint = points[0]; | |
points[0] = points[1]; | |
points[1] = tempPoint; | |
} | |
Console.WriteLine("Points from closest to (0, 0) to farthest:"); | |
foreach (Point p in points) | |
Console.WriteLine(p); | |
} | |
} |
The C# compiler requires that a method that implements an interface be marked as public. The CLR requires that interface methods be marked as virtual. If you do not explicitly mark the method as virtual in your source code, the compiler marks the method as virtual and sealed; this prevents a derived class from overriding the interface method. If you explicitly mark the method as virtual, the compiler marks the method as virtual (and leaves it unsealed); this allows a derived class to override the interface method.
If an interface method is sealed, a derived class cannot override the method. However, a derived class can re-inherit the same interface and can provide its own implementation for the interface’s methods. When calling an interface’s method on an object, the implementation associated with the object’s type is called. Here is an example that demonstrates this.
using System; | |
public static class Program { | |
public static void Main() { | |
/************************* First Example *************************/ | |
Base b = new Base(); | |
// Calls Dispose by using b's type: "Base's Dispose" | |
b.Dispose(); | |
// Calls Dispose by using b's object's type: "Base's Dispose" | |
((IDisposable)b).Dispose(); | |
/************************* Second Example ************************/ | |
Derived d = new Derived(); | |
// Calls Dispose by using d's type: "Derived's Dispose" | |
d.Dispose(); | |
// Calls Dispose by using d's object's type: "Derived's Dispose" | |
((IDisposable)d).Dispose(); | |
/************************* Third Example *************************/ | |
b = new Derived(); | |
// Calls Dispose by using b's type: "Base's Dispose" | |
b.Dispose(); | |
// Calls Dispose by using b's object's type: "Derived's Dispose" | |
((IDisposable)b).Dispose(); | |
} | |
} | |
// This class is derived from Object and it implements IDisposable | |
internal class Base : IDisposable { | |
// This method is implicitly sealed and cannot be overridden | |
public void Dispose() { | |
Console.WriteLine("Base's Dispose"); | |
} | |
} | |
// This class is derived from Base and it re-implements IDisposable | |
internal class Derived : Base, IDisposable { | |
// This method cannot override Base's Dispose. 'new' is used to indicate | |
// that this method re-implements IDisposable's Dispose method | |
new public void Dispose() { | |
Console.WriteLine("Derived's Dispose"); | |
// NOTE: The next line shows how to call a base class's implementation (if desired) | |
// base.Dispose(); | |
} | |
} |
💡小结:C# 编译器要求将实现接口的方法(后文简称为 “接口方法”)标记为 public。CLR 要求将接口方法标记为 virtual。不将方法显式标记为 virtual,编译器会将它们标记为 virtual 和 sealed;这会阻止派生类重写接口方法。将方法显式标记为 virtual,编译器就会将该方法标记为 virtual(并保持它的非密封状态),使派生类能重写它。派生类不能重写 sealed 的接口方法。但派生类可重新继承同一个接口,并为接口方法提供自己的实现。在对象上调用接口方法时,调用的是该方法在该对象的类型中的实现。
# More About Calling Interface Methods
The FCL’s
System.String
type inheritsSystem.Object
’s method signatures and their implementations. In addition, the String type also implements several interfaces:IComparable
,ICloneable
,IConvertible
,IEnumerable
,IComparable
,IEnumerable
, andIEquatable
. This means that the String type isn’t required to implement (or override) the methods its Object base type offers. However, the String type must implement the methods declared in all of the interfaces.
The CLR allows you to define field, parameter, or local variables that are of an interface type. Using a variable of an interface type allows you to call methods defined by that interface. In addition, the CLR will allow you to call methods defined by Object because all classes inherit Object’s methods. The following code demonstrates this.
// The s variable refers to a String object. | |
String s = "Jeffrey"; | |
// Using s, I can call any method defined in | |
// String, Object, IComparable, ICloneable, IConvertible, IEnumerable, etc. | |
// The cloneable variable refers to the same String object | |
ICloneable cloneable = s; | |
// Using cloneable, I can call any method declared by the | |
// ICloneable interface (or any method defined by Object) only. | |
// The comparable variable refers to the same String object | |
IComparable comparable = s; | |
// Using comparable, I can call any method declared by the | |
// IComparable interface (or any method defined by Object) only. | |
// The enumerable variable refers to the same String object | |
// At run time, you can cast a variable from one interface to another as | |
// long as the object's type implements both interfaces. | |
IEnumerable enumerable = (IEnumerable) comparable; | |
// Using enumerable, I can call any method declared by the | |
// IEnumerable interface (or any method defined by Object) only. |
In this code, all of the variables refer to the same “Jeffrey” String object that is in the managed heap, and therefore, any method that I call while using any of these variables applies to the one “Jeffrey” String object. However, the type of the variable indicates the action that I can perform on the object. The s variable is of type String, and therefore, I can use s to call any members defined by the String type (such as the Length property). I can also use the variable s to call any methods inherited from Object (such as
GetType
).
The cloneable variable is of the
ICloneable
interface type, and therefore, using the cloneable variable, I can call the Clone method defined by this interface. In addition, I can call any method defined by Object (such asGetType
) because the CLR knows that all types derive from Object. However, using the cloneable variable, I cannot call public methods defined by String itself or any methods defined by any other interface that String implements. Similarly, using the comparable variable, I can callCompareTo
or any method defined by Object, but no other methods are callable using this variable.
💡重要提示:和引用类型相似,值类型可实现零个或多个接口。但值类型的实例在转换为接口类型时必须装箱。这是由于接口变量是引用,必须指向堆上的对象,使 CLR 能检查对象的类型对象的类型对象指针,从而判断对象的确切类型。调用已装箱值类型的接口方法时,CLR 会跟随对象的类型对象指针找到类型对象的方法表,从而调用正确的方法。
💡小结:CLR 允许定义接口类型的字段、参数或局部变量。使用接口类型的变量可以调用该接口定义的方法。此外,CLR 允许调用 Object 定义的方法,因为所有类都继承了 Object 的方法。
# Implicit and Explicit Interface Method Implementations (What’s Happening Behind the Scenes)
When a type is loaded into the CLR, a method table is created and initialized for the type (as discussed in Chapter 1, “The CLR’s Execution Model”). This method table contains one entry for every new method introduced by the type as well as entries for any virtual methods inherited by the type. Inherited virtual methods include methods defined by the base types in the inheritance hierarchy as well as any methods defined by the interface types. So if you have a simple type defined like this:
internal sealed class SimpleType : IDisposable { | |
public void Dispose() { Console.WriteLine("Dispose"); } | |
} |
the type’s method table contains entries for the following:
All the virtual instance methods defined by Object, the implicitly inherited base class.
All the interface methods defined by
IDisposable
, the inherited interface. In this example, there is only one method, Dispose, because theIDisposable
interface defines just one method.The new method, Dispose, introduced by
SimpleType
.
To make things simple for the programmer, the C# compiler assumes that the Dispose method introduced by
SimpleType
is the implementation forIDisposable
’s Dispose method. The C# compiler makes this assumption because the method is public, and the signatures of the interface method and the newly introduced method are identical. That is, the methods have the same parameter and return types. By the way, if the new Dispose method were marked as virtual, the C# compiler would still consider this method to be a match for the interface method.
When the C# compiler matches a new method to an interface method, it emits metadata indicating that both entries in
SimpleType
’s method table should refer to the same implementation. To help make this clearer, here is some code that demonstrates how to call the class’s public Dispose method as well as how to call the class’s implementation ofIDisposable
’s Dispose method.
public sealed class Program { | |
public static void Main() { | |
SimpleType st = new SimpleType(); | |
// This calls the public Dispose method implementation | |
st.Dispose(); | |
// This calls IDisposable's Dispose method implementation | |
IDisposable d = st; | |
d.Dispose(); | |
} | |
} |
In the first call to Dispose, the Dispose method defined by SimpleType is called. Then I define a variable, d, which is of the IDisposable interface type. I initialize the d variable to refer to the SimpleType object. Now when I call d.Dispose(), I am calling the IDisposable interface’s Dispose method. Because C# requires the public Dispose method to also be the implementation for IDisposable’s Dispose method, the same code will execute, and, in this example, you can’t see any observable difference. The output is as follows.
Dispose
Dispose
Now, let me rewrite the preceding SimpleType so that you can see an observable difference.
internal sealed class SimpleType : IDisposable { | |
public void Dispose() { Console.WriteLine("public Dispose"); } | |
void IDisposable.Dispose() { Console.WriteLine("IDisposable Dispose"); } | |
} |
Without changing the Main method shown earlier, if we just recompile and rerun the program, the output will be the following.
public Dispose
IDisposable Dispose
In C#, when you prefix the name of a method with the name of the interface that defines the method (IDisposable.Dispose as in this example), you are creating an explicit interface method implementation (EIMI). Note that when you define an explicit interface method in C#, you are not allowed to specify any accessibility (such as public or private). However, when the compiler generates the metadata for the method, its accessibility is set to private, preventing any code using an instance of the class from simply calling the interface method. The only way to call the interface method is through a variable of the interface’s type.
Also note that an EIMI method cannot be marked as virtual and therefore cannot be overridden. This is because the EIMI method is not really part of the type’s object model; it’s a way of attaching an interface (set of behaviors or methods) onto a type without making the behaviors/methods obvious. If all of this seems a bit kludgy to you, you are understanding it correctly—this is all a bit kludgy. Later in this chapter, I’ll show some valid reasons for using EIMIs.
💡小结:类型加载到 CLR 中时,会为该类型创建并初始化一个方法表。在这个方法表中,类型引入的每个新方法都有对应的记录项;另外,还为该类型继承的所有虚方法添加了记录项。记录的虚方法既有继承层次结构中的各个基类型定义的,也有接口类型定义的。在上面的例子中,我们在 SimpleType
中定义了 Dispose
方法,为简化编程,C# 编译器假定 SimpleType
引入的 Dispose
方法是对 IDisposable
的 Dispose
方法的实现。之所以这样假定,是由于 Dispose
方法的可访问性是 public,而接口方法的签名和新引入的方法完全一致。如果新的 Dispose
方法被标记为 virtual,C# 编译器仍然认为该方法匹配接口方法。C# 编译器将新方法和接口方法匹配起来之后,会生成元数据,指明 SimpleType
类型的方法表中的两个记录项应引用同一个实现。由于 C# 要求公共 Dispose
方法同时是 IDisposable
的 Dispose
方法的实现,所以会执行相同的代码。在 C# 中,将定义方法的那个接口的名称作为作为方法名前缀(例如 IDisposable.Dispose
),就会创建显式接口方法实现(Explicit Interface Method Implementation,EIMI)。注意,C# 中不允许在定义显式接口方法时指定可访问性(比如 public 或 private)。但是,编译器生成方法的元数据时,可访问行会自动设为 private,防止其他代码在使用类的实例时直接调用接口方法。只有通过接口类型的变量才能调用接口方法。还要注意,EIMI 方法不能标记为 virtual,所以不能被重写。这是由于 EIMI 方法并非真的是类型的对象模型的一部分,它只是将接口(一组行为或方法)和类型联系起来,同时避免公开行为 / 方法。
# Generic Interfaces
C#’s and the CLR’s support of generic interfaces offers many great features for developers. In this section, I’d like to discuss the benefits offered when using generic interfaces.
First, generic interfaces offer great compile-time type safety. Some interfaces (such as the nongeneric IComparable interface) define methods that have Object parameters or return types. When code calls these interface methods, a reference to an instance of any type can be passed. But this is usually not desired. The following code demonstrates.
private void SomeMethod1() { | |
Int32 x = 1, y = 2; | |
IComparable c = x; | |
// CompareTo expects an Object; passing y (an Int32) is OK | |
c.CompareTo(y); // y is boxed here | |
// CompareTo expects an Object; passing "2" (a String) compiles | |
// but an ArgumentException is thrown at runtime | |
c.CompareTo("2"); | |
} |
Obviously, it is preferable to have the interface method strongly typed, and this is why the FCL includes a generic IComparable interface.
Here is the new version of the code revised by using the generic interface.
private void SomeMethod2() { | |
Int32 x = 1, y = 2; | |
IComparable<Int32> c = x; | |
// CompareTo expects an Int32; passing y (an Int32) is OK | |
c.CompareTo(y); // y is not boxed here | |
// CompareTo expects an Int32; passing "2" (a String) results | |
// in a compiler error indicating that String cannot be cast to an Int32 | |
c.CompareTo("2"); // Error | |
} |
The second benefit of generic interfaces is that much less boxing will occur when working with value types. Notice in SomeMethod1 that the non-generic IComparable interface’s CompareTo method expects an Object; passing y (an Int32 value type) causes the value in y to be boxed. However, in SomeMethod2, the generic IComparable interface’s CompareTo method expects an Int32; passing y causes it to be passed by value, and no boxing is necessary.
💡注意 FCL 定义了 IComparable
, ICollection
, IList
和 IDictionary
等接口的泛型和非泛型版本。定义类型时要实现其中任何接口,一般应实现泛型版本。FCL 保留非泛型版本是为了向后兼容,照顾在 .NET Framework 支持泛型之前写的代码。非泛型版本还允许用户以较常规的、类型较不安全 (more general,less type-safe) 的方式处理数据。
有的泛型接口继承了非泛型版本,所以必须同时实现接口的泛型和非泛型版本。例如,泛型 IEnumerable<out T>
接口继承了非泛型 IEnumerable
接口,所以实现 IEnumerable<out T>
就必须实现 IEnumerable
。
和其他代码集成时,有时必须实现非泛型接口,因为接口的泛型版本并不存在。这时,如果接口的任何方法获取或返回 Object
,就会失去编译时的类型安全性,而且值类型将发生装箱。可利用本章 13.9 节 “用显式接口方法实现来增强编译时类型安全性” 介绍的技术来缓解该问题。
The third benefit of generic interfaces is that a class can implement the same interface multiple times as long as different type parameters are used.
The following code shows an example of how useful this could be.
using System; | |
// This class implements the generic IComparable<T> interface twice | |
public sealed class Number: IComparable<Int32>, IComparable<String> { | |
private Int32 m_val = 5; | |
// This method implements IComparable<Int32>'s CompareTo | |
public Int32 CompareTo(Int32 n) { | |
return m_val.CompareTo(n); | |
} | |
// This method implements IComparable<String>'s CompareTo | |
public Int32 CompareTo(String s) { | |
return m_val.CompareTo(Int32.Parse(s)); | |
} | |
} | |
public static class Program { | |
public static void Main() { | |
Number n = new Number(); | |
// Here, I compare the value in n with an Int32 (5) | |
IComparable<Int32> cInt32 = n; | |
Int32 result = cInt32.CompareTo(5); | |
// Here, I compare the value in n with a String ("5") | |
IComparable<String> cString = n; | |
result = cString.CompareTo("5"); | |
} | |
} |
An interface’s generic type parameters can also be marked as contra-variant and covariant, which allows even more flexibility for using generic interfaces. For more about contra-variance and covariance, see the “Delegate and Interface Contra-variant and Covariant Generic Type Arguments” section in Chapter 12.
💡小结:C# 和 CLR 所支持的泛型接口为开发人员提供了需要非常出色的功能。首先,泛型接口提供了出色的编译时类型安全性。接口方法理想情况下应该使用强类型。这正是 FCL 为什么包含众多泛型接口的原因。泛型接口的第二个好处在于,处理值类型时装箱次数会少很多。泛型接口的第三个好处在于,类可以实现同一个接口若干次,只要每次使用不同的类型参数。接口的泛型类型参数可标记为逆变和协变,为泛型接口的使用提供更大的灵活性。
# Generics and Interface Constraints
In the previous section, I discussed the benefits of using generic interfaces. In this section, I’ll discuss the benefits of constraining generic type parameters to interfaces.
The first benefit is that you can constrain a single generic type parameter to multiple interfaces. When you do this, the type of parameter you are passing in must implement all of the interface constraints.
Here is an example.
public static class SomeType { | |
private static void Test() { | |
Int32 x = 5; | |
Guid g = new Guid(); | |
// This call to M compiles fine because | |
// Int32 implements IComparable AND IConvertible | |
M(x); | |
// This call to M causes a compiler error because | |
// Guid implements IComparable but it does not implement IConvertible | |
M(g); | |
} | |
// M's type parameter, T, is constrained to work only with types that | |
// implement both the IComparable AND IConvertible interfaces | |
private static Int32 M<T>(T t) where T : IComparable, IConvertible { | |
... | |
} | |
} |
This is actually quite cool! When you define a method’s parameters, each parameter’s type indicates that the argument passed must be of the parameter’s type or be derived from it. If the parameter type is an interface, this indicates that the argument can be of any class type as long as the class implements the interface. Using multiple interface constraints actually lets the method indicate that the passed argument must implement multiple interfaces.
In fact, if we constrained T to a class and two interfaces, we are saying that the type of argument passed must be of the specified base class (or derived from it), and it must also implement the two interfaces. This flexibility allows the method to really dictate what callers can pass, and compiler errors will be generated if callers do not meet these constraints.
The second benefit of interface constraints is reduced boxing when passing instances of value types. In the previous code fragment, the M method was passed x (an instance of an Int32, which is a value type). No boxing will occur when x is passed to M. If code inside M does call t.CompareTo(...), still no boxing occurs to make the call (boxing may still happen for arguments passed to CompareTo).
On the other hand, if M had been declared like this:
private static Int32 M(IComparable t) { | |
... | |
} |
then in order to pass x to M, x would have to be boxed.
For interface constraints, the C# compiler emits certain Intermediate Language (IL) instructions that result in calling the interface method on the value type directly without boxing it. Aside from using interface constraints, there is no other way to get the C# compiler to emit these IL instructions, and therefore, calling an interface method on a value type always causes boxing.
💡小结:将泛型类型参数约束为接口有两个好处。第一个好处在于,可将泛型类型参数约束为多个接口。这样一来,传递的参数的类型必须实现全部接口约束。如果将 T 约束为一个类和两个接口,就表示传递的实参类型必须是指定的基类(或者它的派生类),而且必须实现两个接口。这种灵活性使方法能细致地约束调用者能传递的内容。调用者不满足这些约束,就会产生编译错误。接口约束的第二个好处是传递值类型的实例时减少装箱。C# 编译器为接口约束生成特殊 IL 指令,导致直接在值类型上调用接口方法而不装箱。不用接口约束便没有其他办法让 C# 编译器生成这些 IL 指令。如此一来,在值类型上调用接口方法总是发生装箱。一个例外是如果值类型实现了一个接口方法,在值类型的实例上调用这个方法不会造成值类型的实例装箱。
# Implementing Multiple Interfaces That Have the Same Method Name and Signature
Occasionally, you might find yourself defining a type that implements multiple interfaces that define methods with the same name and signature. For example, imagine that there are two interfaces defined as follows.
public interface IWindow { | |
Object GetMenu(); | |
} | |
public interface IRestaurant { | |
Object GetMenu(); | |
} |
Let’s say that you want to define a type that implements both of these interfaces. You’d have to implement the type’s members by using explicit interface method implementations as follows.
// This type is derived from System.Object and | |
// implements the IWindow and IRestaurant interfaces. | |
public sealed class MarioPizzeria : IWindow, IRestaurant { | |
// This is the implementation for IWindow's GetMenu method. | |
Object IWindow.GetMenu() { ... } | |
// This is the implementation for IRestaurant's GetMenu method. | |
Object IRestaurant.GetMenu() { ... } | |
// This (optional method) is a GetMenu method that has nothing | |
// to do with an interface. | |
public Object GetMenu() { ... } | |
} |
Because this type must implement multiple and separate GetMenu methods, you need to tell the C# compiler which GetMenu method contains the implementation for a particular interface.
Code that uses a MarioPizzeria object must cast to the specific interface to call the desired method. The following code demonstrates.
MarioPizzeria mp = new MarioPizzeria(); | |
// This line calls MarioPizzeria's public GetMenu method | |
mp.GetMenu(); | |
// These lines call MarioPizzeria's IWindow.GetMenu method | |
IWindow window = mp; | |
window.GetMenu(); | |
// These lines call MarioPizzeria's IRestaurant.GetMenu method | |
IRestaurant restaurant = mp; | |
restaurant.GetMenu(); |
💡小结:定义实现多个接口的类型时,这些接口可能定义了具有相同名称和签名的方法。要定义实现这两个接口的类型,必须使用 “显式接口方法实现” 来实现这个类型的成员,并在使用对应接口的方法时需要将其转换为具体的接口才能调用所需的方法。
# Improving Compile-Time Type Safety with Explicit Interface Method Implementations
Interfaces are great because they define a standard way for types to communicate with each other. Earlier, I talked about generic interfaces and how they improve compile-time type safety and reduce boxing. Unfortunately, there may be times when you need to implement a non-generic interface because a generic version doesn’t exist. If any of the interface’s method(s) accept parameters of type System.Object or return a value whose type is System.Object, you will lose compile-time type safety, and you will get boxing. In this section, I’ll show you how you can use EIMI to improve this situation somewhat.
Look at the very common IComparable interface.
public interface IComparable { | |
Int32 CompareTo(Object other); | |
} |
This interface defines one method that accepts a parameter of type System.Object. If I define my own type that implements this interface, the type definition might look like the following.
internal struct SomeValueType : IComparable { | |
private Int32 m_x; | |
public SomeValueType(Int32 x) { m_x = x; } | |
public Int32 CompareTo(Object other) { | |
return(m_x - ((SomeValueType) other).m_x); | |
} | |
} |
Using SomeValueType, I can now write the following code.
public static void Main() { | |
SomeValueType v = new SomeValueType(0); | |
Object o = new Object(); | |
Int32 n = v.CompareTo(v); // Undesired boxing | |
n = v.CompareTo(o); // InvalidCastException | |
} |
There are two characteristics of this code that are not ideal.
Undesired boxing When v is passed as an argument to the CompareTo method, it must be boxed because CompareTo expects an Object.
The lack of type safety This code compiles, but an InvalidCastException is thrown inside the CompareTo method when it attempts to cast o to SomeValueType.
Both of these issues can be fixed by using EIMIs. Here’s a modified version of SomeValueType that has an EIMI added to it.
internal struct SomeValueType : IComparable { | |
private Int32 m_x; | |
public SomeValueType(Int32 x) { m_x = x; } | |
public Int32 CompareTo(SomeValueType other) { | |
return(m_x - other.m_x); | |
} | |
// NOTE: No public/private used on the next line | |
Int32 IComparable.CompareTo(Object other) { | |
return CompareTo((SomeValueType) other); | |
} | |
} |
Notice several changes in this new version. First, it now has two CompareTo methods. The first CompareTo method no longer takes an Object as a parameter; it now takes a SomeValueType instead. Because this parameter has changed, the code that casts other to SomeValueType is no longer necessary and has been removed. Second, changing the first CompareTo method to make it type-safe means that SomeValueType no longer adheres to the contract placed on it by implementing the IComparable interface. So SomeValueType must implement a CompareTo method that satisfies the IComparable contract. This is the job of the second IComparable.CompareTo method, which is an EIMI.
Having made these two changes means that we now get compile-time type safety and no boxing.
public static void Main() { | |
SomeValueType v = new SomeValueType(0); | |
Object o = new Object(); | |
Int32 n = v.CompareTo(v); // No boxing | |
n = v.CompareTo(o); // compile-time error | |
} |
If, however, we define a variable of the interface type, we will lose compile-time type safety and experience undesired boxing again.
public static void Main() { | |
SomeValueType v = new SomeValueType(0); | |
IComparable c = v; // Boxing! | |
Object o = new Object(); | |
Int32 n = c.CompareTo(v); // Undesired boxing | |
n = c.CompareTo(o); // InvalidCastException | |
} |
In fact, as mentioned earlier in this chapter, when casting a value type instance to an interface type, the CLR must box the value type instance. Because of this fact, two boxings will occur in the previous Main method.
EIMIs are frequently used when implementing interfaces such as IConvertible, ICollection, IList, and IDictionary. They let you create type-safe versions of these interfaces’ methods, and they enable you to reduce boxing operations for value types.
💡小结:有时由于不存在泛型版本的接口,所以仍需实现非泛型接口。接口的任何方法接口 System.Object
类型的参数或返回 System.Object
类型的值,就会失去编译时的类型安全性,装箱也会发生。这两个问题都可以用 EIMI 解决。不过,定义接口类型的变量会再次失去编译时的类型安全性,而且会再次发生装箱。实现 IConvertible
, ICollection
, IList
和 IDictionary
等接口时 EIMI 很有用。可利用它为这些接口的方法创建类型安全的版本,并减少值类型的装箱。
# Be Careful with Explicit Interface Method Implementations
It is critically important for you to understand some ramifications that exist when using EIMIs. And because of these ramifications, you should try to avoid EIMIs as much as possible. Fortunately, generic interfaces help you avoid EIMIs quite a bit. But there may still be times when you will need to use them (such as implementing two interface methods with the same name and signature). Here are the big problems with EIMIs:
There is no documentation explaining how a type specifically implements an EIMI method, and there is no Microsoft Visual Studio IntelliSense support.
Value type instances are boxed when cast to an interface.
An EIMI cannot be called by a derived type.
Let’s take a closer look at these problems.
When examining the methods for a type in the .NET Framework reference documentation, explicit interface method implementations are listed, but no type-specific help exists; you can just read the general help about the interface methods. For example, the documentation for the Int32 type shows that it implements all of
IConvertible
interface’s methods. This is good because developers know that these methods exist; however, this has been very confusing to developers because you can’t call anIConvertible
method on an Int32 directly. For example, the following method won’t compile.
public static void Main() { | |
Int32 x = 5; | |
Single s = x.ToSingle(null); // Trying to call an IConvertible method | |
} |
When compiling this method, the C# compiler produces the following message: messagepil17: 'int' does not contain a definition for '
ToSingle
'. This error message confuses the developer because it’s clearly stating that the Int32 type doesn’t define aToSingle
method when, in fact, it does.
To call
oSingle
on an Int32, you must first cast the Int32 to anIConvertible
, as shown in the following method.
public static void Main() { | |
Int32 x = 5; | |
Single s = ((IConvertible) x).ToSingle(null); | |
} |
Requiring this cast isn’t obvious at all, and many developers won’t figure this out on their own. But an even more troublesome problem exists: casting the Int32 value type to an
IConvertible
also boxes the value type, wasting memory and hurting performance. This is the second of the big problems I mentioned at the beginning of this section.
The third and perhaps the biggest problem with EIMIs is that they cannot be called by a derived class. Here is an example.
internal class Base : IComparable { | |
// Explicit Interface Method Implementation | |
Int32 IComparable.CompareTo(Object o) { | |
Console.WriteLine("Base's CompareTo"); | |
return 0; | |
} | |
} | |
internal sealed class Derived : Base, IComparable { | |
// A public method that is also the interface implementation | |
public Int32 CompareTo(Object o) { | |
Console.WriteLine("Derived's CompareTo"); | |
// This attempt to call the base class's EIMI causes a compiler error: | |
// error CS0117: 'Base' does not contain a definition for 'CompareTo' | |
base.CompareTo(o); | |
return 0; | |
} | |
} |
In
Derived
’sCompareTo
method, I try to callbase.CompareTo
, but this causes the C# compiler to issue an error. The problem is that the Base class doesn’t offer a public or protectedCompareTo
method that can be called; it offers aCompareTo
method that can be called only by using a variable that is of theIComparable
type. I could modifyDerived
’sCompareTo
method so that it looks like the following.
// A public method that is also the interface implementation | |
public Int32 CompareTo(Object o) { | |
Console.WriteLine("Derived's CompareTo"); | |
// This attempt to call the base class's EIMI causes infinite recursion | |
IComparable c = this; | |
c.CompareTo(o); | |
return 0; | |
} |
In this version, I am casting this to an
IComparable
variable, c. And then, I use c to callCompareTo
. However, theDerived
’s publicCompareTo
method serves as the implementation forDerived
’sIComparableCompareTo
method, and therefore, infinite recursion occurs. This could be fixed by declaring the Derived class without theIComparable
interface, like the following.
internal sealed class Derived : Base /*, IComparable */ { ... } |
Now the previous
CompareTo
method will call theCompareTo
method in Base. But sometimes you cannot simply remove the interface from the type because you want the derived type to implement an interface method. The best way to fix this is for the base class to provide a virtual method in addition to the interface method that it has chosen to implement explicitly. Then the Derived class can override the virtual method. Here is the correct way to define the Base and Derived classes.
internal class Base : IComparable { | |
// Explicit Interface Method Implementation | |
Int32 IComparable.CompareTo(Object o) { | |
Console.WriteLine("Base's IComparable CompareTo"); | |
return CompareTo(o); // This now calls the virtual method | |
} | |
// Virtual method for derived classes (this method could have any name) | |
public virtual Int32 CompareTo(Object o) { | |
Console.WriteLine("Base's virtual CompareTo"); | |
return 0; | |
} | |
} | |
internal sealed class Derived : Base, IComparable { | |
// A public method that is also the interface implementation | |
public override Int32 CompareTo(Object o) { | |
Console.WriteLine("Derived's CompareTo"); | |
// Now, we can call Base's virtual method | |
return base.CompareTo(o); | |
} | |
} |
Note that I have defined the virtual method above as a public method, but in some cases, you will prefer to make the method protected instead. It is fine to make this method protected instead of public, but that will necessitate other minor changes. This discussion clearly shows you that EIMIs should be used with great care. When many developers first learn about EIMIs, they think that they’re cool and they start using them whenever possible. Don’t do this! EIMIs are useful in some circumstances, but you should avoid them whenever possible because they make using a type much more difficult.
💡小结:使用 EIMI 也可能造成一些严重后果,所以应该尽量避免使用 EIMI。EIMI 最主要的问题如下:没有文档解释类型具体如何实现一个 EIMI 方法;值类型的实例在转换成接口时装箱;EIMI 不能由派生类型调用。解决派生类调用问题的最佳方法是在基类中除了提供一个被选为显式实现的接口方法,还要提供一个虚方法。
# Design: Base Class or Interface?
I often hear the question, “Should I design a base type or an interface?” The answer isn’t always clearcut. Here are some guidelines that might help you:
IS-A vs. CAN-DO relationship A type can inherit only one implementation. If the derived type can’t claim an IS-A relationship with the base type, don’t use a base type; use an interface. Interfaces imply a CAN-DO relationship. If the CAN-DO functionality appears to belong with various object types, use an interface. For example, a type can convert instances of itself to another type (IConvertible), a type can serialize an instance of itself (ISerializable), etc. Note that value types must be derived from System.ValueType, and therefore, they cannot be derived from an arbitrary base class. In this case, you must use a CAN-DO relationship and define an interface.
Ease of use It’s generally easier for you as a developer to define a new type derived from a base type than to implement all of the methods of an interface. The base type can provide a lot of functionality, so the derived type probably needs only relatively small modifications to its behavior. If you supply an interface, the new type must implement all of the members.
Consistent implementation No matter how well an interface contract is documented, it’s very unlikely that everyone will implement the contract 100 percent correctly. In fact, COM suffers from this very problem, which is why some COM objects work correctly only with Microsoft Word or with Windows Internet Explorer. By providing a base type with a good default implementation, you start off using a type that works and is well tested; you can then modify parts that need modification.
Versioning If you add a method to the base type, the derived type inherits the new method, you start off using a type that works, and the user’s source code doesn’t even have to be recompiled. Adding a new member to an interface forces the inheritor of the interface to change its source code and recompile.
In the FCL, the classes related to streaming data use an implementation inheritance design. The System.IO.Stream class is the abstract base class. It provides a bunch of methods, such as Read and Write. Other classes—System.IO.FileStream, System.IO.MemoryStream, and System.Net. Sockets.NetworkStream—are derived from Stream. Microsoft chose an IS-A relationship between each of these three classes and the Stream class because it made implementing the concrete classes easier. For example, the derived classes need to implement only synchronous I/O operations; they inherit the ability to perform asynchronous I/O operations from the Stream base class.
Admittedly, choosing to use inheritance for the stream classes isn’t entirely clear-cut; the Stream base class actually provides very little implementation. However, if you consider the Windows Forms control classes, in which Button, CheckBox, ListBox, and all of the other controls are derived from System.Windows.Forms.Control, it’s easy to imagine all of the code that Control implements, which the various control classes simply inherit to function correctly.
By contrast, Microsoft designed the FCL collections to be interface based. The System.Collections.Generic namespace defines several collection-related interfaces: IEnumerable, ICollection, IList, and IDictionary. Then Microsoft provided a number of classes, such as List, Dictionary, Queue, Stack, and so on, that implement combinations of these interfaces. Here the designers chose a CAN-DO relationship between the classes and the interfaces because the implementations of these various collection classes are radically different from one another. In other words, there isn’t a lot of sharable code between a List, a Dictionary, and a Queue.
The operations these collection classes offer are, nevertheless, pretty consistent. For example, they all maintain a set of elements that can be enumerated, and they all allow adding and removing of elements. If you have a reference to an object whose type implements the IList interface, you can write code to insert elements, remove elements, and search for an element without having to know exactly what type of collection you’re working with. This is a very powerful mechanism.
Finally, it should be pointed out that you can actually do both: define an interface and provide a base class that implements the interface. For example, the FCL defines the IComparer interface and any type can choose to implement this interface. In addition, the FCL provides an abstract base class, Comparer, which implements this interface and provides a default implementation for the non-generic IComparer’s Compare method. Having both an interface definition and a base class offers great flexibility because developers can now choose whichever they prefer.
💡小结:选择基类还是接口可以从几个方面来考虑:1.IS-A 对比 CAN-DO 关系,如果派生类型和基类型建立不起 IS-A 关系,就不用基类而用接口。接口意味着 CAN-DO 关系。如果多种对象类型都 “能” 做某事,就为它们创建接口。2. 易用性,基类型可提供大量功能,所以派生类型可能只需稍微做改动。而提供接口的话,新类型必须实现所有成员。3. 一致性实现,无论接口协定(contract)订立得有多好,都无法保证所有人百分之百正确实现它。而如果为基类型提供良好的默认实现,那么一开始得到的就是能正常工作并经过良好测试的类型。以后根据需要修改就可以了。4. 版本控制,向基类型添加一个方法,派生类型将继承新方法。一开始使用的就是一个能正常工作的类型,用户的源代码甚至不需要重新编译。而向接口添加新成员,会强迫接口的继承者更改其源代码并重新编译。