Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic delegate instances

I wonder if C# (or the underlying .NET framework) supports some kind of "generic delegate instances": that is a delegate instance that still has an unresolved type parameter, to be resolved at the time the delegate is invoked (not at the time the delegate is created). I suspect this isn't possible, but I'm asking it anyway...

Here is an example of what I'd like to do, with some "???" inserted in places where the C# syntax seems to be unavailable for what I want. (Obviously this code doesn't compile)

class Foo {
  public T Factory<T>(string name) {
    // implementation omitted
  }
}

class Test {
  public void TestMethod()
  {
    Foo foo = new Foo();
    ??? magic = foo.Factory; // No type argument given here yet to Factory!
                             // What would the '???' be here (other than 'var' :) )?
    string aString = magic<string>("name 1"); // type provided on call
    int anInt = magic<int>("name 2"); // another type provided on another call

    // Note the underlying calls work perfectly fine, these work, but i'd like to expose
    // the generic method as a delegate.
    string aString2 = foo.Factory<string>("name 1");
    int anInt2 = foo.Factory<int>("name 2");
  }
}

Is there a way to actually do something like this in C#? If not, is that a limitation in the language, or is it in the .NET framework?

Edit: The reason I ask is because I'd like to pass the delegate to a function in another assembly, and don't want to require that other assembly having to reference any particular type (the "Foo" class in my example). I was hoping to bend the standard Func<> delegate in a way so it would fit the "???" part.

like image 674
Luc C Avatar asked Feb 27 '23 09:02

Luc C


2 Answers

This cannot be done, since what you're asking is declaring a variable (magic) of an unclosed generics type.

One can work with unclosed generics but only at the type level, e.g.:

delegate T FactoryDelegate<T>(string name);

var magicType = typeof (FactoryDelegate<>);

and then "close" the type at a later point:

var stringMagic = magicType.MakeGenericType(typeof(string));

Update: that said, here's a sample on how you can use the above technique to also work with unclosed method "types". Still not as elegant as it would be if we could assign unclosed types though..:

    public class UnclosedMethod
    {
        private readonly MethodInfo _method;

        public UnclosedMethod(Type type, string method)
        {
            _method = type.GetMethod(method);
        }

        public T Invoke<T>(string name)
        {
            var fact = _method.MakeGenericMethod(typeof(T));
            return (T)fact.Invoke(this, new object[] { name });
        }
    }

And then in code do this:

var magic = new UnclosedMethod(typeof(Foo), "Factory");
var x = magic.Invoke<string>("bar");
like image 65
Peter Lillevold Avatar answered Mar 12 '23 11:03

Peter Lillevold


An excellent question. First of all, we can observe that C# doesn't allow you to define any delegate type with a generic Invoke method. There is simply no space for the type parameters; the list that comes after the delegate name is used for the parameters of the delegate type itself.

So I went for CIL and generated what should look like a delegate with a generic Invoke:

.class public auto ansi sealed GenericDelegate extends [mscorlib]System.MulticastDelegate
{

.method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
{
}

.method public hidebysig newslot virtual instance !!T Invoke<T>(!!T arg) runtime managed
{
}

}

To my surprise, C# can actually consume this type without issues – you can create an instance of this type from a generic method (with matching constraints) and the program compiles. However, the result is an invalid CIL, as constructing the delegate uses the ldftn instruction but the generic method has no executable code associated with it, as it is generic.

Even though I couldn't find anything in ECMA-335 that would explicitly prohibit the delegate, the runtime rejects it. The problem is that the runtime attribute on Invoke specifies that the implementation for this method is provided by the runtime, but this isn't supported when the method is generic. While ldftn could be modified to allow generic methods and the implementation of Invoke could be provided in this case, it simply isn't.


I agree however that sometimes this concept is useful. While the runtime will not help you with it, probably the easiest way is to simply use an interface:

class Foo
{
    public T Factory<T>(string name)
    {
    }
}

class FooFactory : IGenericFunc<string>
{
    readonly Foo target;
    public FooFactory(Foo target)
    {
        this.target = target;
    }

    public T Invoke<T>(string name)
    {
        return target.Factory<T>(name);
    }
}

interface IGenericFunc<TArg>
{
    T Invoke<T>(TArg arg);
}

Create an interface for every variation of arguments you need, and an implementation for every method you need to call. If you also want to have something akin to Delegate.CreateDelegate, you will most likely have to use System.Reflection.Emit to have it somewhat performant.

like image 38
IS4 Avatar answered Mar 12 '23 10:03

IS4