5

我在 Internet 和 stackoverflow 上进行了一些基本搜索,当涉及通用版本方法和非通用版本方法时,我看到了很多关于重载解决方案的讨论。我知道重载解决是在编译时完成的 - 因此,如果我有这个代码:

public class A<T>
{
    public void DoStuff(T value)
    {
         InternalDoStuff(value);
    }

    protected void InternalDoStuff(int value)
    {
         Console.WriteLine("Non-generic version");
    }

    protected void InternalDoStuff(T value)
    {
         Console.WriteLine("Generic version");
    }

}

public class Test
{
    static void Main (string [] args)
    {
         A<int> a = new A<int> ();
         a.DoStuff(100);
    }
}

输出将是“通用版本”,因为“InternalDoStuff”的分辨率已被编译器整理出来,编译器看到的是“InternalDoStuff 在 DoStuff 中使用 T 类型参数调用”。

但是我不知道这是否会有所作为:

public class B : A <int> 
{

}

public class Test
{
    static void Main (string [] args)
    {
         B b = new B ();
         b.DoStuff(100);
    }
}

现在我可以说编译器有足够的信息来确定“B 是 A 的特定版本”,因此调用 InternalDoStuff 的非通用版本吗?

是否有任何一般原则来分析这种重载决议?

4

4 回答 4

4

第二种方法与第一种方法在任何意义上都没有不同。

从 A 派生类 B 绝不会更改为类 A 生成的 IL 代码。B 只是继承这些方法。

如果您查看 A 类的 IL 代码,您可以看到它编译为调用通用版本而不是非通用版本 -

.method public hidebysig instance void DoStuff(!T 'value') cil managed
{
    .maxstack 8
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldarg.1 
    L_0003: call instance void
            ConsoleApplication1.A`1<!T>::InternalDoStuff(!0) <-- Generic version
    L_0008: nop 
    L_0009: ret 
}

来自 Jon Skeet 的文章-

提醒一下,当您有两个名称相同但签名不同的方法时,会发生重载。在编译时,编译器会根据参数的编译时类型和方法调用的目标来确定要调用哪一个。(我假设您在这里没有使用动态,这会使事情变得有些复杂。)

正如他所提到的,使用动态延迟解析直到运行时。这段代码将为您的两种方法调用非通用版本方法 -

public void DoStuff(T value)
{
   dynamic dynamicValue = value;
   InternalDoStuff(dynamicValue);
} 

请参阅Jon SkeetEric Lippert在此处详细描述的答案。

于 2013-08-20T16:31:27.490 回答
2

在 C++ 中,程序在执行期间可能创建的每种类型都必须在编译时生成。虽然 C++ 模板看起来像 C# 泛型,但它们的行为更类似于替换宏。因为编译器会分别生成可能由泛型类型替换产生的每个类,所以它可以为每个类单独评估重载决议等内容。C# 泛型不是那样工作的。

C#代码的编译分为两个阶段。第一阶段在构建时完成。处理该阶段的编译器获取源代码并将其转换为“通用中间语言”形式(相同的 CIL 形式用于 VB.NET、F# 等——因此得名)。源代码中的每个通用类定义(例如List<T>)都会产生一个 CIL 形式的类定义。在生成 CIL 之前,编译器会做出关于将在何处应用哪些函数重载的所有决定。

稍后,当程序运行时,公共语言运行时不会为程序可能使用的所有类生成代码,而是将每个类的代码生成推迟到第一次实际使用时。在这一步中,类似 a 的东西会从 a或 aList<int>生成不同的机器码。程序想要使用的可能类型的集合不需要限制。在 C# 中可以合法地拥有一个方法,给定一个 generic 参数,它会调用一个带有 a 的泛型方法(如果给定 a将传递 a ,如果给定它将传递 a等)。该程序可能会因List<string>List<KeyValuePair<Dictionary<int,string>, Tuple<Cat,Dog>>>TList<T>List<T>List<List<T>>List<List<List<T>>>OutOfMemoryException如果事情嵌套得太深,或者类似的问题,但与 C++ 不同的是,程序可以生成的类型数量在编译时不需要限制;只有当程序试图实际使用太多不同的类型时才会有问题。

CLR 能够在生成代码时进行某些类型的通用替换,但它不处理重载决议(如前所述,这是在 C# 到 CIL 的转换步骤中处理的)。虽然在 CLR 中进行重载解析等可能有一些优势,但它也会使 CLR 更加复杂。如果一个特别棘手的重载解决问题需要 1/4 秒,那么编译 C# 代码可能不会有问题,但是对于此类事情将运行时停止 1/4 秒是不可取的。

于 2013-08-20T19:14:41.340 回答
1

在此处输入图像描述这将调用“非通用”版本:

public class A<T>
{
    public virtual void DoStuff(T value)
    {
        InternalDoStuff(value);
    }

    protected void InternalDoStuff(int value)
    {
        Console.WriteLine("Non-generic version");
    }

    protected void InternalDoStuff(T value)
    {
        Console.WriteLine("Generic version");
    }

}
public class B : A<int>
{
    public override void DoStuff(int value)
    {
        InternalDoStuff(value);
    }
}
于 2013-08-20T16:04:13.273 回答
1

InternalDoStuff对inside of的调用在编译DoStuff时被绑定。A<T>调用来自 的实例这一事实B不会以任何方式影响重载解决方案。

编译时DoStuff有2个InternalDoStuff成员可供选择

  • InternalDoStuff(T value)
  • InternalDoStuff(int value)

DoStuff方法正在传递一个T值,因此重载int无法工作。因此只有一个适用的成员InternalDoStuff(T),编译器选择这个。

于 2013-08-20T16:20:58.033 回答