簡體   English   中英

將typeof(x)類型傳遞給泛型方法

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

我制作了一些中間件,可以記錄用戶在我的應用程序中執行的所有操作。 根據所采取的操作,我需要將一些[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以外的任何內容。

唯一的例外是告訴您出了什么問題。

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才能獲得其屬性(如您的問題所暗示)。 在那種情況下,無論如何您可能都不想要通用方法。

如果您使用的是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);
    }

我能夠在重復帖子的幫助下獲得此信息。

在我的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});
                }
            }

Type(一個類)與泛型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);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM