C# params quiz and issues

Friday, 11 April 2008 11:31 by Adrian Aisemberg

Params and arrays

Here's a C# quiz regarding params:

class Program
{
    static void Main()
    {
        // case 1
        Foo(1, 2);

        // case 2
        Foo(new int[] { 1, 2 });

        // case 3
        Foo(new object[] { 1, 2 });
    }

    static void Foo(params object[] args)
    {
        // args?
    }
}

Can you tell what is the value of 'args' in each case?

Answers below...

 

 

 

 

 

Answers:

image

image

image

So both (1,2) and (new object[] { 1, 2 }) give the same result, so we can say that whenever a list of arguments is given, it creates an array of that list (case 1), and whenever an array of that type is given, it leaves it as is (case 3).

But when an array of a derived type is given, it also creates an array of the required type with that array inside it (case 2).  

Let's continue.

// case 4
Foo(new StringBuilder("SB1"), new StringBuilder("SB2"));

// case 5
Foo(new StringBuilder[] { new StringBuilder("SB1"), new StringBuilder("SB2") });

// case 6
Foo(new object[] { new StringBuilder("SB1"), new StringBuilder("SB2") });

StringBuilder derives from object, so there shouldn't be any difference from the previous cases.

Wrong.

Here:

image

image

image

Cases 4 and 6 still behave the same, but case 5 does not behave like case 2.

I suspect different behavior with reference and value types, in cases 2 and 5 (StringBuilder is a reference type, and int - a value type)

More:

// case 7
Foo(new DateTime[] { new DateTime(1,1,1), new DateTime(2,2,2) });

image

Again, DateTime is a value type.

And one more:

struct A { }

static void Main()
{
    // case 8
    Foo(new A[] { new A(), new A() });
}

image

'A' is a struct, which is a value type.

 

My suspicion seems right.

Value types behave different than reference types when using an array and a params method.

Params and overloads

Here are two overloads of some method:

static void Foo(params object[] args)
{
    Console.WriteLine("1");
}

static void Foo(StringBuilder sb, params object[] args)
{
    Console.WriteLine("2");
}

And here are some calls for them:

StringBuilder sb1 = new StringBuilder();
object sb2 = new StringBuilder();

Foo(sb1);
Foo(sb1, "x");
Foo(sb2);
Foo(sb2, "x");

What's the output?

We might believe it is "2,2,2,2", because both sb1 and sb2 are StringBuilders, but no. The output is "2,2,1,1".

sb2 is declared as an object and treated as one.

 

OK. It must be different for value types!

Lets try this:

static void Foo(params object[] args)
{
    Console.WriteLine("1");
}

static void Foo(int i, params object[] args)
{
    Console.WriteLine("3");
}

With this:

int i1 = 1;
object i2 = 2;

Foo(i1);
Foo(i1, "x");
Foo(i2);
Foo(i2, "x");

And the output is: "3, 3, 1, 1".

Seems like there is no difference between value types and reference types in this case.

Conclusion

We've discovered three issues that we all should be aware of and when using 'params' methods:

1. Wrapping arguments with an object array is the same as passing the arguments unwrapped (cases 1 and 3, or 4 and 6).

2. Value types and reference types behave different when wrapping them with an array of the type (cases 2 and 5).

3. When calling an overloaded 'params' method, it will choose the overload according to the declared parameter type and not the actual type.

Some tips

Lets start by suggesting never to overload 'params' methods. You'll never know what may happen.

Next, always write more unit-test than enough to get 100% coverage on your 'params' methods. A lot more.

One last thing: You can know, at runtime, whether a method is a 'params' method.

Here's how:

When you write the 'params' keyword before an argument, the compiler translates it to the 'ParamArrayAttribute'.

So you can get the MethodInfo of the method, and the ParameterInfo of the parameter, and check for existing 'ParamArrayAttribute'. If so, then the method is 'params'.

Here's an extension method for all that:

public static bool IsParams(this MethodBase method)
{
    bool _isLastParameterParams = false;

    // Get the method's parameters
    ParameterInfo[] _parameters = method.GetParameters();

    // If parameters exist
    if (_parameters != null && _parameters.Length > 0)
    {
        // Get the last parameter
        ParameterInfo _lastParameter = _parameters.Last();

        // If the last parameter has a ParamArrayAttribute attribute
        _isLastParameterParams = 
            Attribute.IsDefined(_lastParameter, typeof(ParamArrayAttribute));
    }

    return _isLastParameterParams;
}

Notice that this method takes a MethodBase and not a MethodInfo, so it supports also constructors.

Sample use:

static void Main()
{
    BindingFlags bindingFlags = BindingFlags.Static | BindingFlags.NonPublic;

    MethodInfo foo1 = typeof(Program).GetMethod("Foo1", bindingFlags);
    MethodInfo foo2 = typeof(Program).GetMethod("Foo2", bindingFlags);
    MethodInfo foo3 = typeof(Program).GetMethod("Foo3", bindingFlags);

    bool isFoo1Params1 = foo1.IsParams(); // true
    bool isFoo2Params2 = foo2.IsParams(); // true
    bool isFoo3Params3 = foo3.IsParams(); // false
}

static void Foo1(params object[] args)
{
}

static void Foo2(string str, params int[] args)
{
}
    
static void Foo3(object[] args)
{
}

 

Be creative and write as much unit-tests as possible.

That's my best suggestion.

 

kick it on DotNetKicks.com

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Related posts

Comments

April 15. 2008 05:18

Sebastian Ullrich

The problem is that StringBuilder[] is implicitly convertible to object[] but int[] isn't (the same holds true for the respective non-array types for the same reason: boxing).
So the SB[] parameter is applicable to the method's normal form (specification 3.0 $7.4.3.1) whereas int[] must be applied to its expanded form.
But I'll admit... logical != intuitive ;) .

Sebastian Ullrich

April 15. 2008 14:58

pingback

Pingback from weblogs.asp.net

Interesting Finds: 2008.04.16 - gOODiDEA.NET

weblogs.asp.net

April 16. 2008 00:56

pingback

Pingback from alvinashcraft.com

Dew Drop - April 16, 2008 | Alvin Ashcraft's Morning Dew

alvinashcraft.com

Add comment


(Will show your Gravatar icon)  

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

July 23. 2008 06:29