Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

params

A question to all of you C# wizards. I have a method, call it myFunc, and it takes variable length/type argument lists. The argument signature of myFunc itself is myFunc(params object[] args) and I use reflection on the lists (think of this a bit like printf, for example).

I want to treat myFunc(1, 2, 3) differently from myFunc(new int[] { 1, 2, 3 }). That is, within the body of myFunc, I would like to enumerate the types of my arguments, and would like to end up with { int, int, int} rather than int[]. Right now I get the latter: in effect, I can't distinguish the two cases, and they both come in as int[].

I had wished the former would show up as obs[].Length=3, with obs[0]=1, etc.

And I had expected the latter to show up as obs[].Length=1, with obs[0]={ int[3] }

Can this be done, or am I asking the impossible?

like image 221
Ken Birman Avatar asked Mar 13 '12 13:03

Ken Birman


People also ask

Can I define a variable in a function Python?

The practical upshot of this is that variables can be defined and used within a Python function even if they have the same name as variables defined in other functions or in the main program.

How to define your own function in Python?

Basic Syntax for Defining a Function in Python In Python, you define a function with the def keyword, then write the function identifier (name) followed by parentheses and a colon. The next thing you have to do is make sure you indent with a tab or 4 spaces, and then specify what you want the function to do for you.


3 Answers

Well this will do it:

using System;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("First call");
        Foo(1, 2, 3);
        Console.WriteLine("Second call");
        Foo(new int[] { 1, 2, 3 });
    }

    static void Foo(params object[] values)
    {
        foreach (object x in values)
        {
            Console.WriteLine(x.GetType().Name);
        }
    }
}

Alternatively, if you use DynamicObject you can use dynamic typing to achieve a similar result:

using System;
using System.Dynamic;

class Program
{
    static void Main(string[] args)
    {
        dynamic d = new ArgumentDumper();
        Console.WriteLine("First call");
        d.Foo(1, 2, 3);
        Console.WriteLine("Second call");
        d.Bar(new int[] { 1, 2, 3 });
    }
}

class ArgumentDumper : DynamicObject
{
    public override bool TryInvokeMember
        (InvokeMemberBinder binder,
         Object[] args,
         out Object result)
    {
        result = null;
        foreach (object x in args)
        {
            Console.WriteLine(x.GetType().Name);
        }
        return true;
    }
}

Output of both programs:

First call
Int32
Int32
Int32
Second call
Int32[]

Now given the output above, it's not clear where your question has really come from... although if you'd given Foo("1", "2", "3") vs Foo(new string[] { "1", "2", "3" }) then that would be a different matter - because string[] is compatible with object[], but int[] isn't. If that's the real situation which has been giving you problems, then look at the dynamic version - which will work in both cases.

like image 197
Jon Skeet Avatar answered Nov 03 '22 02:11

Jon Skeet


OK, so let's say that we abandon the other question where you incorrectly believe that any of this is a compiler bug and actually address your real question.

First off, let's try to state the real question. Here's my shot at it:


The preamble:

A "variadic" method is a method which takes an unspecified-ahead-of-time number of parameters.

The standard way to implement variadic methods in C# is:

void M(T1 t1, T2 t2, params P[] p)

that is, zero or more required parameters followed by an array marked as "params".

When calling such a method, the method is either applicable in its normal form (without params) or its expanded form (with params). That is, a call to

void M(params object[] x){}

of the form

M(1, 2, 3)

is generated as

M(new object[] { 1, 2, 3 });

because it is applicable only in its expanded form. But a call

M(new object[] { 4, 5, 6 });

is generated as

M(new object[] { 4, 5, 6 });

and not

M(new object[] { new object[] { 4, 5, 6 } });

because it is applicable in its normal form.

C# supports unsafe array covariance on arrays of reference type elements. That is, a string[] may be implicitly converted to object[] even though attempting to change the first element of such an array to a non-string will produce a runtime error.

The question:

I wish to make a call of the form:

M(new string[] { "hello" });

and have this act like the method was applicable only in expanded form:

M(new object[] { new string[] { "hello" }});

and not the normal form:

M((object[])(new string[] { "hello" }));

Is there a way in C# to implement variadic methods that does not fall victim to the combination of unsafe array covariance and methods being applicable preferentially in their normal form?


The Answer

Yes, there is a way, but you're not going to like it. You are better off making the method non-variadic if you intend to be passing single arrays to it.

The Microsoft implementation of C# supports an undocumented extension that allows for C-style variadic methods that do not use params arrays. This mechanism is not intended for general use and is included only for the CLR team and others authoring interop libraries so that they can write interop code that bridges between C# and languages that expect C-style variadic methods. I strongly recommend against attempting to do so yourself.

The mechanism for doing so involves using the undocumented __arglist keyword. A basic sketch is:

public static void M(__arglist) 
{
    var argumentIterator = new ArgIterator(__arglist);
    object argument = TypedReference.ToObject(argumentIterator.GetNextArg());

You can use the methods of the argument iterator to walk over the argument structure and obtain all the arguments. And you can use the super-magical typed reference object to obtain the types of the arguments. It is even possible using this technique to pass references to variables as arguments, but again I do not recommend doing so.

What is particularly awful about this technique is that the caller is required to then say:

M(__arglist(new string[] { "hello" }));

which frankly looks pretty gross in C#. Now you see why you are better off simply abandoning variadic methods entirely; just make the caller pass an array and be done with it.

Again, my advice is (1) under no circumstances should you attempt to use these undocumented extensions to the C# language that are intended as conveniences for the CLR implementation team and interop library authors, and (2) you should simply abandon variadic methods; they do not appear to be a good match for your problem space. Do not fight against the tool; choose a different tool.

like image 29
Eric Lippert Avatar answered Nov 03 '22 01:11

Eric Lippert


Yes, you can, checking the params length and checking the argument type, see the following working code sample:

class Program
{
    static void Main(string[] args)
    {
        myFunc(1, 2, 3);
        myFunc(new int[] { 1, 2, 3 });
    }

    static void myFunc(params object[] args)
    {
        if (args.Length == 1 && (args[0] is int[]))
        {
            // called using myFunc(new int[] { 1, 2, 3 });
        }
        else
        {
            //called using myFunc(1, 2, 3), or other
        }
    }
}
like image 35
Daniel Peñalba Avatar answered Nov 03 '22 01:11

Daniel Peñalba