天天看點

.NET 7 和 C# 11 的 7 大自定義擴充方法

.NET 7 和 C# 11 的 7 大自定義擴充方法

介紹

自從我開始了解擴充方法以來,我不斷地發現新的可能性,讓我的編碼生活更輕松。 擴充方法是 SOLID完美應用 中的O ——開閉原則。 一個類應該盡可能簡單,并且隻在其他元件真正需要時才将屬性和方法暴露給外部。

通過擴充方法,您可以為您的類實作額外的方法,而無需更改類本身! 這非常适合将類作為參數的重複方法。

實作擴充方法非常簡單。 看看下面的例子:

namespace System;

public static class EnumerableExtensions
{
    public static void ForEach<T>(this IEnumerable<T> sequence, Action<T> action) {
        foreach (var item in sequence) 
            action(item);
    }
}           

這在每個 IEnumerable<T>(由 this 關鍵字引入)上實作了 ForEach() 方法,就像您從 List<T> 類型中了解到的那樣。 要通路此方法,您唯一需要做的就是添加對相應命名空間的引用,在本例中為 System。

我最常用的 7 種擴充方法

自 .NET 6 以來,Microsoft 為 IEnumerable<T> 實作了一些擴充方法,我之前将這些方法列在了我的首選清單中,包括 DistinctBy() 和 Chunk()。 但是,從 .NET 7 開始,我仍然缺少一些非常重要的方法,尤其是處理任務(Task)集合的方法。

事不宜遲,以下是我在 .NET 7 中最常用的擴充方法:

1. TryAsync

第一種擴充方法是我最喜歡的一種。 您有多少次在剛剛建立的方法周圍添加 try-catch 塊,并且因為它破壞了外觀而變得有點惱火? 當您的方法傳回 Task 或 Task<T> 時,這是一個非常簡潔的解決方案:

public static async Task<Result> TryAsync(
    this Task task,
    Action<Exception> errorHandler = null
) {
    try {
        await task;
        return Result.Ok();
    }
    catch (Exception ex) {
        if (errorHandler is not null) errorHandler(ex);
        return ex;
    }
}

public static async Task<Result<T>> TryAsync<T>(
    this Task<T> task,
    Action<Exception> errorHandler = null
) where T : class {
    try {
        return await task;
    }
    catch (Exception ex) {
        if (errorHandler is not null) errorHandler(ex);
        return ex;
    }
}           

現在你可以這樣寫:

var result = await GetSomethingAsync().TryAsync();           

您的方法将自動包裝在 try-catch 塊中。 此外,您可以為其他輔助邏輯(如日志記錄)提供 errorHandler。 然後可以檢查這些方法傳回的結果是否成功,後續您可以繼續您的邏輯。

2. WhenAllAsync

集合中有多個 Task 或 Task<T> 以正常方式處理有點不友善。 您需要調用 Task.WhenAll(tasks) 這讓我有點脫離流程,因為它不是流暢的風格。 這是它的樣子:

public static async Task<IEnumerable<T>> WhenAllAsync<T>(this IEnumerable<Task<T>> tasks) {
    if (tasks is null)
        throw new ArgumentNullException(nameof(tasks));

    return await Task
        .WhenAll(tasks)
        .ConfigureAwait(false);
}

public static Task WhenAllAsync(this IEnumerable<Task> tasks) {
    if (tasks is null)
        throw new ArgumentNullException(nameof(tasks));

    return Task
        .WhenAll(tasks);
}           

現在我可以友善的地處理任何任務集合:

var results = await tasks.WhenAllAsync();           

3. WhenAllSequentialAsync

下一個擴充方法甚至可以為您節省幾行代碼,并讓您能夠逐個執行每個任務。 這在您可能不會并行執行許多任務的情況下很有用。

public static async Task<IEnumerable<T>> WhenAllSequentialAsync<T>(this IEnumerable<Task<T>> tasks) {
    if (tasks is null)
        throw new ArgumentNullException(nameof(tasks));

    var results = new List<T>();
    foreach (var task in tasks)
        results.Add(await task.ConfigureAwait(false));
    return results;
}

public static async Task WhenAllSequentialAsync(this IEnumerable<Task> tasks) {
    if (tasks is null)
        throw new ArgumentNullException(nameof(tasks));

    foreach (var task in tasks)
        await task.ConfigureAwait(false);
}           

4. WhenAllParallelAsync

最後但同樣重要的是,可能有一個用例,您可以并行執行任務,但可能有最大數量限制。 對于這種情況,我有以下擴充方法:

