通用方法不要调用类型'T'的方法

Pau*_*aul 5 .net c# 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 void sayHi() { Console.WriteLine("Hi"); }
}
Run Code Online (Sandbox Code Playgroud)

如果我调用一个通用方法,要求类型'T'从类'a'派生:

void call<T>() where T : a
Run Code Online (Sandbox Code Playgroud)

然后在该方法中我调用类型为'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"
}
Run Code Online (Sandbox Code Playgroud)

通过使用反射,我能够得到预期的结果:

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"
}
Run Code Online (Sandbox Code Playgroud)

此外,通过使用类'a'的接口,我得到了预期的结果:

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"
}
Run Code Online (Sandbox Code Playgroud)

等效的非通用代码也可以正常工作:

call();
...
void call()
{
    b o = Activator.CreateInstance<b>();
    o.sayHi(); // writes "Hi"
    o.sayGoodbye(); // writes "Bye"
}
Run Code Online (Sandbox Code Playgroud)

如果我将通用约束更改为'b',则同样如此:

call<b>();
...
void call<T>() where T : b
{
    T o = Activator.CreateInstance<T>();
    o.sayHi(); // writes "Hi"
    o.sayGoodbye(); // writes "Bye"
}
Run Code Online (Sandbox Code Playgroud)

似乎编译器正在生成对约束中指定的基类的方法调用,所以我想我明白发生了什么,但这不是我所期望的.这真的是正确的结果吗?

Eam*_*nne 7

泛型不是C++模板

泛型是一种通用类型:编译器只输出一个泛型类(或方法).泛型不能通过编译时替换所T提供的实际类型来工作,这需要为每个类型参数编译一个单独的通用实例,而是通过使一个类型为空"空白"来工作.在泛型类型中,编译器然后在不知道特定参数类型的情况下继续解析对那些"空白"的动作.因此它使用了它已有的唯一信息; 也就是说,除了全局事实之外,你提供的约束,例如一切都是对象.

所以,当你说...

void call<T>() where T : a {
    T o = Activator.CreateInstance<T>();
    o.sayGoodbye();//nonvirtual
Run Code Online (Sandbox Code Playgroud)

......然后键入To在编译时相关-运行时类型可以是更具体.在编译时,T它本质上是一个同义词a- 毕竟,这是所有编译器都知道的T!因此,请考虑以下完全等效的代码:

void call<T>() where T : a {
    a o = Activator.CreateInstance<T>();
    o.sayGoodbye();//nonvirtual
Run Code Online (Sandbox Code Playgroud)

现在,调用非虚方法会忽略变量的运行时类型.正如预期的那样,你会看到它a.sayGoodbye()被称为.

相比之下,C++模板工作,你希望的方式-他们实际上在编译时展开模板,而不是使一个单一的定义为"空白",因此特定的模板实例可以使用只适用于专业化的方法.事实上,即使在运行时,CLR也避免实际实例化模板的特定实例:因为所有调用都是虚拟的(不需要显式实例化)或非虚拟特定类(同样,实例化中没有任何意义) ),CLR可以使用相同的字节 - 甚至可能使用相同的x86代码 - 来覆盖多种类型.这并不总是可行的(例如对于值类型),但对于节省内存和JIT时间的引用类型.

还有两件事......

首先,你的呼叫方法使用Activator- 这是不必要的; new()你可以使用一个特殊的约束来做同样的事情但是使用编译时检查:

void call<T>() where T : a, new() {
    T o = new T();
    o.sayGoodbye();
Run Code Online (Sandbox Code Playgroud)

尝试编译call<TypeWithoutDefaultConstructor>()将在编译时使用人类可读消息失败.

其次,如果它们只是空白,那么仿制药看起来似乎毫无意义 - 毕竟,为什么不一直只是简单地处理a变量?好吧,虽然在编译时,你不能依赖于任何细节的子类a可能有通用的方法,你还在执行,所有T都是的相同的子类,允许特别是良好的使用已知的容器,例如List<int>- 即使List<>永远不能依靠int内部构件,对于List<>它的用户来说仍然很方便避免铸造(以及相关的性能和正确性问题).

泛型也允许比普通参数更丰富的约束:例如,您通常不能编写一个方法,要求其参数既是aIDisposable- 的子类型- 但您可以对类型参数有几个约束,并声明一个参数是那种泛型.

最后,泛型可能存在运行时差异.您的来电Activator.CreateInstance<T>()就是一个完美的例子,因为是简单的表达typeof(T)if(myvar is T)....所以,尽管在某种意义上编译器"认为"的返回类型Activator.CreateInstance<T>()a在编译时,在运行时的对象将是类型T.