C# 带约束的泛型方法的重载求解问题

C# 带约束的泛型方法的重载求解问题,c#,generics,overload-resolution,C#,Generics,Overload Resolution,代码示例: interface IFoo { } class FooImpl : IFoo { } static void Bar<T>(IEnumerable<T> value) where T : IFoo { } static void Bar<T>(T source) where T : IFoo { } 目标Bar(T源代码)(因此不编译) 在解析重载时,编译器是否考虑了类型参数约束 UPD 避免与数组混淆。这发生在IEnume

代码示例:

interface IFoo { }
class FooImpl : IFoo { }

static void Bar<T>(IEnumerable<T> value)
    where T : IFoo
{
}

static void Bar<T>(T source)
    where T : IFoo
{
}
目标
Bar(T源代码)
(因此不编译)

在解析重载时,编译器是否考虑了类型参数约束

UPD

避免与数组混淆。这发生在
IEnumerable
的任何实现中,例如:

var value = new List<FooImpl>();
产生相同的错误。
我确信,
List
IEnumerable
之间存在隐式转换,因为我可以很容易地编写如下内容:

static void SomeMethod(IEnumerable<IFoo> sequence) {}

编辑:好的,在传递列表时选择
Bar(T源)
而不是
Bar(IEnumerable源)
的原因是因为C语言参考的“
7.5.3.2更好的函数成员”
”部分。它说的是,当必须进行重载解析时,参数类型与适用函数成员的参数类型相匹配(第7.5.3.1节),并通过以下规则选择更好的函数成员:

•对于每个参数,从EX到QX的隐式转换并不比从EX到PX的隐式转换好,并且

•对于至少一个参数,从EX到PX的转换比从EX到QX的转换要好

(PX是第一种方法的参数类型,QX是第二种方法的参数类型)

此规则在展开和类型参数替换后应用。由于类型参数替换会将Bar(T源)交换为Bar>(IList源),因此此方法参数比需要转换的Bar(IEnumerable源)更匹配

我找不到语言参考的在线版本,但您可以阅读它


编辑:最初误解了问题,正在c语言规范中寻找正确答案。基本上,IIRC方法是通过考虑最合适的类型来选择的,如果您没有将参数精确地强制转换为
IEnumerable
,则
条(t源)
将与参数类型完全匹配,就像在这个示例中:

public interface ITest { }
public class Test : ITest { }

private static void Main(string[] args)
{
    test(new Test() ); // outputs "anything" because Test is matched to any type T before ITest
    Console.ReadLine();
}


public static void test<T>(T anything)
{
    Console.WriteLine("anything");
}

public static void test(ITest it)
{
    Console.WriteLine("it");
}
这也是:

var value = new FooImpl[0] as IEnumerable<IFoo>;
Bar(value);
var value=new FooImpl[0]作为IEnumerable;
巴(值);
发件人:

从.NET Framework 2.0开始,Array类实现 System.Collections.Generic.IList, System.Collections.Generic.ICollection和 System.Collections.Generic.IEnumerable通用接口。这个 实现在运行时提供给阵列,因此, 通用接口不会出现在的声明语法中 数组类

因此,您的编译器不知道数组与Bar的签名匹配,您必须显式地强制转换它,这是一个问题<代码>列表不是协变的,因此在
列表
列表
之间没有隐式转换

另一方面,从C#4开始,
IEnumerable
现在支持协方差,因此这是可行的:

var value = Enumerable.Empty<FooImpl>();
Bar(value);

var value = new List<FooImpl>().AsEnumerable();
Bar(value);

var value = new List<FooImpl>();
Bar((IEnumerable<IFoo>)value);
var value=Enumerable.Empty();
巴(值);
var value=new List().AsEnumerable();
巴(值);
var值=新列表();
Bar((IEnumerable)值);
在解析重载时,编译器是否考虑了类型参数约束


否,因为泛型约束不是函数签名的一部分。您可以通过添加除通用约束外相同的
重载来验证这一点:

interface IBar { }

static void Bar<T>(IEnumerable<T> value)
    where T : IFoo
{
}

static void Bar<T>(T source)
    where T : IBar
{
    // fails to compile : Type ____ already defines a member called 'Bar' with the same parameter types
}
接口IBar{}
静态空心条(IEnumerable值)
T:IFoo在哪里
{
}
静态空心条(T源)
其中T:IBar
{
//未能编译:类型“”已使用相同的参数类型定义了名为“Bar”的成员
}
代码不编译的原因是编译器根据方法签名选择“最佳”匹配,然后尝试应用泛型约束

一个可能的原因是此调用不明确:

{假设
List
有一个
Add(IEnumerable source
)方法}

List junk=new List();
垃圾。添加(1);//好啊
junk.Add(“xyzy”)//确定
添加(新[{1,2,3,4})//不明确-是否要添加数组的_数组uu或内容u?
显而易见的解决办法是为获取集合的
Bar
方法使用不同的名称(如BCL中的
Add
AddRange
,从c#7.3开始,通用约束现在被视为方法签名的一部分,用于重载解析。从:

  • 当方法组包含某些类型参数不满足其约束的泛型方法时,这些成员将从候选集中删除
  • 因此,在和更高版本中,问题中显示的代码编译并成功运行,并且根据需要调用
    条(IEnumerable value)

    • 一个演示.NET5小提琴现在编译成功

    • 演示版.NET Framework 4.7.3 fiddle仍无法编译,错误如下:

      类型“FooImpl[]”不能用作泛型类型或方法“TestClass.Bar(T)”中的类型参数“T”。没有从“FooImpl[]”到“IFoo”的隐式引用转换。


    刚刚选中,Bar(值)被接受。@VictorMukherjee:事实上,我想了解是什么阻止编译器以
    Bar(IEnumerable值)为目标
    没有任何额外提示。乍一看,没有什么可以阻止它。答案有问题吗?为什么投反对票?如果列表通过,同样的问题也会出现。不过,我不是downvoter@samy:这是一个非常可疑的假设。首先,正如维克托所提到的,同样的事情也会发生在
    IE的任何其他实现中numerable
    。第二,我不知道我的问题与您提供的链接有何关联。丹尼斯,这个链接解释了为什么数组不能作为IEnumerable in Bar接受,但@VictorMukherjee的评论明确了这个问题
    var value = new FooImpl[0].AsEnumerable();
    Bar(value);
    
    var value = new FooImpl[0] as IEnumerable<IFoo>;
    Bar(value);
    
    var value = Enumerable.Empty<FooImpl>();
    Bar(value);
    
    var value = new List<FooImpl>().AsEnumerable();
    Bar(value);
    
    var value = new List<FooImpl>();
    Bar((IEnumerable<IFoo>)value);
    
    interface IBar { }
    
    static void Bar<T>(IEnumerable<T> value)
        where T : IFoo
    {
    }
    
    static void Bar<T>(T source)
        where T : IBar
    {
        // fails to compile : Type ____ already defines a member called 'Bar' with the same parameter types
    }
    
    List<object> junk = new List<object>();
    junk.Add(1);   // OK
    junk.Add("xyzzy") // OK
    junk.Add(new [] {1, 2, 3, 4});  //ambiguous - do you intend to add the _array_ or the _contents_ of the array?