Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can generic parameters be specified when using dynamic variables?

Consider the following code:

class Program {
    void Foo<T>() { }

    static void Main(string[] args) {
        dynamic p = new Program();
        p.Foo();
    }
}

Note surprisingly, the call to p.Foo() is not valid because the dynamic binder has no way of knowing what type to use for T. The specific failure is:

"The type arguments for method 'ConsoleApplication1.Program.Foo()' cannot be inferred from the usage. Try specifying the type arguments explicitly."

Now my question is: is there a way to specify the generic type, or is such method simply not callable using 'dynamic'?

like image 418
David Ebbo Avatar asked Dec 04 '22 14:12

David Ebbo


2 Answers

As Jared says, you can specify it in code just as you would for a static call:

using System;

class Program {
    void Foo<T>() {
        Console.WriteLine(typeof(T));
    }

    static void Main(string[] args) {
        dynamic p = new Program();
        p.Foo<string>();
    }
}

The above code prints System.String.

Now if you only know T at execution time, it's slightly harder. If you have an instance of it though, you could use dynamic typing and type inference together:

using System;

class Program {
    void Foo<T>() {
        Console.WriteLine(typeof(T));
    }

    static void Main(string[] args) {
        dynamic p = new Program();
        dynamic v = GetRandomInstance();

        // Now to call p.Foo<T> where T is the type of v's value...
        Dummy(v, p);
    }

    static void Dummy<T>(T t, Program p) {
        p.Foo<T>();
    }

    static object GetRandomInstance() {
        return DateTime.Now.Hour > 10 ? "hello" : (object) 10;
    }
}

EDIT: Pavel came up with an amazing idea in the comments. You don't need to come up with an instance of T, just an array. This means you can even use type arguments where you wouldn't normally be able to get an instance of T (e.g. due to a private constructor):

using System;

class PrivateConstructor {
    private PrivateConstructor() {}
}

class Program {
    static void Foo<T>() {
        Console.WriteLine(typeof(T));
    }

    static void CallFooProxy<T>(T[] array) {
        Foo<T>();
    }

    static void CallFoo(Type t) {
        dynamic array = Array.CreateInstance(t, 0);
        CallFooProxy(array);
    }

    static void Main(string[] args) {
        CallFoo(typeof(PrivateConstructor));
    }
}

Before anyone asks - no, this doesn't let you call Foo<Enumerable> dynamically - you still can't use a static class as a type argument, even if you try to delay the attempt until execution time :)

If all of that fails for some reason, it's back to reflection as normal... get the method info, call MakeGenericMethod and invoke it.

like image 187
Jon Skeet Avatar answered May 18 '23 12:05

Jon Skeet


Why not specify the type exactly as you would for a non-dynamic type

p.Foo<int>();
like image 34
JaredPar Avatar answered May 18 '23 12:05

JaredPar