C#方法重载和通用接口

Ste*_*ger 8 c# generics overloading

我对项目中遇到的问题感到困惑.我试图简化它以重现效果:

interface IBar { }

class Bar : IBar {}

interface IFoo<T> where T : IBar { }

class Foo<T> : IFoo<T> where T : IBar { }


class Class1
{
    public void DoTheFoo<T>(T bar) where T : IBar
    {}

    public void DoTheFoo<T>(IFoo<T> foo) where T : IBar
    {}


    public void Test()
    {
        var bar = new Bar();
        var foo = new Foo<Bar>();

        DoTheFoo(bar); // works

        DoTheFoo<Bar>(foo); // works
        DoTheFoo((IFoo<Bar>)foo); // works
        DoTheFoo(foo); // complains
    }
}
Run Code Online (Sandbox Code Playgroud)

对我来说这看起来很好,但编译器在最后一次调用时抱怨,因为它试图DoTheFoo<T>(T bar)而不是DoTheFoo<T>(IFoo<T> foo)抱怨参数类型不适合.

  • 当我删除该方法时DoTheFoo<T>(T bar),最后一次调用有效!
  • 当我改变它DoTheFoo<T>(Foo<T> foo),它的工作原理,但我无法使用它

在我们当前的代码中解决这个问题并不困难.但它是a)奇怪而且b)太糟糕了,我们不能拥有这两个重载方法.

是否存在解释此行为的通用规则?是否可以使它工作(除了给方法不同的名称)?

Jon*_*eet 7

当与重载决策结合使用时,这只是类型推断的问题,而不是对你有利.只需明确指定类型参数即可轻松修复 - 无需强制转换:

DoTheFoo<Bar>(foo);
Run Code Online (Sandbox Code Playgroud)

通常我对过载采取相当不同的参数类型感到紧张.如果只是给方法赋予不同的名称,代码通常会更简单.除了其他任何东西,那么你的读者不需要尝试在类型推断的同时执行重载解析...

编辑:我认为问题是订购工作如下:

  • 找到了这两种方法
  • 类型推断应用于两种方法而不验证约束 - 因此对于第一种方法,我们获得T = Foo<Bar>并获得第二种方法T = Bar.这两种方法都适用.
  • 执行过载分辨率,确定第一种方法是最具体的方法.
  • 只有已进行重载决议是在约束T检查-和失败,因为有一个从没有引用转换BarIFoo.

一篇关于为什么语言是这样设计Eric Lippert博客文章,我写的一篇关于它博客文章,以及我写的关于一般的重载的文章.他们每个人可能会或可能不会帮助:)

编辑:抛开类型推断暂时,第一种方法更具体的原因是,在一种情况下,我们正在转换Foo<Bar>Foo<Bar>,而在另一种情况下,我们正在转换Foo<Bar>IFoo<Bar>.根据C#5规范的7.5.3.3节:

给定从表达式E转换为类型T1的隐式转换C1,以及从表达式E转换为类型T2的隐式转换C2,如果以下至少一个成立,则C1是比C2更好的转换: - E具有类型S并且存在从S到T1而不是从S到T2的身份转换 - ...

身份转换是从类型到自身,这第一次重载的情况,但不适用于第二次.所以第一次转换更好.

  • @StefanSteinegger:"IFoo <T>"是否比"T"更具特异性,不是相关的问题.方法类型推断*成功*,然后在成功之后问题是"哪个更好*转换*:`foo`转换为`IFoo <Bar>`via*reference*conversion或`foo`转换为`Foo <Bar >`via*identity*conversion?很明显,身份转换获胜,因此重载决策选择了`Foo <Bar>`方法的最佳方法.只有这样才能检查约束,我们确定`Foo <Bar>`不符合`IBar`约束. (2认同)