c#方法重载和泛型接口

本文关键字:泛型接口 重载 方法 | 更新日期: 2023-09-27 18:06:01

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

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)不能有这两个重载方法,这太可惜了。

是否有一个共同的规则来解释这种行为?是否有可能使其工作(除了给方法不同的名称)?

c#方法重载和泛型接口

这只是一个类型推断的问题,当与重载解析相结合时,它并不完全对您有利。只需显式指定类型参数就可以很容易地修复—不需要强制转换:

DoTheFoo<Bar>(foo);

通常我对采用不同参数类型的重载感到紧张。如果给方法起不同的名字,代码通常会更简单。除此之外,您的阅读器不需要在类型推断的同时尝试执行重载解析…

编辑:我认为问题是排序是这样的:

  • 类型推断应用于两个方法而不验证约束 -因此对于第一个方法,我们得到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的恒等转换-…

标识转换是从类型到自身的转换,对于第一个重载,,但是对于第二个重载,不是