8

我对我们项目中的一个问题感到困惑。我试图简化它以重现效果:

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
    }
}

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

  • 当我删除方法DoTheFoo<T>(T bar)时,最后一次调用有效!
  • 当我将其更改为 时DoTheFoo<T>(Foo<T> foo),它可以工作,但我不能使用它

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

是否有解释这种行为的通用规则?是否有可能使它工作(除了给方法不同的名称)?

4

1 回答 1

7

当与重载解析结合使用时,这只是类型推断的问题,对您不利。只需明确指定类型参数即可轻松修复 - 不需要强制转换:

DoTheFoo<Bar>(foo);

通常我对采用不同参数类型的重载感到紧张。如果您只是为方法提供不同的名称,通常代码会变得更简单。除此之外,您的读者不需要在类型推断的同时尝试执行重载解析......

编辑:我相信问题是排序是这样的:

  • 两种方法都找到了
  • 类型推断适用于这两种方法而不验证约束- 所以对于第一种方法,我们得到T = Foo<Bar>,而对于第二种方法,我们得到T = Bar。这两种方法都适用于这一点。
  • 执行重载解析,它决定第一种方法是最具体的方法。
  • 只有执行重载决议之后,才会对已检查的约束T- 并且由于没有从Barto的引用转换而失败IFoo

有一篇Eric Lippert 博客文章介绍了为什么要以这种方式设计语言,还有我写的一篇关于它的博客文章,还有一篇关于一般重载的文章。他们每个人都可能有帮助,也可能没有帮助:)

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

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

标识转换是从一个类型到自身,这第一个重载的情况,但不是第二个。所以第一次转换更好。

于 2013-03-26T14:07:22.410 回答