泛型扩展方法歧义

本文关键字:歧义 方法 扩展 泛型 | 更新日期: 2023-09-27 17:55:29

>我定义了两个接口:

// IVector.cs
public interface IVector
{
    int Size { get; }
    float this[int index] { get; set; }
}
// IMatrix.cs
public interface IMatrix
{
    int Size { get; }
    float this[int row, int column] { get; set; }
}

以及这些接口的扩展方法

// VectorExtensions.cs
public static T Add<T>(this T vector, T value) where T : struct, IVector
{
    var output = default(T);
    for (int i = 0; i < output.Size; i++)
        output[i] = vector[i] + value[i];
    return output;
}
// MatrixExtensions.cs
public static T Add<T>(this T matrix, T value) where T : struct, IMatrix
{
    var output = default(T);
    for (int i = 0; i < output.Size; i++)
        for (int j = 0; j < output.Size; j++)
            output[i, j] = vector[i, j] + value[i, j];
    return output;
}

所有类型都位于同一命名空间中。

出于某种原因,当对从 IVector 派生的内容调用 Add() 时,编译器无法确定是使用 MatrixExtensions 类还是 VectorExtensions 类中的定义。 将其中一个扩展类移动到不同的命名空间会停止错误...但我有点希望它们在同一个命名空间中:D

为什么会这样?

编辑:(我不敢相信我忘了添加这个)
我应该怎么做才能解决这个问题?

泛型扩展方法歧义

我刚刚找到了一种奇怪的方法,它使用带有默认参数的技巧在 .NET 4.5 中工作。

/// <summary>Simple base class. Can also be an interface, for example.</summary>
public abstract class MyBase1
{
}
/// <summary>Simple base class. Can also be an interface, for example.</summary>
public abstract class MyBase2
{
}
/// <summary>Concrete class 1.</summary>
public class MyClass1 :
    MyBase1
{
}
/// <summary>Concrete class 2.</summary>
public class MyClass2 :
    MyBase2
{
}
/// <summary>Special magic class that can be used to differentiate generic extension methods.</summary>
public class Magic<TBase, TInherited>
    where TInherited : TBase
{
    private Magic()
    {
    }
}
// Extensions
public static class Extensions
{
    // Rainbows and pink unicorns happens here.
    public static T Test<T>(this T t, Magic<MyBase1, T> x = null)
        where T : MyBase1
    {
        Console.Write("1:" + t.ToString() + " ");
        return t;
    }
    // More magic, other pink unicorns and rainbows.
    public static T Test<T>(this T t, Magic<MyBase2, T> x = null)
        where T : MyBase2
    {
        Console.Write("2:" + t.ToString() + " ");
        return t;
    }
}
class Program
{
    static void Main(string[] args)
    {
        MyClass1 t1 = new MyClass1();
        MyClass2 t2 = new MyClass2();
        MyClass1 t1result = t1.Test();
        Console.WriteLine(t1result.ToString());
        MyClass2 t2result = t2.Test();
        Console.WriteLine(t2result.ToString());
    }
}

我很好奇这是否适用于单声道编译器 (Mcs) 有人想尝试吗? :)

您有两个扩展方法,每个方法具有相同的签名。

// VectorExtensions.cs
public static T Add<T>(this T vector, T value)
// MatrixExtensions.cs 
public static T Add<T>(this T matrix, T value)

是的,您已在代码中提供了约束,但约束不是签名的一部分。因此,您有两种具有相同签名的方法,因此没有一种方法比另一种方法更好,并且您存在歧义问题。

