受限扩展方法会导致调用模糊

3

假设我们有两个不相关的接口

public interface IFirst
{

}

public interface ISecond
{

}

同时,针对每个接口进行约束的同名扩展方法。

public static class IFirstExtensions
{
    public static void DoIt<TFirst>(this TFirst model)
        where TFirst : IFirst
    {

    }
}

public static class ISecondExtensions
{
    public static void DoIt<TSecond>(this TSecond model)
        where TSecond : ISecond
    {

    }
}

当我尝试使用IFirst实例时:
IFirst first = ...;
first.DoIt();

然后我遇到了一个错误:

"CS0121 调用 'IFirstExtensions.DoIt(TFirst)' 和 'ISecondExtensions.DoIt(TSecond)' 方法或属性时不明确。"

这很奇怪。看起来这两个方法在这个作用域都是可见的。但是如果我将它们重命名,例如:

public static class IFirstExtensions
{
    public static void DoFirst<TFirst>(this TFirst model)
        where TFirst : IFirst
    {

    }
}

public static class ISecondExtensions
{
    public static void DoSecond<TSecond>(this TSecond model)
        where TSecond : ISecond
    {

    }
}

约束起作用,第二种方法不可见,导致编译错误:

IFirst first = ...;
first.DoSecond();

看起来,当检测歧义时和调用时,约束满足条件的工作方式不同。但在C#规范中,我只找到了一个与此主题严格相关的章节,描述约束如何工作。这是编译器的漏洞还是我错过了什么?

1个回答

8

通用约束不是方法签名的一部分。在重载决议方面,这两个方法完全相同,因此会生成一个模棱两可的调用错误。

具体来说,方法的签名由其名称、类型参数的数量以及其形式参数的数量、修饰符和类型组成(C# 5.0规范,10.6方法)。

为了进行签名比较,任何类型参数约束子句都将被忽略,方法类型参数的名称也将被忽略,但泛型类型参数的数量是相关的(ECMA-334,25.6.1泛型方法签名)。

更明确地说,在重载决议方面,这两个扩展方法仅仅是:

public static void DoFirst<T>(this T model)

此外,请注意该问题并非仅与扩展方法有关。请考虑以下示例,其中在同一类中声明了具有相同签名但不同约束的两个通用方法:
class Foo
{
    void Bar<T>(Blah blah) where T: Frob { }
    void Bar<T>(Blah blah) where T: Blob { } //CS0111 error
}

您将会得到一个编译时错误:

CS0111 类型“Foo”已经定义了一个与参数类型相同的成员“Bar”。


Foo 类与我提到的不相关类的用法不同。我希望看到我的版本被封闭为这样的签名:Bar(Frob blah),Bar(Blob blah)。这就是为什么它们编译时没有出现 CS0111 错误。如果您查看 MethodInfo->Signature->Arguments[0],您会看到在 ImplementedInterfaces 属性中具有不同值和类型的参数。因此,在运行时签名是不同的,并且包含约束条件。 - Stan
但是你说得对。我在规范中错过了重点,即通用约束在签名中被官方忽略。这太荒谬了。如果我扩展IFirst/ISecond接口-没问题。如果我把这些合同作为约束条件-就不行。 - Stan
@stan 不,签名是相同的,错误是不同的,因为这些方法在不同的类中定义。在解析调用时,两种方法都在候选集中,这就是完全相同的签名问题出现的时刻,调用被标记为模糊不清。之前没有给出错误是因为当您将其作为常规静态方法调用时,您始终可以明确定义实现扩展方法的静态类。这也适用于实现具有相同方法签名和两个不同约束的两个接口的类的情况。 - InBetween
@Stan,你可以通过使用一个带有所需接口类型的变量来调用该方法,以消除歧义。 - InBetween
@Stan 当这两个方法被定义在同一个类中时,你永远无法消除调用的歧义,因此错误会立即出现。 - InBetween

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接