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)不能有这两个重载方法,这太可惜了。
是否有一个共同的规则来解释这种行为?是否有可能使其工作(除了给方法不同的名称)?
这只是一个类型推断的问题,当与重载解析相结合时,它并不完全对您有利。只需显式指定类型参数就可以很容易地修复—不需要强制转换:
DoTheFoo<Bar>(foo);
通常我对采用不同参数类型的重载感到紧张。如果给方法起不同的名字,代码通常会更简单。除此之外,您的阅读器不需要在类型推断的同时尝试执行重载解析…
编辑:我认为问题是排序是这样的:
- 类型推断应用于两个方法而不验证约束 -因此对于第一个方法,我们得到
T = Foo<Bar>
,对于第二个方法,我们得到T = Bar
。在这一点上,这两种方法都适用。 - 执行过载解析,它决定第一个方法是最具体的方法。
- 只有在执行了重载解析之后才检查
T
的约束-并且失败了,因为没有从Bar
到IFoo
的引用转换。
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的恒等转换-…
标识转换是从类型到自身的转换,对于第一个重载,是,但是对于第二个重载,不是。