5

我很想知道如何修改现有的 LINQ 函数以添加Func<T> TResult到函数签名中,即允许它使用(o => o.CustomField).

例如,在 C# 中,我可以.IsDistinct()用来检查整数列表是否不同。我还可以.IsDistinctBy(o => 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);
}
4

3 回答 3

4

考虑一个像下面这样的实现,它只枚举给定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();
}
于 2013-02-13T19:57:15.470 回答
3

只需将Funca 和 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);
}
于 2013-02-13T19:15:27.327 回答
0

上面的一个接近正确,但存在问题:

  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”,因为扩展方法只能在非泛型、非嵌套静态类中声明。


回应弗雷德里克·哈米迪:

考虑以下:

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 接口。

于 2013-02-13T19:33:45.170 回答