Visual Studio 2015的方法重载解析行为

4
我在这个简单的例子中很难理解为什么没有选择正确的重载。在查看C# 5规范中的7.5.3.2 Better function member后,似乎应该能够选择非泛型重载,但是我不理解object参数在某种程度上如何影响决策。我的问题是,我无法调用非泛型版本的Foo(object)而不将参数强制转换为object。根据错误提示,似乎不支持这样做,我希望有人能解释一下原因。
public class A
{
    public string Type { get { return "non-generic"; } }
}

public class A<T>
{
    public string Type { get { return "generic"; } }
}

class Program
{
    // (In reality only one of the variants below can be uncommented.)
    static void Main(string[] args)
    {
        // This works fine and calls the generic overload as expected
        A<string> x = Foo<string>("foobar");

        // This results in a compile time error
        // CS0029: Cannot implicitly convert type 'A<string>' to 'A'
        A x = Foo("foobar");

        // This works, but ends up calling the generic overload
        var x = Foo("foobar");

        // This works fine and calls the non-generic overload as expected
        object a = "foobar";
        var x = Foo(a);

        // This works fine and calls the non-generic overload as expected
        A x = Foo((object)"foobar");

        // By using dynamic we're able to get rid of the compile-time error, but get a
        // runtime exception.
        // RuntimeBinderException: Cannot implicitly convert type 'A<string>' to 'A'
        A x = Foo((dynamic)"foobar");

        Console.WriteLine(x.Type);
        Console.ReadLine();
    }

    private static A Foo(object x)
    {
        return new A();
    }

    private static A<T> Foo<T>(T x)
    {
        return new A<T>();
    }
}

1
我认为编译时出错是因为Foo("foobar")为您创建了一个A<string>(泛型),然后您尝试将其放入类型为A(非泛型)的变量中。就编译器而言,它们是两种完全不同的类型。如果您使用:var x = Foo("foobar"); 它可能会编译通过。 - Russ Penska
你说得对,var x = Foo("foobar"); 可以编译和运行。它也调用了泛型重载。难道没有不显式转换为对象或传递对象的方法来调用非泛型重载吗? - Mitch
1个回答

2

In

A x = Foo("foobar");

C#选择泛型方法,因为它比非泛型方法更具体且不需要转换。实际上,C#编译器会创建Foo方法的副本,并将泛型类型参数T替换为具体类型string。重载分辨率在编译时执行。运行时将调用具有string参数的方法。运行时不会创建任何泛型开销。
请注意,仅考虑赋值右侧的表达式进行分辨率。具体来说,C#查看方法签名,即方法参数。方法返回类型不属于其签名。
泛型方法返回A,但由于A不派生自A,因此无法将方法Foo()的A类型结果分配给类型为A的x。对于动态示例也是如此:从A到A没有有效的转换。由于重载分辨率在编译时完成,因此动态无法解决您的问题。动态在运行时执行其“工作”(即绑定)。
再次强调,确定使用哪个重载不是您期望从方法获得的结果,而是传递给该方法的(静态)参数。
另一个有助于澄清事实的例子:
var x = Foo(5);
var y = Foo("hello");

在编译时,C# 会创建两个 Foo 方法的副本!一个是使用 int 替换泛型类型参数 T,另一个是使用 string。在运行时,不会发生任何转换,甚至不会进行装箱(与 Java 不同,Java 会将 int 包装成对象)。


谢谢。我明白决定选择重载的是参数。我卡在的部分是规格文件中的第7.5.3.2节,以及它如何决定选择哪种方法。我猜测是因为我不理解第二段话,其中讨论了参数转换。字符串到对象的转换不能比字符串到泛型更好 - 就像你指出的那样。 - Mitch
字符串不能转换为通用类型。在通用情况下,字符串被用作字符串,没有任何转换,因为 A<T> Foo<T>(T x) 被编译成 A<string> Foo(string x)。也就是说,在调用 Foo("hello") 时,将一个字符串值传递给一个字符串参数。而在非通用变体中,则将一个字符串值传递给一个object 参数。 - Olivier Jacot-Descombes
Foo<T> 中设置一个断点,当你悬停在 x 上时,你会发现 T 已经被替换为 string - Olivier Jacot-Descombes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接