如何使用反射获取Func <t>的返回类型?

时间:2016-11-30 20:46:52

标签: c# .net reflection

我有以下类型层次结构:

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

我还有一个方法可以根据需要解析给定类型的实例(将其视为外行人的 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();
    });
}

我需要在T中找出Func<T>的类型,但是当我尝试时:

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

    Resolve(factories);
}

我得Controller而不是PersonControllerOrderController

有什么想法吗?

更新

我感谢大家提供了详细的答案和例子。他们促使我重新考虑 API ,这就是我最终想出的东西,它是我想要完成的:
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);            
        }
    }
}

以下是一些使用示例:

void Main()
{
    var resolver = new ControllerResolver();
    resolver.Register(() => new PersonController());
    resolver.Register(() => new OrderController());   

    resolver.ResolveAll();
    resolver.Resolve(typeof(PersonController));
}

4 个答案:

答案 0 :(得分:8)

每个方法都有一个存储在程序集中的返回类型,您指定方法的返回类型为Controller。这是唯一可以保证作为信息的东西(这是我们所知道的而不执行该方法 - 也是在编译时

因此我们知道该方法应该返回Controller或从中派生的任何内容。在我们实际调用该方法之前,我们永远不会知道什么是运行时类型。

例如,如果该方法具有如下代码,该怎么办?

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

如果需要获取返回对象的运行时类型,则需要:

var controllerInstance = x();
Console.WriteLine(controllerInstance.GetType().Name);

答案 1 :(得分:6)

C#中的Lambdas根据分配给它们的表达式获取它们的类型。例如,这个:

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

使lambda x => x成为string => string函数,理由是这是变量d所期望的。

为什么这有关系?因为在以下代码中,您正在为以下lambdas创建类似的期望:

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

期望这些lambdas属于Func<Controller>类型。如果编译器正在查看类型的lambda实体,它将得出一个不同的结论,特别是一个是Func<PersonController>而另一个是Func<OrderController>,两者都是{{ 1}}。但是编译器没有查看类型的主体,它查看变量,在这种情况下“变量”是数组槽。

因此,编译器将生成这些委托而不是这些方法:

Func<Controller>

但是正如这些方法:

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

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

因此,当这些委托插入到数组中时,只保留签名的类型信息,而lambda实体的实际类型信息将丢失。

但是有一种方法可以维护有关lambda主体的信息,然后检查该信息而不是委托签名。您可以使用表达式树,这是告诉编译器将代码视为数据并基本上生成表示 lambda的树对象的方式,而不是实际的方法实现 lambda。

他们的语法几乎与lambdas的语法相同:

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

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

不同之处在于,现在这些对象不是函数,而是函数的表示。您可以遍历这些表示,并且很容易找到其顶级表达式的类型:

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

最后,如果您还打算运行它们,也可以将这些表示转换为实际函数:

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

答案 2 :(得分:3)

问题是相同的T必须适用于数组中的每个类型。在

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

你的T是什么?是PersonController吗?是OrderController吗?为了使其适合您的用例,它只能是PersonControllerOrderController的超类,同时是Controller的子类。因此T只能是一个类:Controller(假设这两个类没有扩展Controller的公共基类)。使这个类变得通用没有意义。它与此完全相同:

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

而不是一个Func<Controller>个实例的数组,传入一个键入你的返回类型的字典可能更有意义:

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

Func<Controller>的实例根本不包含足够的信息来确定其返回值的类型:不是没有评估它。即使你评估它,记住一个功能是一个黑盒子。仅仅因为它返回PersonController一次并不意味着它在下次调用时不会返回OrderController

答案 3 :(得分:2)

这是它的工作原理,当你实例化你的数组时,你指定它将存储类型为Func的{​​{1}}个对象,现在你要实例化数组中的子类型对象Controller,Resolve,不知道实际类型是什么,除非您调用Func来确定它的实际类型,它只知道GetType()将是{{1}类型因为约束,即T