C# 广义扩展法

C# 广义扩展法,c#,.net,generics,C#,.net,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; } } 以及这些接口的扩展方法 // V

我定义了两个接口:

// 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;
}
//VectorExtensions.cs
公共静态T Add(这个T向量,T值),其中T:struct,IVector
{
var输出=默认值(T);
for(int i=0;i
所有类型都在同一命名空间中

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

为什么会这样

编辑:(真不敢相信我忘了添加这个)

如何解决这个问题?

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

为什么会这样


重载解析不考虑约束(
IVector
IMatrix
),因为这是扩展方法之间唯一不同的地方,所以它们都是不明确的-它们具有相同的名称和相同的泛型参数。

这是因为在评估两个方法是否具有相同的签名时没有考虑泛型约束。您实际上定义了两个相同的add方法

/// <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());
    }
}
尝试以下方法:

// 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;
}
//VectorExtensions.cs
公共静态T Add(此T向量,IVector值),其中T:struct,IVector
{
var输出=默认值(T);
for(int i=0;i
您有两个扩展方法,每个扩展方法都有相同的签名

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

// MatrixExtensions.cs 
public static T Add<T>(this T matrix, T value)
//VectorExtensions.cs
公共静态T加法(此T向量,T值)
//MatrixExtensions.cs
公共静态T加法(此T矩阵,T值)
是的,您在代码中提供了约束,但是您有两个具有相同签名的方法,因此这两个方法都不比另一个好,并且存在歧义问题

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

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

7.5.5.2扩展方法调用

在方法调用中(§7.5.5.1) 其中一种形式

expr。标识符()

expr。标识符(args)

expr。标识符()

expr。标识符(args)

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

C。标识符(expr)

C。标识符(expr,args)

C。标识符(expr)

C。标识符(expr,args)

搜索C的过程如下:

  • 从最近的封闭命名空间声明开始,继续 每个封闭的命名空间声明, 以包含 编译单元,连续尝试 是为了找到一组候选的 扩展方法:
    • 如果给定的命名空间或编译单元直接包含 带有Ci的非泛型类型声明 具有以下特性的扩展方法Mj 可访问的名称标识符和 适用于所需的 静态方法调用,然后 这些扩展方法的集合是 候选人决定辞职
    • 如果在给定的 直接使用命名空间或编译单元 包含非泛型类型声明 具有扩展方法Mj且具有 名称和标识符是可访问的 并适用于 所需的静态方法调用 上面,然后是这些扩展的集合 方法是候选集
  • 如果在任何封闭的命名空间声明或 编译单元,编译时错误 发生
  • 否则,重载解析将应用于候选集,如下所示 如(§7.4.3)所述。如果没有单身 找到了最好的方法,编译时 发生错误
  • C是一种类型,其中
    /// <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());
        }
    }