具有约束的泛型方法的重载解决问题

时间:2014-09-25 11:28:05

标签: 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
{
}

任何人都可以解释一下,为什么这个方法会调用:

var value = new FooImpl[0];
Bar(value);

目标Bar<T>(T source)(因此,不编译)?

在解决重载问题时,编译器是否会考虑类型参数约束?

UPD

避免与数组混淆。任何IEnumerable<T>的实现都会发生这种情况,例如:

var value = new List<FooImpl>();

UPD 2

@ ken2k提到了协方差。 但是,让我们忘记FooImpl。这样:

var value = new List<IFoo>();
Bar(value);

产生相同的错误 我确定,List<IFoo>IEnumerable<IFoo>之间存在隐式转换,因为我可以轻松地写出这样的内容:

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

并将value传递给它:

SomeMethod(value);

4 个答案:

答案 0 :(得分:3)

  

在解决重载问题时,编译器是否会考虑类型参数约束?

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

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
}

你的代码没有编译的原因是因为编译器会选择&#34; best&#34;基于方法签名的匹配,然后尝试应用通用约束。

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

{假设List<T>Add<T>(IEnumerable<T> source)方法}

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?

显而易见的解决方法是为采用集合的Bar方法使用不同的名称(如使用AddAddRange在BCL中所做的那样。

答案 1 :(得分:2)

编辑:好的,在传递列表时选择Bar<T>(T source)超过Bar<T>(IEnumerable<T> source)的原因是因为&#34; 7.5.3.2 Better function member&#34; C#语言参考部分。它说的是,当必须发生重载解析时,参数类型与适用的函数成员的参数类型匹配(见第7.5.3.1节),并且更好的函数成员由以下规则集选择:

  

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

     

•对于至少一个参数,从EX到PX的转换优于从EX到QX的转换。

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

此规则在扩展和类型参数替换&#34; 之后应用&#34;由于类型参数替换会将Bar(T源)交换为Bar&gt;(IList源),因此此方法参数将比需要转换的Bar(IEnumerable源)更好地匹配。

我无法找到该语言参考的在线版本,但您可以阅读here


编辑:最初误解了这个问题,努力在c#语言规范中找到正确的答案。基本上是IIRC,通过考虑最合适的类型来选择方法,如果你没有准确地将参数转换为IEnumerable<>,那么Bar<T>(T source)将完全匹配参数类型,就像这样样品:

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].AsEnumerable();
Bar(value);

这样做:

var value = new FooImpl[0] as IEnumerable<IFoo>;
Bar(value);

来自the doc

  

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

所以你的编译器不知道数组与Bar的签名匹配,你必须明确地转换它

答案 2 :(得分:0)

这是一个covariance问题。 List<T>不是协变的,因此List<FooImpl>List<IFoo>之间没有隐式转换。

另一方面,从C#4开始,IEnumerable<T>现在支持协方差,所以这有效:

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

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

var value = new List<FooImpl>();
Bar((IEnumerable<IFoo>)value);

答案 3 :(得分:0)

c#7.3 开始,出于重载解析的目的,现在将泛型约束视为方法签名的一部分。来自What's new in C# 7.0 through C# 7.3: Improved overload candidates

  1. 当方法组包含一些类型参数不满足其约束的通用方法时,这些成员将从候选集中删除。

因此,在c# 7.3 / .Net Core 2.x和更高版本中,问题编译并成功运行中显示的代码,并且根据需要调用Bar<T>(IEnumerable<T> value)

  • .net 5小提琴here演示现在可以成功编译

  • 演示.NET Framework 4.7.3小提琴here仍然无法编译,并显示以下错误:

    The type 'FooImpl[]' cannot be used as type parameter 'T' in the generic type or method 'TestClass.Bar<T>(T)'. There is no implicit reference conversion from 'FooImpl[]' to 'IFoo'.