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