通用扩展方法歧义

我定义了两个接口:

// 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(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(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

为什么会这样?

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

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

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

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

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

为方便起见,语言规范。

7.5.5.2扩展方法调用

在其中一个表单的方法调用(第7.5.5.1节)中

expr。 标识符()

expr。 标识符(args)

expr。 标识符()

expr。 标识符(args)

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

C 。 标识符(expr)

C 。 identifier(expr,args)

C 。 标识符(expr)

C 。 标识符(expr,args)

对C的搜索过程如下:

  • 从最近的封闭命名空间声明开始,继续每个封闭的命名空间声明,并以包含的编译单元结束,连续尝试查找一组候选扩展方法:
    • 如果给定的命名空间或编译单元直接包含非generics类型声明Ci,其扩展方法Mj具有名称标识符,并且对于上面所需的静态方法调用是可访问和适用的,那么这些扩展方法的集合是候选集合。
    • 如果在给定命名空间或编译单元中使用命名空间指令导入的命名空间直接包含非generics类型声明Ci,其扩展方法Mj具有名称标识符,并且对于上面所需的静态方法调用是可访问和适用的,那么那些扩展方法是候选集。
  • 如果在任何封闭的名称空间声明或编译单元中找不到候选集,则会发生编译时错误。
  • 否则,重载决策应用于候选集,如(第7.4.3节)中所述。 如果找不到单个最佳方法,则会发生编译时错误。
  • C是将最佳方法声明为扩展方法的类型。 使用C作为目标,然后将方法调用作为静态方法调用进行处理(第7.4.4节)。 前面的规则意味着实例方法优先于扩展方法,内部命名空间声明中可用的扩展方法优先于外部命名空间声明中可用的扩展方法,并且直接在命名空间中声明的扩展方法优先于导入到该命名空间中的扩展方法。带有using namespace指令的命名空间

我刚刚使用默认参数的技巧找到了一种在.NET 4.5中运行的好奇方法。

 /// Simple base class. Can also be an interface, for example. public abstract class MyBase1 { } /// Simple base class. Can also be an interface, for example. public abstract class MyBase2 { } /// Concrete class 1. public class MyClass1 : MyBase1 { } /// Concrete class 2. public class MyClass2 : MyBase2 { } /// Special magic class that can be used to differentiate generic extension methods. public class Magic where TInherited : TBase { private Magic() { } } // Extensions public static class Extensions { // Rainbows and pink unicorns happens here. public static T Test(this T t, Magic x = null) where T : MyBase1 { Console.Write("1:" + t.ToString() + " "); return t; } // More magic, other pink unicorns and rainbows. public static T Test(this T t, Magic 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()); } } 

我很想知道这是否适用于MONO编译器(Mcs)有人想尝试吗? 🙂

之所以发生这种情况,是因为在评估两种方法是否具有相同的签名时不考虑通用约束。 您正在有效地定义两个相同的添加方法。

试试这种方法:

 // VectorExtensions.cs public static T Add(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(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的文章: 约束不是签名的一部分 。

为什么会这样?

重载IVector不考虑约束( IVectorIMatrix ),因为这是扩展方法之间唯一不同的方法,它们都是不明确的 – 它们具有相同的名称和相同的通用参数。

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

 // IVector.cs public interface IVector where T : IVector { int Size { get; } float this[int index] { get; set; } } // IMatrix.cs public interface IMatrix where T : IMatrix { int Size { get; } float this[int row, int column] { get; set; } } // VectorExtensions.cs public T Add(this IVector 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(this IMatrix 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; } 

它工作得很漂亮。 Hooray for CRTP :D