将typeof(x)类型传递给泛型方法

时间:2018-07-02 15:59:03

标签: c# generics

我制作了一些中间件来记录用户在我的应用程序中执行的所有操作。根据所采取的操作,我需要将一些[FromBody] JSON解析为各自的键/值对以进行记录。

我需要在中间件中反序列化我的JSON,但是为了做到这一点,我需要将DtoType发送到反序列化器中,以便解析我的键/值。我有一个方法设置可以做到这一点,但是我需要传递一个泛型类型,因为对于用户执行的每个操作,它都会有所不同。 (例如,我有一个UserDto,CustomerDto等)

我已经设置了一个字典来获取所需的类型,但是当我将var传递给我的日志记录方法来完成其余工作时,我收到一条错误消息,指出这不是类型,而是一个变量。的确如此,但是我不知道如何将我从字典中拉出的类型转换为方法泛型类型。

请参见下面的代码:

LoggingMiddleware.cs只读字典

    private readonly Dictionary<string, Type> _postDictionary = new Dictionary<string, Type>
    {
        { "path/customers", typeof(CustomerPostDto) },
        ...//More entries//...
    };

LoggingMiddleware.cs调用方法

public async Task Invoke(HttpContext context)
{
    using (var streamCopy = new MemoryStream())
    {
        ...//Do some stuff here//...
        //Logging Actions
        if (request.Path != "/")
        {
            if (request.Method == "POST")
            {
               Type T = _postDictionary[path];
               logAction<T>(contextDto);
            }
        }
        ...//Do some stuff here//...
    }
}

LoggingMiddleware.cs logAction方法

private void logAction<T>(object contextDto)
{
    var dto = ControllerBase.ParseBody<T>(contextDto.Body);
    ...//Do some stuff here//...
}

编辑:以下可能重复的示例-更新的代码

                if (request.Method == "POST")
                {
                    Type T = _postDictionary[path];

                    MethodInfo methodLogAction = typeof(LoggingMiddleware).GetMethod("logAction", BindingFlags.NonPublic);
                    MethodInfo generic = methodLogAction.MakeGenericMethod(T);
                    generic.Invoke(contextDto, null);
                }

以上内容从不为GetMethod返回null以外的任何内容。

4 个答案:

答案 0 :(得分:1)

例外情况是完全告诉您出了什么问题。

Type T = _postDictionary[path];

此行代码从字典中提取一个Type实例并将其存储在变量T中。然后,您尝试像这样使用它:

logAction<T>(contextDTO);

但是,通用方法期望尖括号之间有一个不变的参数。类型在运行时不会更改;但是泛型方法的类型参数可以。 (该语句有一些特定于编译器的细微差别,但我们暂时将其忽略。)

您基本上想了解的是:

logAction<SomeType>(contextDTO);

但是,如果要将类型存储在Dictionary中,则必须将该类型作为参数传递给您的方法,并失去通用功能:

public void logAction(Type type, object data)
{
    // Log the data here
}; 

这是因为T的值仅在运行时才知道,而在编译时才知道。您将需要反思T才能获得其属性(如您的问题所暗示)。在这种情况下,无论如何您可能都不想要通用方法。

答案 1 :(得分:0)

如果您使用的是json.net,则可以执行以下操作:

    public void LogAction<T>(string contextDto, Type type)
    {
        T obj = (T)JsonConvert.DeserializeObject(contextDto, type) ;
    }

或者如果我读错了,而您和您想要这样的话,您可以这样做。

    public void LogAction<T>(T obj)
    {

    }
    public ActionResult Test([FromBody] Thing thing)
    {
        LogAction(thing);
    }

答案 2 :(得分:0)

我能够在重复帖子的帮助下得到它。

在我的Invoke方法中,我使用GetMethod来找到我的方法并根据我的字典分配一个通用类型。由于它是一个私有方法,因此必须同时使用BindingFlags.NonPublic和BindingFlags.Instance标志,以便它找到方法。

            //Logging Actions
            if (request.Path != "/")
            {
                if (request.Method == "POST")
                {
                    Type T = _postDictionary[path];

                    MethodInfo methodLogAction = typeof(LoggingMiddleware).GetMethod("LogAction", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] {typeof(object)}, null);
                    MethodInfo generic = methodLogAction.MakeGenericMethod(T);
                    generic.Invoke(this, new object[]{contextDto});
                }
            }

答案 3 :(得分:0)

类型(类)和泛型T之间有区别。您要从字典中获取的T只是类型Type的普通变量,而不能作为泛型传递任何内容参数。您可能需要稍作更改,才能在不使用反射的情况下实现所需的功能。

方法1.让LogAction将Type作为参数,并希望有一个接受此类参数的重载版本:

private void LogAction(object contextDto, Type type) {
    ControllerBase.ParseBody(contextDto.Body, type);
}

或者您可以考虑使用Func更好地控制解析行为,例如

    // Method to get a Func that can parse your object
    private static Func<System.IO.Stream, T> GetParser<T>()
    {
        return (contextDto) => ControllerBase.ParseBody<T>(contextDto.Body);
    }

    // Use that in your dictionary
    private Dictionary<string, Func<System.IO.Stream, object>> transformers = new Dictionary<string, Func<System.IO.Stream, object>>
    {
        {  "/myPath", GetParser<CustomerPostDto>() },
        {  "/myPath-2", GetParser<CustomerPostDto>() }
    };

    // Now the LogAction method can just take the DTO that will be inferred from our parser
    private void LogAction<T>(T dto)
    {
        ...//Do some stuff here//...
    }

    // And call it as such
    if (transformers.ContainsKey(path))
            LogAction(transformers[path](context.Response.Body));

我建议您不要使用反射,因为从长远来看,它应该可以给您更多的控制权。

您可以通过将日志记录与其他不相关的代码分离来获得更多乐趣和抽象:

    // Return a logger with a specification on how to parse a stream from a body
    private static TypeLogger CreateLogger<T>()
    {
        return new TypeLogger<T>((ctx) => ControllerBase.ParseBody<T>(contextDto.Body));
    }

    // Create a list with paths and loggers of specified type
    private Dictionary<string, TypeLogger> loggers = new Dictionary<string, TypeLogger>
    {
        { "/path1", CreateLogger<CustomerPostDto>() },
        { "/path2", CreateLogger<CustomerPostDto>() },
    };

    // Abstract base logger class that allows you to log from a requestbody
    public abstract class TypeLogger
    {
        public abstract void Log(System.IO.Stream requestBody);
    }

    // An actual implementation to parse your dto using by using the parser previously specified
    public class TypeLogger<T> : TypeLogger
    {
        // Parser to use when getting entity
        public Func<System.IO.Stream, T> Parser { get; private set; }

        // Constructor that takes sa func which helps us parse
        public TypeLogger(Func<System.IO.Stream, T> parser) => Parser = parser;

        // The actual logging
        public override void Log(System.IO.Stream requestBody)
        {
            var dto = Parser(requestBody);

            //...
        }
    }

    // And usage
    if (loggers.Contains(path))
        loggers[path].Log(ctx.Response.Body);