C# 通用方法Don';t调用类型为'的方法;T';
假设我有两个班:C# 通用方法Don';t调用类型为'的方法;T';,c#,.net,generics,C#,.net,Generics,假设我有两个班: class a { public void sayGoodbye() { Console.WriteLine("Tschüss"); } public virtual void sayHi() { Console.WriteLine("Servus"); } } class b : a { new public void sayGoodbye() { Console.WriteLine("Bye"); } override public voi
class a
{
public void sayGoodbye() { Console.WriteLine("Tschüss"); }
public virtual void sayHi() { Console.WriteLine("Servus"); }
}
class b : a
{
new public void sayGoodbye() { Console.WriteLine("Bye"); }
override public void sayHi() { Console.WriteLine("Hi"); }
}
如果调用要求从类“a”派生类型“T”的泛型方法:
void call<T>() where T : a
void call(),其中T:a
然后在该方法中,我对类型为“T”的实例调用方法,该方法调用绑定到类型为“a”,就好像该实例被强制转换为“a”一样:
call<b>();
...
void call<T>() where T : a
{
T o = Activator.CreateInstance<T>();
o.sayHi(); // writes "Hi" (virtual method)
o.sayGoodbye(); // writes "Tschüss"
}
call();
...
void call(),其中T:a
{
T o=Activator.CreateInstance();
o、 sayHi();//写入“Hi”(虚拟方法)
o、 saybye();//写“Tschüss”
}
通过使用反射,我能够获得预期的结果:
call<b>();
...
void call<T>() where T : a
{
T o = Activator.CreateInstance<T>();
// Reflections works fine:
typeof(T).GetMethod("sayHi").Invoke(o, null); // writes "Hi"
typeof(T).GetMethod("sayGoodbye").Invoke(o, null); // writes "Bye"
}
interface Ia
{
void sayGoodbye();
void sayHi();
}
...
class a : Ia // 'a' implements 'Ia'
...
call<b>();
...
void call<T>() where T : Ia
{
T o = Activator.CreateInstance<T>();
o.sayHi(); // writes "Hi"
o.sayGoodbye(); // writes "Bye"
}
call();
...
void call(),其中T:a
{
T o=Activator.CreateInstance();
//反射效果很好:
typeof(T).GetMethod(“sayHi”).Invoke(o,null);//写“Hi”
typeof(T).GetMethod(“saybye”).Invoke(o,null);//写“Bye”
}
此外,通过使用类“a”的接口,我获得了预期的结果:
call<b>();
...
void call<T>() where T : a
{
T o = Activator.CreateInstance<T>();
// Reflections works fine:
typeof(T).GetMethod("sayHi").Invoke(o, null); // writes "Hi"
typeof(T).GetMethod("sayGoodbye").Invoke(o, null); // writes "Bye"
}
interface Ia
{
void sayGoodbye();
void sayHi();
}
...
class a : Ia // 'a' implements 'Ia'
...
call<b>();
...
void call<T>() where T : Ia
{
T o = Activator.CreateInstance<T>();
o.sayHi(); // writes "Hi"
o.sayGoodbye(); // writes "Bye"
}
接口Ia
{
虚空说再见();
void sayHi();
}
...
a类:Ia/'a'实现“Ia”
...
call();
...
void call(),其中T:Ia
{
T o=Activator.CreateInstance();
o、 sayHi();//写“Hi”
o、 saybye();//写“再见”
}
等效的非泛型代码也可以正常工作:
call();
...
void call()
{
b o = Activator.CreateInstance<b>();
o.sayHi(); // writes "Hi"
o.sayGoodbye(); // writes "Bye"
}
call();
...
无效调用()
{
b o=Activator.CreateInstance();
o、 sayHi();//写“Hi”
o、 saybye();//写“再见”
}
如果我将泛型约束更改为“b”,也会发生同样的情况:
call<b>();
...
void call<T>() where T : b
{
T o = Activator.CreateInstance<T>();
o.sayHi(); // writes "Hi"
o.sayGoodbye(); // writes "Bye"
}
call();
...
void call(),其中T:b
{
T o=Activator.CreateInstance();
o、 sayHi();//写“Hi”
o、 saybye();//写“再见”
}
编译器似乎正在生成对约束中指定的基类的方法调用,所以我想我理解发生了什么,但这不是我所期望的。这真的是正确的结果吗?说再见不是虚拟的 编译器只“知道”T是类型a。它会在一天内打电话说再见
在类型b上,您重新定义了saybye,但编译器不知道类型b。它不可能知道a的所有衍生物。您可以通过使saybaye虚拟化来告诉编译器saybaye可能被重写。这将导致编译器以一种特殊的方式调用saybye。正如您所看到的,方法隐藏与多态性不同。只要从B向下转换到A,就可以调用方法的A版本 对于泛型方法,当T被限制为类型a时,编译器无法知道它是否可能是其他类型,因此它使用隐藏方法而不是在a上定义的方法是非常意外的。方法隐藏是为了方便或互操作性;它与替代行为无关;为此,您需要多态性和虚拟方法
编辑:
我认为这里的基本混淆实际上是泛型和C++样式模板。在.NET中,泛型类型只有一个代码基。创建专用泛型类型不涉及为特定类型发出新代码。这与C++不同,其中模板专门化实际上涉及创建和编译附加代码,以便它将对指定的类型真正地专业化。 < P> <代码>新< /Cord>关键字是C语言中的一种黑客攻击。它与多态性相矛盾,因为调用的方法取决于C++引用的类型。 泛型是一种通用类型:编译器只输出一个泛型类(或方法)。泛型不能在编译时使用提供的实际类型替换
t
,这需要为每个类型参数编译一个单独的泛型实例,但可以通过使用空“空格”生成一个类型来工作。在泛型类型中,编译器接着在不知道特定参数类型的情况下解决这些“空白”上的操作。因此,它只使用它已经拥有的信息;也就是说,除了全局事实之外,您还提供了约束,例如“一切都是对象”
所以当你说
void call<T>() where T : a {
T o = Activator.CreateInstance<T>();
o.sayGoodbye();//nonvirtual
现在,调用非虚方法会忽略变量的运行时类型。正如预期的那样,您会看到调用了a.saybye()
比较而言,C++模板确实以你期望的方式工作——它们实际上在编译时扩展模板,而不是用“空白”来定义单个定义,因此特定模板实例可以使用只能用于该专门化的方法。事实上,即使在运行时,CLR也避免实际实例化模板的特定实例:因为所有调用对于特定类都是虚拟的(不需要显式实例化)或非虚拟的(同样,实例化没有意义),CLR可以使用相同的字节(甚至可能是相同的x86代码)来覆盖多种类型。这并不总是可能的(例如,对于值类型),但是对于节省内存和JIT时间的引用类型
还有两件事。。。 首先,您的调用方法使用Activator
——这不是必需的;您可以使用一个例外约束new()
,它执行相同的操作,但用于编译时检查:
void call<T>() where T : a, new() {
T o = new T();
o.sayGoodbye();
void call(),其中T:a,new(){
T o=新的T();
o、 说再见();
尝试编译call()
将在编译时失败,并显示人类可读的消息
其次,如果泛型仅仅是空白的话,那么它似乎在很大程度上是毫无意义的——毕竟,为什么不一直简单地处理a
类型的变量呢?好吧,虽然在编译时你不能依赖于a
的子类在泛型方法中可能具有的任何细节,但你仍然强制所有t
都是of同一子类,它特别允许使用众所周知的容器,如List
——其中即使List
永远不能依赖int
内部,对于List
的用户来说,仍然可以方便地