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:



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:

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

Again, DateTime is a value type.
And one more:
struct A { }
static void Main()
{
// case 8
Foo(new A[] { new A(), new A() });
}

'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.