其中一个静态扩展方法类移动到不同的命名空间具有不同结果的原因是,编译器将首先在最近的包含命名空间中查找扩展方法匹配项,然后再向外扩展搜索。(请参阅:C# 语言规范的第 7.5.5.2 节。例如,如果您将MatrixExtensions移动到不同的命名空间中,现在原始命名空间内的扩展方法调用将明确解析为 VectorExtensions 方法,因为它在命名空间方面是最接近的。但是,这并不能完全解决您的问题。因为如果VectorExtensions实现是最接近的扩展方法,您仍然可以IMatrix尝试使用它,因为约束同样不是签名的一部分。

为方便起见,请参阅语言规范。

7.5.5.2 扩展方法调用

在方法调用 (§7.5.5.1) 中 表格之一

表达式 。标识符 ( )

expr . 标识符 ( 参数 )

expr . 标识符 <类型参数> ( )

expr . 标识符 ( args )

如果正常处理 调用发现不适用 方法,尝试处理 作为扩展方法的构造 调用。目标是找到 最佳类型名称 C,以便 对应的静态方法调用 可以发生:

C. 标识符 ( expr )

C . 标识符 ( expr , 参数 )

C. 标识符<类型参数> ( expr )

C. 标识符 <类型参数> ( expr , 参数 )

搜索 C 的过程如下:

  • 从最接近的封闭命名空间声明开始,继续 每个封闭的命名空间声明, 并以包含结尾 编译单元,连续尝试 是为了找到一个候选集 扩展方法:
    • 如果给定的命名空间或编译单元直接包含 非泛型类型声明 Ci 与 具有 名称标识符,可访问和 适用于所需的 上面的静态方法调用,然后 这些扩展方法的集合是 候选集。
    • 如果使用给定命名空间指令导入的命名空间 命名空间或编译单元直接 包含非泛型类型声明 带有扩展方法 Mj 的 Ci 具有 名称标识符,可访问 并适用于 所需的静态方法调用 上面,然后是那些扩展的集合 方法是候选集。
  • 如果在任何封闭命名空间声明中找不到候选集,或者 编译单元,编译时错误 发生。
  • 否则,重载解析将应用于设置为 (§7.4.3) 中所述。如果没有单 找到最佳方法,编译时 发生错误。
  • C 是将最佳方法声明为扩展的类型 方法。使用 C 作为目标, 然后将方法调用作为 静态方法调用 (§7.4.4)。这 前面的规则表示实例 方法优先于扩展 方法,即扩展方法 在内部命名空间中可用 声明优先于 外部可用的扩展方法 命名空间声明,以及 直接在 中声明的扩展方法 命名空间优先于 导入到其中的扩展方法 与 using 命名空间相同的命名空间 命令

发生这种情况是因为在评估两个方法是否具有相同的签名时不考虑泛型约束。 您有效地定义了两个相同的添加方法。

请尝试此方法:

// VectorExtensions.cs
public static T Add<T>(this T vector, IVector value) where T : struct, IVector
{
    var output = default(T);
    for (int i = 0; i < output.Size; i++)
        output[i] = vector[i] + value[i];
    return output;
}
// MatrixExtensions.cs
public static T Add<T>(this T matrix, IMatrix value) where T : struct, IMatrix
{
    var output = default(T);
    for (int i = 0; i < output.Size; i++)
        for (int j = 0; j < output.Size; j++)
            output[i, j] = vector[i, j] + value[i, j];
    return output;
}

约束不是签名的一部分,签名用于确定要使用的重载。在不考虑约束的情况下,您可以看到两个方法具有相同的签名,因此存在歧义。请参阅Eric Lippert的文章:约束不是签名的一部分。

为什么会这样?

重载解析不考虑约束(IVectorIMatrix),因为这是扩展方法之间唯一不同的部分,它们都是模棱两可的 - 它们具有相同的名称和相同的泛型参数。

再次找到了我自己的答案(这有点黑客):

// IVector.cs
public interface IVector<T>
    where T : IVector<T>
{
    int Size { get; }
    float this[int index] { get; set; }
}
// IMatrix.cs
public interface IMatrix<T>
    where T : IMatrix<T>
{
    int Size { get; }
    float this[int row, int column] { get; set; }
}
// VectorExtensions.cs
public T Add<T>(this IVector<T> vector, T value)
    where T : struct, IVector<T>
{
    var output = default(T);
    for (int i = 0; i < output.Size; i++)
        output[i] = vector[i] + value[i];
    return output;
}
// MatrixExtensions.cs
public static T Add<T>(this IMatrix<T> matrix, T value)
    where T : struct, IMatrix<T>
{
    var output = default(T);
    for (int i = 0; i < output.Size; i++)
        for (int j = 0; j < output.Size; j++)
            output[i, j] = vector[i, j] + value[i, j];
    return output;
}

它工作得很好。CRTP :D万岁