More Effective C# Item7 : 不要为基类或者接口创建泛型的特殊实现
引入泛型方法将让编译器对重载的解析变得非常复杂,每个泛型方法的类型参数都可以任意转换。如果稍有疏忽,程序的行为都将变得极其古怪,在创建泛型类或者方法时,必须保证让使用者能够尽可能的理解你的设计意图,安全的使用你的代码。
我们来查看以下的代码,首先定义一个具有继承层次的结构。
代码
下面是测试代码。
1 public class MyBase
2 { }
3
4 public interface MyInterface
5 {
6 void WriteMessage();
7 }
8
9 public class MyDerived : MyBase, MyInterface
10 {
11 public void WriteMessage()
12 {
13 Console.WriteLine("MyDerived.WriteMessage");
14 }
15 }
16
17 public class AnotherImpl : MyInterface
18 {
19 public void WriteMessage()
20 {
21 Console.WriteLine("AnotherImpl.WriteMessage");
22 }
23 }
代码
你能猜出程序的输出结果吗?
1 private static void WriteMessage(MyInterface obj)
2 {
3 Console.WriteLine("Inside WriteMessage(MyInterface)");
4 obj.WriteMessage();
5 }
6
7 private static void WriteMessage(MyBase obj)
8 {
9 Console.WriteLine("Inside WriteMessage(MyBase)");
10 }
11
12 private static void WriteMessage<T>(T obj)
13 {
14 Console.WriteLine("Inside WriteMessge<T>()");
15 Console.WriteLine(obj.ToString());
16 }
17
18 private static void Test()
19 {
20 MyDerived derived = new MyDerived();
21 WriteMessage(derived);
22 Console.WriteLine();
23 WriteMessage((MyInterface)derived);
24 Console.WriteLine();
25 WriteMessage((MyBase)derived);
26 Console.WriteLine();
27
28 AnotherImpl another = new AnotherImpl();
29 WriteMessage(another);
30 Console.WriteLine();
31 WriteMessage((MyInterface)another);
32 }
程序的输出结果如下所示
其实,下面这行代码是执行的最诡异的一行代码
1 MyDerived derived = new MyDerived();
2 WriteMessage(derived);
而接下来的两个测试,则说明即使不在类型的继承体系中,编译器选择重载版本时,也会遵循同样的原则,即如果参数的运行类型不能够精确匹配非泛型版本中的参数类型时,那么编译器会优先考虑使用泛型版本的重载形式;如果参数的运行时类型能够精确匹配费泛型版本中的参数类型时,纳闷编译器会优先考虑使用非泛型版本的重载形式。相对于类型转换来说(这里指隐式类型转换),泛型版本更具优势。
由此,我们可以看出编译器对名字解析的规则是非常有趣的,当你想支持某一个类型及其派生类型时,基于基类创建泛型并不是一个好的选择,同样,基于接口也是如此。我们在进行设计时,需要优先考虑让编译器自己决定参数的类型,而不是在程序中进行动态的判定。
有时,我们也可以为特定的类型创建专门的处理方法,这时需要对重载类型的类型进行检查,如果符合条件,那么就会执行特定的逻辑。通常情况下,是不建议这么做的,因为这样违反了泛型的初衷,因为泛型就是对类型的抽象。
在一个有继承关系或者接口实现的场景中,重载方法时需要特别注意,尤其是同时包含了非泛型版本的重载形式和泛型版本的重载形式,你需要非常清楚的知道,在运行时,到底是哪种形式的重载会被调用。
作者:李潘
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。