为何C#泛型类型约束示例会产生不同执行结果?
我正在学习C#的泛型类型约束,在类类型约束示例中发现,派生类中Get方法的执行结果依赖于基类中该方法的代码,对此我无法理解。以下是我的示例代码:
B b = new B(10, 20); Print(b); void Print<T>(T t) where T : A { Console.WriteLine(t.Get()); } class A { protected int a; public A(int a) { this.a = a; } public A Get() { return this; // return new A(a); } public override string ToString() { return $"{a}"; } } class B : A { int b; public B(int a, int b) : base(a) { this.b = b; } public B Get() { return this; } public override string ToString() { return $"{a}, {b}"; } }
当前代码执行会输出“10, 20”,但将基类A的Get方法中return this替换为return new A(a)后,执行结果变为“10”,希望有人能解释原因。
原因解释
1. 派生类的Get方法是方法隐藏,不是重写
你代码里B类的public B Get()并没有重写A类的public A Get()——重写必须加override关键字,你这里没加,所以这属于方法隐藏(编译器会给出警告,提示你应显式添加new关键字)。这种情况下,B的Get方法只是“覆盖”了基类同名方法,但不会触发多态机制。
2. 泛型方法里调用的是基类的Get方法
泛型方法Print<T>的约束是T : A,编译器仅知道t是A类型(或其派生类),所以调用t.Get()时,会绑定到A类的Get方法,而非B类的Get方法——因为没有通过重写实现多态。
3. 两种返回值的本质差异
当A的Get返回
this时:this在B实例中就是当前的B对象,所以Console.WriteLine(t.Get())会调用这个B对象的ToString()(你已经用override重写了ToString),自然输出10, 20。当A的Get返回
new A(a)时:
这里返回的是一个全新的A类对象,和原来的B实例没有关联。调用ToString()时,执行的是A类的ToString方法,所以输出10。
额外补充:如何让泛型方法调用B的Get方法?
如果想让Print方法调用B的Get方法,需要把基类的Get声明为虚方法,派生类用override重写:
class A { protected int a; public A(int a) { this.a = a; } public virtual A Get() // 声明为virtual { return this; } public override string ToString() { return $"{a}"; } } class B : A { int b; public B(int a, int b) : base(a) { this.b = b; } public override A Get() // 用override重写 { return this; } public override string ToString() { return $"{a}, {b}"; } }
这样多态机制会生效,Print方法会根据t的实际类型调用对应的Get方法,ToString也会执行对应类型的实现。
内容的提问来源于stack exchange,提问作者Александр Черняк

