Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get the return type of a Func<T> using reflection?

I have the following type hierarchy:

public abstract class Controller {}
public sealed class PersonController : Controller {}
public sealed class OrderController : Controller {}

I also have a method that resolves the instance of a given type on demand (think of it as a layman's IOC):

private void Resolve<T>(Func<T>[] controllerFactories) where T : Controller
{
    Array.ForEach(controllerFactories, x => 
    {
        // This returns Controller instead of the child class
        // I need to know the actual type so that I can do other stuff
        // before resolving the instance.
        Console.WriteLine(x.Method.ReturnType);
        var controllerInstance = x();
    });
}

I need to figure out the type of T in Func<T> but when I try:

void Main()
{
    var factories = new Func<Controller>[] {
        () => new PersonController(),
        () => new OrderController()
    };
    
    Resolve(factories);
}

I get Controller instead of the PersonController and OrderController.

Any ideas?

Update:

I thank everyone for providing such detailed answers and examples. They prompted me to rethink the API and here is what I finally came up with which serves what I wanted to accomplish:
public interface IResolver<T>
{
    void Register<TKey>(Func<TKey> factory) where TKey : T;
    T Resolve(Type type);
    void ResolveAll();
}

public sealed class ControllerResolver : IResolver<Controller>
{
    private Dictionary<Type, Func<Controller>> _factories = new Dictionary<Type, Func<Controller>>();
        
    public void Register<TKey>(Func<TKey> factory) where TKey : Controller
    {
        _factories.Add(typeof(TKey), factory);
    }
    
    public Controller Resolve(Type type) => _factories[type]();

    public void ResolveAll()
    {
        foreach (var pair in _factories)
        {
            // Correctly outputs what I want
            Console.WriteLine(pair.Value.Method.ReturnType);            
        }
    }
}

And here are some usage examples:

void Main()
{
    var resolver = new ControllerResolver();
    resolver.Register(() => new PersonController());
    resolver.Register(() => new OrderController());   
    
    resolver.ResolveAll();
    resolver.Resolve(typeof(PersonController));
}
like image 529
MaYaN Avatar asked Nov 30 '16 20:11

MaYaN


3 Answers

Each method has a return type stored in the assembly, you specified that your methods' return type is Controller. This is the only thing that is guaranteed as an information (That is what we know without executing the method - Also at compile time)

So we know that the method should return Controller or anything that derive from that. We can never know what is the run-time type until we actually call that method.

As an example, what if the method has a code like:

var factories = new Func<Controller>[] {
    () =>
    {
        if ( DateTime.Now.Second % 2 == 0 )
            return new OrderController();
        else
            return new PersonController();
    }
};

If you need to get the run-time type of the object returned, then you need to:

var controllerInstance = x();
Console.WriteLine(controllerInstance.GetType().Name);
like image 175
Zein Makki Avatar answered Nov 02 '22 09:11

Zein Makki


Lambdas in C# get their type based on the expression they are assigned to. For example, this:

Func<string, string> d = x => x;

Makes the lambda x => x a string => string function, on the grounds that this is what the variable d expects.

Why is this relevant? Because in the following code, you're creating a similar expectation for the following lambdas:

var factories = new Func<Controller>[] {
    () => new PersonController(),
    () => new OrderController()
};

The expectation is that these lambdas are of the Func<Controller> type. If the compiler was looking at the lambda bodies for the type, it would arrive at a different conclusion, specifically that one is a Func<PersonController> and the other is a Func<OrderController>, both of which are kinds of Func<Controller>. But the compiler doesn't look at the body for the type, it looks at the variable and the "variable" in this case is the array slot.

Thus, the compiler will generate these delegates not as these methods:

PersonController Delegate1()
{
    return new PersonController();
}

OrderController Delegate2()
{
    return new OrderController();
}

But as these methods:

Controller Delegate1()
{
    return new PersonController();
}

Controller Delegate2()
{
    return new OrderController();
}

Therefore, by the time these delegates are inserted into the array, only the type information of the signatures remain, while the actual type information of the lambda bodies is lost.

But there is one way to maintain the information about the lambda bodies, and then inspect that information instead of the delegate signatures. You can use of expression trees, which are a way to tell the compiler to treat code as data and essentially generate tree objects that represent the lambda, instead of the actual method that implements the lambda.

Their syntax is nearly identical to that of lambdas:

var factories = new Expression<Func<Controller>>[] {
    () => new PersonController(),
    () => new OrderController()
};

The difference is that now these objects aren't functions, but rather representations of functions. You can traverse those representation and very easily find the type of their top-level expression:

var t0 = factories[0].Body.Type; // this is equal to typeof(PersonController)
var t1 = factories[1].Body.Type; // this is equal to typeof(OrderController)

Finally, you can also turn these representations into actual functions, if you also intend to run them:

Func<Controller>[] implementations = factories.Select(x => x.Compile()).ToArray();
like image 36
Theodoros Chatzigiannakis Avatar answered Nov 02 '22 10:11

Theodoros Chatzigiannakis


The problem is that the same T has to apply to every type in your array. In

private void Resolve<T>(Func<T>[] controllerFactories) where T : Controller
{
    Array.ForEach(controllerFactories, x => 
    {
        // This returns Controller instead of the child class
        Console.WriteLine(x.Method.ReturnType);
        var controllerInstance = x();
    });
}

what is your T? Is it PersonController? Is it OrderController? To make it fit your use case, it can only be a class that is a superclass of both PersonController and OrderController, while being a subclass of Controller. Therefore T can only be one class: Controller (assuming the two classes don't have a common base class that extends Controller). There is no point in making this class generic. It is exactly the same as this:

private void Resolve(Func<Controller>[] controllerFactories)
{
    Array.ForEach(controllerFactories, x => 
    {
        Console.WriteLine(x.Method.ReturnType);
        var controllerInstance = x();
    });
}

Instead of an array of Func<Controller> instances, it may make more sense to pass in a dictionary keyed on your return type:

var factories = new Dictionary<Type, Func<Controller>> {
    [typeof(PersonController)] = () => new PersonController(),
    [typeof(OrderController)] = () => new OrderController()
};

An instance of Func<Controller> simply does not contain enough information to determine the type of its return value on its own: not without evaluating it. And even if you evaluate it, remember that a function is a black box. Just because it returns a PersonController once doesn't mean that it won't return an OrderController the next time you invoke it.

like image 3
Rob Lyndon Avatar answered Nov 02 '22 11:11

Rob Lyndon