使用扩展方法,我们可以编写方便的LINQ操作符来解决通用问题。
我想听听您在System.Linq
命名空间中缺少哪些方法或重载以及您如何实现它们。
干净而优雅的实现,最好使用现有的方法。
使用扩展方法,我们可以编写方便的LINQ操作符来解决通用问题。
我想听听您在System.Linq
命名空间中缺少哪些方法或重载以及您如何实现它们。
干净而优雅的实现,最好使用现有的方法。
EnumerableEx.OfThese:
public static class EnumerableEx
{
public static IEnumerable<T> OfThese<T>(params T[] objects)
{
return objects;
}
}
var nums=EnumerableEx.OfThese(1,2,3);
var nums2 = nums.Concat(EnumerableEx.OfThese(4));
类似于 SingleOrDefault
,但是当列表中有多个元素时,它返回 null 而不是抛出异常。
public static T OneOrDefault<T>(this IEnumerable<T> list)
{
using (var e = list.GetEnumerator())
{
if (!e.MoveNext())
return default(T);
T val = e.Current;
if (e.MoveNext())
return default(T);
return val;
}
}
FirstOrDefault
不会再调用 MoveNext
一次。 - GabeMoveNext
,你会得到 FirstOrDefault
。如果你有第二个 MoveNext
并且在返回 true
时抛出异常,你会得到 SingleOrDefault
。如果你有第二个并且返回默认值,你会得到 OneOrDefault
。 - Gabe这是我最喜欢的方法 - 可以轻松地对任何集合进行广度优先搜索。在每次迭代中,该方法会返回当前节点、其父节点、所处图中的层级以及其在广度优先顺序中的索引。在某些场景中,这非常有帮助,但如果你不需要这些信息,该方法也可以大大简化,你可以选择只返回节点本身。
public class IteratedNode<T>
{
public T Node;
public T ParentNode;
public int Level;
public int Index;
}
/// <summary>
/// Iterates over a tree/graph via In Order Breadth First search.
/// </summary>
/// <param name="root">The root item.</param>
/// <param name="childSelector">A func that receives a node in the tree and returns its children.</param>
public static IEnumerable<IteratedNode<T>> GetBreadthFirstEnumerable<T>(this T root, Func<T, IEnumerable<T>> childSelector)
{
var rootNode = new IteratedNode<T> { Node = root, ParentNode = default(T), Level = 1, Index = 1};
var nodesToProcess = new Queue<IteratedNode<T>>( new[] {rootNode});
int itemsIterated = 0;
while (nodesToProcess.Count > 0)
{
IteratedNode<T> currentItem = nodesToProcess.Dequeue();
yield return currentItem; itemsIterated++;
// Iterate over the children of this node, and add it to queue, to process later.
foreach (T child in childSelector(currentItem.Node))
{
nodesToProcess.Enqueue(
new IteratedNode<T> {
Node = child,
ParentNode = currentItem.Node,
Level = currentItem.Level + 1,
Index = itemsIterated
});
}
}
}
这是一个非常简单的包装器,可以使某些查询变得更加容易:
public static IEnumerable<T> Except<T>(this IEnumerable<T> elements, params T[] exceptions)
{
return elements.Except(exceptions);
}
使用方法:
//returns a list of "work week" DayOfWeek values.
Enum.GetValues(typeof(DayOfWeek)).Except(DayOfWeek.Saturday, DayOfWeek.Sunday);
这样可以避免创建自己的集合来传递到Except中,或者使用Where和大型布尔“不等于A且不等于B”的构造,使代码更加简洁。
我还看到过给Except一个谓词的情况,基本上是将其变成Where的反向操作,最简单的实现方式就是:
public static IEnumerable<T> Except<T>(this IEnumerable<T> elements, Predicate<T> predicate)
{
return elements.Where(x=>!predicate(x));
}
使用方法:
//best use is as a "natural language" statement already containing Where()
ListOfRecords.Where(r=>r.SomeValue = 123).Except(r=>r.Id = 2536);
类似于Timwi的MinElement,但是有一种更简洁的算法,使用Aggregate扩展方法来遍历源Enumerable:
public static T ObjectWithMax<T, TResult>(this IEnumerable<T> elements, Func<T, TResult> projection)
where TResult : IComparable<TResult>
{
if (elements == null) throw new ArgumentNullException("elements", "Sequence is null.");
if (!elements.Any()) throw new ArgumentException("Sequence contains no elements.");
//Set up the "seed" (current known maximum) to the first element
var seed = elements.Select(t => new {Object = t, Projection = projection(t)}).First();
//run through all other elements of the source, comparing the projection of each
//to the current "seed" and replacing the seed as necessary. Last element wins ties.
return elements.Skip(1).Aggregate(seed,
(s, x) =>
projection(x).CompareTo(s.Projection) >= 0
? new {Object = x, Projection = projection(x)}
: s
).Object;
}
public static T ObjectWithMin<T, TResult>(this IEnumerable<T> elements, Func<T, TResult> projection)
where TResult : IComparable<TResult>
{
if (elements == null) throw new ArgumentNullException("elements", "Sequence is null.");
if (!elements.Any()) throw new ArgumentException("Sequence contains no elements.");
var seed = elements.Select(t => new {Object = t, Projection = projection(t)}).First();
//ties won by the FIRST element in the Enumerable
return elements.Aggregate(seed,
(s, x) =>
projection(x).CompareTo(s.Projection) < 0
? new {Object = x, Projection = projection(x)}
: s
).Object;
}
相较于 OrderBy().First()
或者 Where(x=>x.SomeField == source.Min(someField)).First()
,我发现这些方法是非常宝贵的。它是线性的,只需要遍历一次 Enumerable,并且与列表当前顺序保持稳定。
根据条件将IEnumerable拆分为两个IList
public static void Partition<T>(this IEnumerable<T> source, Func<T, bool> predicate, ref IList<T> matches, ref IList<T> nonMatches)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (predicate == null)
throw new ArgumentNullException(nameof(source));
var _matches = new List<T>();
var _nonMatches = new List<T>();
foreach (var itm in source)
{
if (predicate(itm))
_matches.Add(itm);
else
_nonMatches.Add(itm);
}
if (matches == null)
matches = new List<T>();
else
matches.Clear();
if (nonMatches == null)
nonMatches = new List<T>();
else
nonMatches.Clear();
foreach (var m in _matches)
matches.Add(m);
nonMatches.Clear();
foreach (var m in _nonMatches)
nonMatches.Add(m);
nonMatches = _nonMatches;
}
将 Where
与 SQL 的 LIKE
模式匹配相结合。
public static IEnumerable<TSource> WhereLike<TSource>(this IEnumerable<TSource> source, Func<TSource, string> selector, string match)
{
/* Turn "off" all regular expression related syntax in
* the pattern string. */
string pattern = Regex.Escape(match);
/* Replace the SQL LIKE wildcard metacharacters with the
* equivalent regular expression metacharacters. */
pattern = pattern.Replace("%", ".*?").Replace("_", ".");
/* The previous call to Regex.Escape actually turned off
* too many metacharacters, i.e. those which are recognized by
* both the regular expression engine and the SQL LIKE
* statement ([...] and [^...]). Those metacharacters have
* to be manually unescaped here. */
pattern = pattern.Replace(@"\[", "[").Replace(@"\]", "]").Replace(@"\^", "^");
Regex reg = new Regex(pattern, RegexOptions.IgnoreCase);
return source.Where(t => reg.IsMatch(selector(t)));
}
C#中类似于广为人知的SQL "between"结构的等效语句
/// <summary>
/// Determines if the current value is included in the range of specified values. Bounds are included.
/// </summary>
/// <typeparam name="T">The type of the values</typeparam>
/// <param name="val">The value.</param>
/// <param name="firstValue">The lower bound.</param>
/// <param name="secondValue">The upper bound.</param>
/// <returns>
/// Return <c>true</c> if the <paramref name="val">value</paramref> is between the <paramref name="firstValue"/> and the <paramref name="secondValue"/>; otherwise, <c>false</c>
/// </returns>
public static bool Between<T>(this T val, T firstValue, T secondValue) where T : IComparable<T>
{
if (val == null)
throw new ArgumentNullException();
if (firstValue == null ||
secondValue == null)
return false;
return firstValue.CompareTo(val) <= 0 && secondValue.CompareTo(val) >= 0;
}
IEnumerable<T>
。 - Timwipublic static void AddRangeWhere<T>(this IEnumerable<T> list, IEnumerable<T> sourceList, Func<T, bool> predicate)
{
if (list != null)
{
List<T> newRange = new List<T>();
foreach (var item in sourceList)
{
if (predicate(item))
newRange.Add(item);
}
list.Concat(newRange);
}
}
RemoveConcurrent<T>()
:
public static IEnumerable<T> RemoveConcurrent<T>(this IEnumerable<T> ts) where T : IEquatable<T>
{
bool first = true;
T lval = default(T);
foreach (T t in ts)
{
if (first || !t.Equals(lval))
{
first = false;
yield return t;
}
lval = t;
}
}
也许可以使用标准的LINQ查询来完成这个任务,但如果有的话,我不知道怎么做。