簡體   English   中英

將 C# Async Lambda 方法分配給作為任務類型的變量

[英]Assign C# Async Lambda Method to Variable Typed as a Task

這在 C# 中是否可行? 以下代碼會產生編譯器錯誤。

HashSet<Task<(string Value, int ToNodeId)>> regionTasks =
  new HashSet<Task<(string Value, int ToNodeId)>>();
foreach (Connection connection in Connections[RegionName])
{
    regionTasks.Add(async () =>
    {        
        string value = await connection.GetValueAsync(Key);
        return (value, connection.ToNode.Id);
    }());
}

C# 編譯器報錯,“錯誤 CS0149:需要方法名稱。” 它無法推斷 lambda 方法的返回類型。

請注意我在 lambda 塊關閉 {} 后立即通過 () 調用 lambda 方法的技術。 這可確保返回Task而不是Func

VB.NET 編譯器理解這種語法。 我驚訝地發現 VB.NET 編譯器比 C# 編譯器更聰明。 請參閱我的 An Async Lambda Compiler Error Where VB Outsmarts C#博客文章了解全文。

Dim regionTasks = New HashSet(Of Task(Of (Value As String, ToNodeId As Integer)))
For Each connection In Connections(RegionName)
    regionTasks.Add(Async Function()
        Dim value = Await connection.GetValueAsync(Key)
        Return (value, connection.ToNode.Id)
    End Function())
Next

VB.NET 編譯器了解End Function()技術。 它正確推斷 lambda 方法的返回類型是Function() As Task(Of (Value As String, ToNodeId As Integer)) ,因此調用它返回Task(Of (Value As String, ToNodeId As Integer)) 這可分配給regionTasks變量。

C# 要求我將 lambda 方法的返回值轉換為Func ,這會產生非常難以辨認的代碼。

regionTasks.Add(((Func<Task<(string Values, int ToNodeId)>>)(async () =>
{
    string value = await connection.GetValueAsync(Key);
    return (value, connection.ToNode.Id);
}))());

糟糕的。 括號太多了! 我在 C# 中能做的最好的事情就是明確聲明一個Func ,然后立即調用它。

Func<Task<(string Value, int ToNodeId)>> getValueAndToNodeIdAsync = async () =>
{
    string value = await connection.GetValueAsync(Key);
    return (value, connection.ToNode.Id);
};
regionTasks.Add(getValueAndToNodeIdAsync());

有沒有人找到更優雅的解決方案?

如果您可以使用.NET Standard 2.1 (或某些 .NET 框架版本,請參閱兼容性列表),您可以將 LINQ 與ToHashSet方法一起使用:

var regionTasks = Connections[RegionName]
    .Select(async connection => 
    {        
        string value = await connection.GetValueAsync(Key);
        return (Value: value, ToNodeId: connection.ToNode.Id);
    })
    .ToHashSet();

或者只是用相應的IEnumerable初始化HashSet

UPD

評論中鏈接的另一種解決方法:

static Func<R> WorkItOut<R>(Func<R> f) { return f; }

foreach (Connection connection in Connections[RegionName])
{
    regionTasks.Add(WorkItOut(async () =>
    {        
        string value = await connection.GetValueAsync(Key);
        return (value, connection.ToNode.Id);
    })());
}

當我第一次閱讀您的問題的標題時,我想“嗯?誰會建議嘗試將 x 類型的值分配給 y 類型的變量,而不是與 x 的 inheritance 關系?這就像嘗試將 int 分配給字符串。 ..”

我閱讀了代碼,然后更改為“好的,這不是為任務分配委托,這只是創建一個任務並將其存儲在任務集合中。但看起來他們確實在分配一個委托到一個任務...

然后我看到了

請注意我在 lambda 塊關閉 {} 后立即通過 () 調用 lambda 方法的技術。 這確保了返回一個任務,而不是一個函數。

你必須用評論來解釋這一點,這意味着這是一種代碼味道,而且是錯誤的做法。 您的代碼已經從可讀的自我記錄變成了代碼高爾夫練習,使用了聲明委托並立即執行它以創建任務的神秘語法技巧。 這就是我們擁有Task.Run / TaskFactory.StartNew的目的,這也是我所見過的所有 TAP 代碼在需要任務時所做的事情

您會注意到此表單有效並且不會產生錯誤:

HashSet<Task<(string Value, int ToNodeId)>> regionTasks =
  new HashSet<Task<(string Value, int ToNodeId)>>();
foreach (Connection connection in Connections[RegionName])
{
    regionTasks.Add(Task.Run(async () =>
    {        
        string value = await connection.GetValueAsync(Key);
        return (value, connection.ToNode.Id);
    }));
}

它的工作原理要清楚得多,而且您在不鍵入Task.Run時保存的 7 個字符意味着您不必編寫 50 多個字符的注釋來解釋為什么可以將看起來像委托的東西分配給 Task 類型的變量

我會說 C# 編譯器可以讓你避免在這里編寫糟糕的代碼,這是 VB 編譯器讓開發人員快速松散並編寫難以理解的代碼的另一種情況

調用異步 lambda 以獲取具體化任務的一種簡單方法是使用幫助程序 function,如下面的Materialize

public static Task Materialize(Func<Task> taskFactory) => taskFactory();
public static Task<T> Materialize<T>(Func<Task<T>> taskFactory) => taskFactory();

使用示例:

regionTasks.Add(Materialize(async () =>
{
    string value = await connection.GetValueAsync(Key);
    return (value, connection.ToNode.Id);
}));

暫無
暫無

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

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