public static async Task<IEnumerable<T>> WhenAllParallelAsync<T>(
    this IEnumerable<Task<T>> tasks,
    int degree
) {
    if (tasks is null)
        throw new ArgumentNullException(nameof(tasks));

    var results = new List<T>();
    foreach (var chunk in tasks.Chunk(degree)) {
        var chunkResults = await Task.WhenAll(chunk).ConfigureAwait(false);
        results.AddRange(chunkResults);
    }
    return results;
}

public static async Task WhenAllParallelAsync(
    this IEnumerable<Task> tasks,
    int degree
) {
    if (tasks is null)
        throw new ArgumentNullException(nameof(tasks));

    foreach (var chunk in tasks.Chunk(degree)) 
        await Task.WhenAll(chunk).ConfigureAwait(false);
}           

使用 degree 參數,您可以指定應并行執行多少個任務。

5. MapAsync

這也是一個流暢的擴充,但這次是針對單個 Task 或 Task<T>。

public static async Task<TOut> MapAsync<TIn, TOut>(
    this Task<TIn> task,
    Func<TIn, Task<TOut>> mapAsync
) {
    if (task is null)
        throw new ArgumentNullException(nameof(task));

    if (mapAsync is null)
        throw new ArgumentNullException(nameof(mapAsync));

    return await mapAsync(await task);
}

public static async Task<TOut> MapAsync<TIn, TOut>(
    this Task<TIn> task,
    Func<TIn, TOut> map
) {
    if (task is null)
        throw new ArgumentNullException(nameof(task));

    if (map is null)
        throw new ArgumentNullException(nameof(map));

    return map(await task);
}           

使用此擴充方法,您可以将 Task<T1> 流暢地映射到 Task<T2>,類似于 Enumerable.Select() 方法。

6. DoAsync

類似的方法是 DoAsync() ,但不是轉換任務,而是可以使用任務結果執行輔助邏輯而不改變其傳回值。

public static async Task<T> DoAsync<T>(
    this Task<T> task,
    Func<T, Task> tapAsync
) {
    if (task is null)
        throw new ArgumentNullException(nameof(task));

    if (tapAsync is null)
        throw new ArgumentNullException(nameof(tapAsync));

    var res = await task;
    await tapAsync(res);
    return res;
}

public static async Task<T> DoAsync<T>(
    this Task<T> task,
    Action<T> tap
) {
    if (task is null)
        throw new ArgumentNullException(nameof(task));

    if (tap is null)
        throw new ArgumentNullException(nameof(tap));

    var res = await task;
    tap(res);
    return res;
}           

7. String.Join

最後一個是我有時用于連接配接字元串以進行日志記錄的擴充方法。 通常,您可以為此使用 string.Join() ,但同樣,這并不流暢,讓我無法了解。

public static string Join(this IEnumerable<string> sequence, string separator = "") {
    return string.Join(separator, sequence);
}           

縮略

技術上不是擴充方法,但對節省一些代碼也很有用,以下是我的縮寫方法:

namespace System;

public static class Abbreviations
{
    public static IEnumerable<T> Arr<T>(params T[] elements) => elements;

    public static void Try(
        Action action,
        Action<Exception>? errorHandler = null
    ) {
        try {
            action();
        }
        catch (Exception ex) {
            if (errorHandler is not null) errorHandler(ex);
        }
    }

    public static Result<T> Try<T>(
        Func<T> action,
        Action<Exception>? errorHandler = null
    ) where T : class 
    {
        try {
            return action();
        }
        catch (Exception ex) {
            if (errorHandler is not null) errorHandler(ex);
            return ex;
        }
    }
}           

要輕松使用它們,您必須在根級别建立一個特殊檔案(我通常将其稱為 GlobalUsings.cs)并在其中放入以下行:

global using static System.Abbreviations;           

1. Arr

Arr 方法是從現有值建立新 IEnumerable<T> 的縮寫。 通常,這将需要非常醜陋的代碼,如下所示:

var arr = new [] { param1, param2,... };           

現在你可以這樣寫:

var arr = Arr(param1, param2,... );           

哪個看起來更漂亮?

2. Try

您有多少次隻是想在一個新方法周圍添加一個簡單的 try-catch 塊,但由于它在您的代碼中占用了太多空間,是以感覺很難看? 這是一個縮寫方法 Try() 的解決方案。

在最短的形式中,你可以寫:

Try(TestMethod);           

其中 TestMethod 是一個不帶任何參數的方法。 這使您能夠編寫極短的代碼并消除那些讨厭的 try-catch 塊。

對于異步方法,我建議您使用上面的擴充方法 TryAsync,因為它是流暢的風格。

當然,還有更多的可能性,您可以使用此架構并制作自己的縮寫詞和擴充方法。 在評論中讓我知道你的想法。

我希望您發現其中一些方法有用并将它們應用到您的項目中。

謝謝閱讀!

繼續閱讀