奇怪的通用行为

本文关键字: | 更新日期: 2023-09-27 18:01:52

在下面的代码中,发生了装箱(在Generic.Print中):

using System;
namespace Test
{
    static class Program
    {
        static void Main()
        {
            Generic<string> generic = new Generic<string>("test");
            generic.Print();
        }
    }
    class Generic<Type>
    {
        Type value;
        public Generic(Type value)
        {
            this.value = value;
        }
        public void Print()
        {
            Console.WriteLine(value);
        }
    }
}

ILSpy输出:

.method public hidebysig 
    instance void Print () cil managed 
{
    // Method begins at RVA 0x207d
    // Code size 17 (0x11)
    .maxstack 8
    IL_0000: ldarg.0
    IL_0001: ldfld !0 class Test.Generic`1<!Type>::'value'
    IL_0006: box !Type
    IL_000b: call void [mscorlib]System.Console::WriteLine(object)
    IL_0010: ret
} // end of method Generic`1::Print

它正在装箱并调用Console.WriteLine(object)。我假设它只调用Console.WriteLine(string)。这是怎么回事?

奇怪的通用行为

不,它实际上不会框。来自ECMA-335对box指令的描述:

如果typeTok是引用类型,则box指令返回valobj相同。

换句话说,如果在引用类型上调用box,它是无害的。

(JIT无论如何都会为引用类型和值类型生成单独的本机代码,所以我怀疑这在引用类型版本中最终会被完全删除。)

它为Console.WriteLine选择了object重载,因为这是最适合该调用的重载。

请记住,重载解析是在编译时完成的——编译器必须根据提供的类型信息选择合适的重载,在这种情况下,唯一合适的是object重载。

为了理解这一点,忽略Main方法并考虑Generic类在不同程序集中的情况可能会有所帮助。编译器需要选择一个重载,并且只知道Type可以被强制转换或装箱成object。仅仅因为,实际上在程序集中其他地方使用带有string类型参数的类的代码并不影响Generic的编译方式。

或者考虑如果Console.WriteLine没有接受object的重载会发生什么——在这种情况下,方法根本不会编译(因为Type没有限制,这将使另一个重载适合)。

看起来像是在重用代码。

你可以试着强迫它不要那样做。

public void Print<Type>()
{
   Console.WriteLine(value);
}
相关文章:
  • 没有找到相关文章