为什么params对于一个字符串数组和一个整数数组的行为不同?

时间:2018-03-22 19:03:19

标签: c# unit-testing parameters nunit

在编写我的单元测试时,我偶然发现了一个问题:NUnit' [TestCaseAttribute],以下构造函数重载:

public TestCaseAttribute(params object arg)
public TestCaseAttribute(params object args[])

会接受一个整数数组,以及包含字符串数组的参数列表,但不接受字符串数组本身:

[TestCaseAttribute(new[] { 1, 2, 3 })] //works
[TestCaseAttribute("Other string", new[] { "1", "2", "3" })] //works
[TestCaseAttribute(new[] { "1", "2", "3" })] //compilation error?

这令我感到惊讶,所以我验证了这个行为:

private static void PrintTypes(params object[] objects)
{
    Console.WriteLine("Array type is " + objects.GetType());
    Console.WriteLine("Object count is " + objects.Length);
    Console.WriteLine("Object type is " + objects[0].GetType());
}

public static void Main()
{
    Console.WriteLine("Array of ints: ");
    PrintTypes(new[] { 1, 2, 3 });
    Console.WriteLine();
    Console.WriteLine("Array of strings: ");
    PrintTypes(new[] { "1", "2", "3" });
}

输出对我来说有点莫名其妙 - 似乎整数数组被视为单个对象,但是字符串数组被展开:

Array of ints:
Array type is System.Object[]
Object count is 1
Object type is System.Int32[]

Array of strings:
Array type is System.String[]
Object count is 3
Object type is System.String

如果我们添加以下方法:

private static void PrintTypes(object obj)
{
    Console.WriteLine("In object method");
    Console.WriteLine("Object type is " + obj.GetType());
}

编译器似乎更喜欢它的一个int数组,但不是一个字符串数组:

Array of ints: 
In object method
Object type is System.Int32[]

Array of strings: 
In array method
Array type is System.String[]
Object count is 3
Object type is System.String

为什么会这样?我假设它与整数不是引用类型有关,但我想更多地解释一下究竟是什么规定编译器更喜欢不同数组的不同重载,以及为什么一个是允许的对于属性构造函数,而不是另一个。

1 个答案:

答案 0 :(得分:3)

您走在正确的轨道上,int不是参考类型,是 PrintTypes 测试方法观察到行为的原因。

根据C#规范(link),第15.6.2.5章 参数数组:

  

参数数组允许以两种方式之一指定参数   在方法调用中:

     

•为参数数组提供的参数可以是可隐式转换(第11.2节)到参数数组类型的单个表达式。在这种情况下,参数数组的作用与值参数完全相同。

第11.2章(隐式引用转换)解释了“隐式可转换”对数组的意义:

  

隐式参考转换是:

     

[...]

     

•从具有元素类型SE的数组类型S到具有元素类型TE的数组类型T,条件是满足以下所有条件:

     

- S和T仅在元素类型上有所不同。换句话说,S和   T具有相同的维数。

     

- 从SE到TE

存在隐式参考转换

请注意,此处需要存在隐式引用转换。从值类型到引用类型的转换不是(隐式)引用转换(请参阅第11.2章)。这就是为什么int []数组被视为单个对象参数的原因,这导致以扩展形式调用 PrintTypes

现在,为什么呢?

[TestCaseAttribute(new[] { 1, 2, 3 })] //works

编译,而

[TestCaseAttribute(new[] { "1", "2", "3" })] //compilation error?

不是吗?它与我刚才说的不矛盾吗?

让我们看一下TestCaseAttribute的构造函数:

public TestCaseAttribute(params object[] arguments);
public TestCaseAttribute(object arg);
public TestCaseAttribute(object arg1, object arg2);
public TestCaseAttribute(object arg1, object arg2, object arg3);

根据上面给出的解释,应该清楚[TestCaseAttribute(new[] { 1, 2, 3 })]正在编译,因为它使用TestCaseAttribute(object arg)构造函数重载。

new[] { "1", "2", "3" }是一个字符串数组,编译器选择此参数TestCaseAttribute(params object args[])的最佳匹配重载(根据第12.6.4节“重载分辨率”中解释的规则)。不幸的是,重载决策并没有考虑属性的特殊要求,即属性参数必须是常量表达式。常量表达式的规则(第12.20章常量表达式)状态:

  

注意:其他转换包括装箱,拆箱和隐式   常量中不允许引用非空值的转换   表达式

(强调我的)

这就是导致关于属性的编译器错误的原因。编译器选择一个最适合string[]参数的构造函数重载。然后,在编译过程的后期阶段,编译器尝试应用该属性。它注意到属性参数不允许必需的隐式引用转换,这会导致观察到的编译错误。