C# params object[] 奇怪行为

19
考虑到这段代码。
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] strings = new string[] { "Test1", "Test2", "Test3" };

            int[] ints = new int[] { 1, 2, 3, 4 };

            Test(strings);

            Test(ints);
        }

        public static void Test(params object[] objects)
        {
        }
    }
}

这个页面 https://msdn.microsoft.com/fr-ca/library/w5zay9db.aspx

我希望 (params object[] objects) 是一个数组,其中第一个元素是一个 string[] 数组。但是当我调试时,我发现 (params object[] objects) 是 { "Test1", "Test2", "Test3" }。

然而,如果是 int[],我会得到一个以 int[] 为第一个元素的 object[]。

With strings

With ints

这是否属于未定义行为?这取决于.Net Framework版本/Mono版本吗?

2
尝试使用 int[] 来测试一下... - Dennis_E
https://dev59.com/mHE95IYBdhLWcg3wrPwn#2210603 - Dennis_E
3个回答

30

很好的发现!

这是未定义行为吗?

不是。这是按设计行为。奇怪的设计,但是经过设计。

这是否取决于.Net框架版本/Mono版本?

不是。所有版本的C#都有此行为。

这是C#一些有趣规则的碰撞所导致的结果。

第一个相关规则是:带有params数组的方法可以以“正常”或“扩展”形式调用。正常形式就像没有“params”一样。扩展形式将params打包成自动生成的数组。如果两种形式都适用,则正常形式胜过扩展形式

现在,这可能看起来很合理;如果你手头有一个对象数组,那么你想传递的很可能是对象数组,而不是包含对象数组的数组。

第二个相关规则是,当元素类型为引用类型时,C#允许不安全的数组协变。也就是说,字符串数组可以隐式转换为对象数组。你会注意到这有两个含义。首先,这意味着当你有一个对象数组时,它可能实际上是一个字符串数组,因此将乌龟放入该对象数组中可能会导致类型错误。这非常令人惊讶!你期望每个对象数组都可以接受任何对象,但在C#中并不是这样。有些对象数组是虚假的。
第二个含义是:由于将那只乌龟放入实际上是字符串数组的数组中必须抛出异常,因此每次将东西放入基础类型的数组中时,运行时都必须验证类型是否匹配。因此,在C#中,每次写入数组都会额外增加开销,以便能够捕获极少数的错误写入。
这很混乱,这就是为什么不安全的数组协变成为我不幸的C#功能列表中的首选原因。
这两条规则的组合解释了您的观察结果。字符串数组可转换为对象数组,因此该方法适用于正常形式。
对于int数组,协方差不适用于值类型。因此,int数组不能转换为对象数组,因此该方法在其正常形式下不适用。但是,int数组是一个对象,因此它适用于扩展形式。
另请参见: 为什么params会表现出这样的行为? 您的问题可以说是以下问题的重复: 是否有一种方法可以区分myFunc(1, 2, 3)和myFunc(new int[] {1, 2, 3})?

这些信息来自最好的来源,Eric提供了很棒的见解。 - mjsr
谢谢。我进行了更多的测试,似乎行为取决于我们传递一个“类对象”数组:与字符串相同的行为,当我们传递一个“结构对象”数组时:我们得到与整数相同的行为。 - evg02gsa3
2
@evg02gsa3:正确。所有T是引用类型的T[]都可以转换为object[],但这种协变仅适用于引用类型。如果您有引用类型AnimalGiraffe,则Giraffe[]可转换为Animal[],但int[]不能转换为ValueType[]object[] - Eric Lippert
@EricLippert 感谢您在这里提供清晰的解释! ;) 此外,您的博客文章既有趣又棒! ;) 请继续写作! - Ian
@EricLippert 或许你能为这个问题 https://dev59.com/H6nka4cB1Zd3GeqPHgnw 提供一些启示,它与这个问题高度相关。我有一种感觉,没有其他人能够做到 :) - Evk
1
@Evk:谢谢你的留言。我看了一下。基本问题很简单,但事实证明,发帖人可能在重载解析和属性处理的交集中发现了一个错误。有趣的问题! - Eric Lippert

5

我不是专家,但params关键字的想法是为了使方法的调用与元素数量无关,从而实现不同的调用。

Test(object1)
Test(object1, object2)
Test(object1,..., objectN)

所以你看到的是正常的行为,没有什么奇怪的。更多信息请查看msdn

使用params关键字,您可以指定一个方法参数,该参数接受可变数量的参数。

您可以发送参数类型在参数声明中指定的逗号分隔的参数列表或指定类型的参数数组。您还可以不发送参数。如果您不发送参数,则params列表的长度为零。

在方法声明中,params关键字后不允许使用其他参数,并且在方法声明中只允许使用一个params关键字。


1
好的,看起来如果我使用int[]而不是string[]行为不同。这非常奇怪。我将编辑我的原始帖子。 - evg02gsa3

-1
public static void Test(params string[] strings)
{
}

Test(string1)
Test(string1, string2)

等等字符串1...字符串N。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接