簡體   English   中英

在LINQ中,如何修改現有的LINQ擴展方法以添加“By”選擇器,即添加Func <T> TResult到函數簽名?

[英]In LINQ, how do I modify an existing LINQ extension method to add a “By” selector, i.e. add Func<T> TResult to the function signature?

我很想知道如何修改現有的LINQ函數以將Func<T> TResult添加到函數簽名中,即允許它使用選擇器(o => o.CustomField)

例如,在C#中,我可以使用.IsDistinct()來檢查整數列表是否不同。 我也可以使用.IsDistinctBy(o => o.SomeField)來檢查字段o.SomeField中的整數o.SomeField是不同的。 我相信,在幕后, .IsDistinctBy(...)有類似函數簽名Func<T> TResult附加到它?

我的問題是:獲取現有LINQ擴展函數並將其轉換為具有參數(o => o.SomeField)什么?

這是一個例子。

此擴展函數檢查列表是否單調增加(即值永遠不會減少,如1,1,2,3,4,5,5):

main()
{
   var MyList = new List<int>() {1,1,2,3,4,5,5};
   DebugAssert(MyList.MyIsIncreasingMonotonically() == true);
}

public static bool MyIsIncreasingMonotonically<T>(this List<T> list) where T : IComparable
{
    return list.Zip(list.Skip(1), (a, b) => a.CompareTo(b) <= 0).All(b => b);
}

如果我想添加一個“By”,我添加一個參數Func<T> TResult 但是如何修改函數體以使其選擇(o => o.SomeField)

main()
{
   DebugAssert(MyList.MyIsIncreasingMonotonicallyBy(o => o.CustomField) == true);
}

public static bool MyIsIncreasingMonotonicallyBy<T>(this List<T> list, Func<T> TResult) where T : IComparable
{
    // Question: How do I modify this function to make it  
    // select by o => o.CustomField?
    return list.Zip(list.Skip(1), (a, b) => a.CompareTo(b) <= 0).All(b => b);
}

考慮如下所示的實現,它只列舉給定的IEnumerable<T>一次。 枚舉可能會產生副作用,如果可能的話,調用者通常會期望單個傳遞。

public static bool IsIncreasingMonotonically<T>(
    this IEnumerable<T> _this)
    where T : IComparable<T>
{
    using (var e = _this.GetEnumerator())
    {
        if (!e.MoveNext())
            return true;
        T prev = e.Current;
        while (e.MoveNext())
        {
            if (prev.CompareTo(e.Current) > 0)
                return false;
            prev = e.Current;
        }
        return true;
    }
}

你描述的enumerable.IsIncreasingMonotonicallyBy(x => x.MyProperty)重載現在可以寫成如下。

public static bool IsIncreasingMonotonicallyBy<T, TKey>(
    this IEnumerable<T> _this,
    Func<T, TKey> keySelector)
    where TKey : IComparable<TKey>
{
    return _this.Select(keySelector).IsIncreasingMonotonically();
}

只需將Func應用於a和b:

public static bool MyIsIncreasingMonotonicallyBy<T, TResult>(this IEnumerable<T> list, Func<T, TResult> selector)
    where TResult : IComparable<TResult>
{
    return list.Zip(list.Skip(1), (a, b) => selector(a).CompareTo(selector(b)) <= 0).All(b => b);
}

上面的一個接近右邊但有問題:

  1. 您的列表可能有多個IEnumeration枚舉

     public static bool MyIsIncreasingMonotonicallyBy<T, TResult>( this IEnumerable<T> list, Func<T, TResult> selector) where TResult : IComparable<TResult> { var enumerable = list as IList<T> ?? list.ToList(); return enumerable.Zip( enumerable.Skip(1), (a, b) => selector(a).CompareTo(selector(b)) <= 0 ).All(b => b); } 

PS我相信你需要刪除“this”,因為Extension方法只能在非泛型的非嵌套靜態類中聲明。


回應FrédéricHamidi:

考慮以下:

IEnumerable<string> names = GetNames();
foreach (var name in names)   Console.WriteLine("Found " + name);
var allNames = new StringBuilder();
foreach (var name in names)   allNames.Append(name + " ");

假設GetNames()返回一個IEnumerable,我們實際上是通過在兩個foreach語句中枚舉這個集合兩次來做額外的工作。 如果GetNames()導致數據庫查詢,則最終執行該查詢兩次,同時兩次獲取相同的數據。

這種問題很容易解決 - 只需通過將序列轉換為列表(或者你可以做數組)來強制枚舉在變量初始化點。 數組和列表類型都實現IEnumerable接口。

// the existing LINQ operator that you want to modify:
public static … Foo<T>(this IEnumerable<T> xs, …)
{
    …
}

// the extended ("modified") operator:
public static … FooBy<T, U>(this IEnumerable<T> xs, Func<T, U> func)
{
    return xs.Select(func).Foo(…);
}       // --^^^^^^^^^^^^^-------
        //   there you go!

暫無
暫無

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

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