C# params object[] strange behavior
Asked Answered
C

3

19

Considering this code

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)
        {
        }
    }
}

And this page https://msdn.microsoft.com/fr-ca/library/w5zay9db.aspx

I would expect (params object[] objects) to be an array of one element with a string[] as the first element, but when I debug, I see that (params object[] objects) is { "Test1", "Test2", "Test3" }.

However, with an int[], I get an object[] with int[] as first element.

With strings

With ints

Is this undefined behavior? Does that depend on the .Net framework version / Mono version?

Copley answered 8/3, 2016 at 19:35 Comment(2)
Try it with an int[], though...Coloration
https://mcmap.net/q/473819/-why-does-params-behave-like-thisColoration
R
30

Nice find!

Is this undefined behavior?

No. This is by-design behaviour. Weird design, but by design.

Does that depend on the .Net framework version / Mono version?

No. All versions of C# have this behaviour.

This is a consequence of the collision of some interesting rules of C#.

The first relevant rule is: a method with a params array may be called in either "normal" or "expanded" form. Normal form is as if there was no "params". Expanded form takes the params and bundles them up into an array that is automatically generated. If both forms are applicable then normal form wins over expanded form.

Now, that perhaps seems sensible; if you have an array of objects in hand, odds are good that you want to pass the array of objects, not an array that contains an array of objects.

The second relevant rule is that C# allows unsafe array covariance when the element type is a reference type. That is, an array of strings may be converted to an array of objects implicitly. You'll note that this has two implications. First, it means that when you have an array of objects, it might actually be an array of strings, so putting, say, a turtle into that array of objects might cause a type error. This is very surprising! You expect that every array of objects can take any object, but that is not true in C#. Some arrays of objects are lying.

The second implication is: since putting that turtle into what is really an array of strings must throw, it means that every time you put something into an array of base type, the runtime must verify that the types check. So array writes are extra expensive in C# on every write, so that the vanishingly small minority of bad writes can be caught.

This is a mess, and this is why unsafe array covariance tops my list of unfortunate C# features.

The combination of these two rules explains your observations. The array of strings is convertible to an array of objects, and therefore the method is applicable in normal form.

For the array of ints, well, covariance does not apply to value types. So the array of ints is not convertible to an array of objects, so the method is not applicable in its normal form. But an array of ints is an object, so it is applicable in expanded form.

See also:

Why does params behave like this?

Your question is arguably a duplicate of:

Is there a way to distingish myFunc(1, 2, 3) from myFunc(new int[] { 1, 2, 3 })?

Raquel answered 8/3, 2016 at 19:55 Comment(6)
that information couldn't come from a better source, great insights EricHootman
Thank you. I did more tests and it seems that the behavior varies whether we pass an array of "class objects": same behavior as string and when we pass an array of "struct objects": we get the same behavior as int.Copley
@evg02gsa3: Correct. All T[] where T is a reference type are convertible to object[], but that covariance only applies to reference types. If you have reference types Animal and Giraffe then Giraffe[] is convertible to Animal[], but int[] is not convertible to ValueType[] or object[].Raquel
@EricLippert Thanks for the clear explanation here! ;) also, your blog posts are both particularly entertaining and awesome! ;) keep writing!Dean
@EricLippert Maybe you can shed some light on this question: https://mcmap.net/q/666396/-attribute-with-params-object-constructor-gives-inconsistent-compiler-errors/5311735, which is highly related to this one. I have a feeling no one else will be able to do that :)Edric
@Evk: Thanks for the note. I took a look at it. The basic question is pretty straightforward but it turns out that the poster might have discovered a bug in the intersection of overload resolution and attribute processing. Interesting question!Raquel
H
5

Im not an expert but the params keyword idea was to enable the posibility to make different calls to the method independent of how many elements you have.

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

So what you are seeing is the normal behaviour nothing weird. More info on this link msdn

By using the params keyword, you can specify a method parameter that takes a variable number of arguments.

You can send a comma-separated list of arguments of the type specified in the parameter declaration or an array of arguments of the specified type. You also can send no arguments. If you send no arguments, the length of the params list is zero.

No additional parameters are permitted after the params keyword in a method declaration, and only one params keyword is permitted in a method declaration.

Hootman answered 8/3, 2016 at 19:39 Comment(1)
Well, if looks like the behavior is different if I use an int[] instead of string[]. This is very strange. I will edit my original post.Copley
D
-1
public static void Test(params string[] strings)
{
}

Test(string1)
Test(string1, string2)

and so on string1 ... stringN.

Dubious answered 8/3, 2016 at 19:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.