这是关于类型推断的编译器选择的另一个问题。
在此代码段中,您可以看到接受DoStuff
的{{1}}方法正在创建实现params[]
的{{1}}。但输出是对List<T>
方法的递归调用,接受IList<T>
,最终导致堆栈溢出。
我知道这里明显的解决方案是简单地将DoStuff
声明为params
,但我想知道为什么编译器会选择接受set
的{{1}}方法。< / p>
IList<T>
输出:
DoStuff
答案 0 :(得分:7)
我们可以为您的问题制作更简单的复制品。
class Animal {}
class Tiger : Animal {}
...
static void M(Animal animal) {}
static void M<T>(params T[] items) {}
...
M(new Tiger());
选择?
我们有两个选择M(Animal)
和params方法的扩展形式:M<Tiger>(Tiger)
。前者要求将老虎转变为动物;后者与参数类型完全匹配。因此选择后者。
现在假设我们有
Animal animal = new Tiger();
M(animal);
现在会发生什么?
我们有两个选择。 M(Animal)
以及params方法的扩展形式:M<Animal>(Animal)
。形式参数类型是相同的,都是完全匹配。编译器回到了一个有争议的回合:如果其中一个是“自然的”,而其中一个由于通用替换而只有Animal
,则自然获胜。所以在这种情况下,前者会赢。
答案 1 :(得分:2)
编译器总是选择更接近的方法Closer is better。
DoStuff<T>
是通用的,这意味着它可以适用于任何类型,并且当特定类型(在这种情况下为List<T>
)没有重载时它更接近,因此编译器选择DoStuff<T>(params T[] items)
比DoStuff<T>(IList<T> set)
。
我相信编译器会在没有隐式转换时更接近,在这种情况下将List<T>
转换为IList<T>
编译器需要隐式转换,但DoStuff<T>(params T[] items)
已经是通用的可以采取任何类型,不需要转换,因此编译器很乐意选择此重载。
您可以通过声明void DoStuff<T>(List<T> set)
或将变量声明为IList<T> set
来解决此问题。
答案 2 :(得分:-3)
这是因为“a”,“b”参数自动创建为2个元素的字符串数组,因为它们可以转换为params T []项,因此第二个DoStuff签名是匹配的。
请参阅:MSDN上的参数数组以获取更多信息。