通用扩展方法歧义

Yel*_*ika 12 .net c# generics

我定义了两个接口:

// 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; }
}
Run Code Online (Sandbox Code Playgroud)

以及这些接口的扩展方法

// 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;
}
Run Code Online (Sandbox Code Playgroud)

所有类型都在同一名称空间中.

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

为什么会这样?

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

Sal*_*iti 13

我刚刚使用默认参数的技巧找到了一种在.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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 哇,这看起来很疯狂,但实际上在某种程度上是有意义的!我爱它xD (3认同)

Ant*_*ram 12

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

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

// MatrixExtensions.cs 
public static T Add<T>(this T matrix, T value)
Run Code Online (Sandbox Code Playgroud)

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

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

为方便起见,语言规范.

7.5.5.2扩展方法调用

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

expr.标识符()

expr.标识符(args)

expr.标识符<typeargs>()

expr.标识符<typeargs>(args)

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

C .标识符(expr)

C .identifier(expr,args)

C .标识符<typeargs>(expr)

C .标识符<typeargs>(expr,args)

对C的搜索过程如下:

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


Yel*_*ika 2

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

// 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;
}
Run Code Online (Sandbox Code Playgroud)

它工作得很漂亮。CRTP万岁:D