I am very curious to know how to modify an existing LINQ function to add Func<T> TResult
to the function signature, ie 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 (ie 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);
}
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();
}
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);
}
One above is close to right but there are issues:
Your list has possible multiple enumeration of 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 I believe you need to remove the "this" because Extension method can only be declared in non-generic, non-nested static class.
In response to Frédéric Hamidi:
Consider the following:
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 + " ");
Assuming that GetNames() returns an IEnumerable, we are, effectively, doing extra work by enumerating this collection twice in the two foreach statements. If GetNames() results in a database query, you end up doing that query twice, while both times getting the same data.
This kind of problem can be easily fixed – simply force the enumeration at the point of variable initialization by converting the sequence to a list(or you could do array). Both array and list types implement the IEnumerable interface.
// 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!
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.