繁体   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