Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

Tags:

c#

.net

linq

I am very curious to know how to modify an existing LINQ function to add Func<T> TResult to the function signature, i.e. allow it to use a selector as in (o => o.CustomField).

For example, in C#, I can use .IsDistinct() to check if a list of integers are distinct. I can also use .IsDistinctBy(o => o.SomeField) to check if the integers in field o.SomeField are distinct. I believe that, behind the scenes, .IsDistinctBy(...) has something like the function signature Func<T> TResult appended to it?

My question is this: what is the technique for taking an existing LINQ extension function, and converting it so it can have a parameter (o => o.SomeField)?

Here is an example.

This extension function checks to see if a list is increasing monotonically (i.e. values are never decreasing, as in 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);
}

If I want to add a "By", I add a parameter Func<T> TResult. But how do I modify the body of the function to make it select by (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);
}
like image 872
Contango Avatar asked Feb 13 '13 19:02

Contango


2 Answers

Consider an implementation like the following, which enumerates the given IEnumerable<T> only once. Enumerating can have side-effects, and callers typically expect a single pass-through if that's possible.

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;
    }
}

Your enumerable.IsIncreasingMonotonicallyBy(x => x.MyProperty) overload that you describe can now be written as follows.

public static bool IsIncreasingMonotonicallyBy<T, TKey>(
    this IEnumerable<T> _this,
    Func<T, TKey> keySelector)
    where TKey : IComparable<TKey>
{
    return _this.Select(keySelector).IsIncreasingMonotonically();
}
like image 155
Timothy Shields Avatar answered Nov 15 '22 11:11

Timothy Shields


Just apply the Func to a and 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);
}
like image 3
Thomas Levesque Avatar answered Nov 15 '22 12:11

Thomas Levesque