简体   繁体   English

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

[英]Pass typeof(x) type to generic method

I've made some Middleware that logs all actions taken by a user within my application. 我制作了一些中间件,可以记录用户在我的应用程序中执行的所有操作。 Depending on the action taken, I need parse out some [FromBody] JSON into their respective key/value pairs for logging. 根据所采取的操作,我需要将一些[FromBody] JSON解析为各自的键/值对以进行记录。

I need to deserialize my JSON within the middleware, but in order to do that, I need to send my DtoType along to the deserializer in order for it to parse out my key/values. 我需要在中间件中反序列化我的JSON,但是为了做到这一点,我需要将DtoType发送到反序列化器中,以便解析出我的键/值。 I've got a method setup to do that, but I need to pass in a generic type because this will be different for every single action the user takes. 我有一个方法设置可以做到这一点,但是我需要传递一个泛型类型,因为对于用户执行的每个操作,它都会有所不同。 (eg I have a UserDto, CustomerDto, etc...) (例如,我有一个UserDto,CustomerDto等)

I've setup a dictionary in order to get the type that I need, however when I pass the var to my logging method to do the rest of the work, I get an error stating that this is not a type but a variable. 我已经设置了一个字典来获取所需的类型,但是当我将var传递给我的日志记录方法以完成其余工作时,我收到一条错误消息,指出这不是类型而是变量。 This is true, however I have no idea how I'm supposed to get the type that I pulled out of my dictionary into the method generic type. 的确如此,但是我不知道如何将我从字典中拉出的类型转换为方法泛型类型。

See my code below: 请参阅下面的代码:

LoggingMiddleware.cs readonly dictionary LoggingMiddleware.cs只读字典

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

LoggingMiddleware.cs Invoke Method 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 Method LoggingMiddleware.cs logAction方法

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

EDIT: Following Example of Possible Duplicate - updated code 编辑:可能重复的以下示例-更新的代码

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

The above never returns anything for GetMethod other than null. 上面的内容从不为GetMethod返回null以外的任何内容。

The exception is telling you exactly what is wrong. 唯一的例外是告诉您出了什么问题。

Type T = _postDictionary[path];

This line of code pulls a Type instance from the dictionary and stores it in the variable, T . 这行代码从字典中提取Type实例,并将其存储在变量T Then, you try to use it like this: 然后,您尝试像这样使用它:

logAction<T>(contextDTO);

However, a generic method expects a non-variable argument between the angle-brackets. 但是,通用方法要求在尖括号之间使用不可变的参数。 Types don't change at run-time; 类型在运行时不会更改; but the type arguments to a generic method can. 但是泛型方法的类型参数可以。 (There are some compiler-specific nuances to that statement, but we'll ignore those for now.) (该语句有一些特定于编译器的细微差别,但我们暂时将其忽略。)

What you're essentially trying to get at is this: 您本质上想要得到的是:

logAction<SomeType>(contextDTO);

But if you want to store the type in a Dictionary , you'll have to pass that type as an argument to your method, and lose the generic capability: 但是,如果要将类型存储在Dictionary ,则必须将该类型作为参数传递给您的方法,并失去通用功能:

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

This is because the value of T is only known at runtime, not at compile time. 这是因为T的值仅在运行时才知道,而在编译时才知道。 You're going to have to reflect over T to get at its properties (as your question implies). 您将不得不考虑T才能获得其属性(如您的问题所暗示)。 In that event, you likely don't want a generic method, anyway. 在那种情况下,无论如何您可能都不想要通用方法。

If you're using json.net you could do something like: 如果您使用的是json.net ,则可以执行以下操作:

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

Or If I'm reading this wrong, and you and you want something like this, you could do this. 或者,如果我读错了,而您和您想要这样的话,您可以这样做。

    public void LogAction<T>(T obj)
    {

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

I was able to get this with help of the Duplicate Post. 我能够在重复帖子的帮助下获得此信息。

Within my Invoke method, I used GetMethod to find my method and assign a generic type based on my dictionary. 在我的Invoke方法中,我使用GetMethod来找到我的方法并根据我的字典分配一个通用类型。 Since it was a private method, I had to use both the BindingFlags.NonPublic & BindingFlags.Instance flags in order for it to find the method. 由于它是一个私有方法,因此我必须同时使用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});
                }
            }

There is a difference between a Type (a class) and a generic type T. The T you are trying to get from your dictionary is simply a normal variable of the class Type, and not anything you can pass as a generic type parameter. Type(一个类)与泛型T之间是有区别的。您试图从字典中获取的T只是Type类的普通变量,而不是您可以作为泛型类型参数传递的任何内容。 You would probably have to change your approach a bit in order to achieve what you want without using reflection. 您可能需要稍作更改,才能在不使用反射的情况下实现所需的功能。

Approach 1. Let LogAction take a Type as a parameter and hope that there is an overloaded version that accepts such an argument: 方法1.让LogAction将Type作为参数,并希望有一个接受此类参数的重载版本:

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

Or you can look into using a Func to control your parsing behavior better, something like 或者您可以考虑使用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));

I would recommend it over reflection as it should give you more control in the long run. 我建议不要使用反射,因为从长远来看,它应该可以给您更多的控制权。

You can get some more fun and abstraction by separating what is logging, and the other unrelated code: 通过分离日志记录和其他不相关的代码,您可以得到更多的乐趣和抽象:

    // 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);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM