【问题标题】:VS 2015 Method Overload Resolution BehaviorVS 2015 方法重载解析行为
【发布时间】:2015-08-07 15:18:55
【问题描述】:

我很难理解为什么在这个简单的示例中没有选择正确的重载。查看 7.5.3.2 Better 函数成员下的 C# 5 spec,似乎它应该能够选择非泛型重载,但 object 参数似乎以某种我不理解的方式影响了决策。我遇到的问题是,如果不将参数转换为object,我就无法调用Foo(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>();
    }
}

【问题讨论】:

  • 我认为编译时错误是因为 Foo("foobar") 正在为您创建一个 A (通用),然后您尝试将其放入 A 类型的变量(非通用的)。就编译器而言,它们是两种完全不同的类型。如果您要使用: var x = Foo("foobar");它可能会编译
  • 你是对的 var x = Foo("foobar");编译并运行。它还调用泛型重载。如果不显式转换为对象或传递对象,是否无法调用非泛型重载?

标签: c# generics .net-4.5 visual-studio-2015 overloading


【解决方案1】:

A x = Foo("foobar");

C# 选择泛型方法,因为它比非泛型方法更具体并且不需要转换。实际上,C# 编译器创建了 Foo 方法的副本,并将泛型类型参数 T 替换为具体类型 string。重载决议在编译时执行。在运行时,将调用带有字符串参数的方法。运行时不会产生一般开销。

请注意,只有赋值右侧的表达式才会考虑解析。更具体地说,C# 着眼于方法签名,即方法参数。方法的返回类型属于它的签名。

泛型方法返回一个A&lt;T&gt;,但是由于A&lt;T&gt;不是从A派生的,所以方法Foo&lt;T&gt;()A&lt;T&gt;类型的结果不能赋值给x类型A。动态示例也是如此:没有从A&lt;T&gt;A 的有效转换。由于重载解析是在编译时完成的,动态无法解决您的问题。动力学在运行时完成它们的“工作”(即绑定)。

同样,确定使用哪个重载的方法不是您期望的结果,而是传递给该方法的(静态)参数。


另一个有助于澄清事实的例子:

var x = Foo(5);
var y = Foo("hello");

在编译时,C# 会创建 Foo 方法的两个副本!一种使用int,另一种使用string 代替泛型类型参数T。在运行时不发生转换;甚至不是拳击(不像 Java 会将 int 包装到一个对象中)。

【讨论】:

  • 谢谢。我知道是参数决定了选择的重载。我挂断的部分是规范中的第 7.5.3.2 节以及它如何决定选择哪种方法。我怀疑这是我不理解关于论点转换的第二段。字符串到对象不能是字符串到泛型的更好转换——就像你指出的那样。
  • 没有从字符串到泛型的转换。在一般情况下,字符串用作字符串并且不涉及转换,因为A&lt;T&gt; Foo&lt;T&gt;(T x) 然后编译为A&lt;string&gt; Foo(string x)。 IE。在调用Foo("hello") 中,一个字符串值被传递给一个字符串参数。在非泛型变体中,字符串值被传递给object 参数。
  • Foo&lt;T&gt; 中设置断点,当您将鼠标悬停在x 上时,您会看到T 已被string 替换。
猜你喜欢
  • 2016-06-21
  • 1970-01-01
  • 2019-09-17
  • 1970-01-01
  • 1970-01-01
  • 2011-10-11
  • 2011-04-10
  • 1970-01-01
相关资源
最近更新 更多