One of C#’s most unique features is its SQL-style LINQ syntax. It’s a powerful and expressive way to treat data structures like a database and perform all kinds of actions on them. LINQ can also be used without the SQL-style syntax via various extension methods of IEnumerable<T> defined in the System.Linq namespace. Due to C#’s extension method feature, we’re free to add on our own LINQ-style functions to extend its power. Today’s article introduces some extension methods to do just that. Read on for the source code and power up your LINQ!

All of the extension methods shown today are inspired by the <algorithm> header file from C++. You can read more in the documentation to learn about them. I’ve ported them to C# as extension methods to IEnumerable<T> and named them so that they fit with the existing LINQ extensions.

Today’s article has definitions for these functions:

  • bool None(Func<T, bool>): opposite of Any
  • void ForEach(Action<T>): function version of foreach
  • T FirstNot(Func<T, bool>): find first element not matching, exception otherwise
  • T FirstNotOrDefault(Func<T, bool>): find first element not matching, default otherwise
  • T LastNot(Func<T, bool>): find last element not matching, exception otherwise
  • T LastNotOrDefault(Func<T, bool>): find last element not matching, default otherwise
  • IEnumerable WhereAdjacent(): iterate starting at adjacent equal elements, as defined by Equals
  • IEnumerable WhereAdjacent(Func<T, T, bool>): iterate starting at adjacent equal elements, as defined by predicate
  • IEnumerable WhereMismatch(): iterate starting at adjacent not equal elements, as defined by Equals
  • IEnumerable WhereMismatch(Func<T, T, bool>): iterate starting at adjacent not equal elements, as defined by predicate
using System;
using System.Collections.Generic;
using System.Linq;
 
/// <summary>
/// LINQ-style extensions to IEnumerable{T} inspired by the algorithm header in C++
/// </summary>
/// <author>Jackson Dunstan, http://JacksonDunstan.com/articles/3408</author>
/// <license>MIT</license>
public static class EnumerableExtensions
{
	public static bool None<TSource>(
		this IEnumerable<TSource> source,
		Func<TSource, bool> predicate
	)
	{
		return !source.Any(predicate);
	}
 
	public static void ForEach<TSource>(
		this IEnumerable<TSource> source,
		Action<TSource> callback
	)
	{
		foreach (var cur in source)
		{
			callback(cur);
		}
	}
 
	public static TSource FirstNot<TSource>(
		this IEnumerable<TSource> source,
		Func<TSource, bool> predicate
	)
	{
		foreach (var cur in source)
		{
			if (predicate(cur) == false)
			{
				return cur;
			}
		}
		throw new InvalidOperationException("no matches found");
	}
 
	public static TSource FirstNotOrDefault<TSource>(
		this IEnumerable<TSource> source,
		Func<TSource, bool> predicate
	)
	{
		foreach (var cur in source)
		{
			if (predicate(cur) == false)
			{
				return cur;
			}
		}
		return default(TSource);
	}
 
	public static TSource LastNot<TSource>(
		this IEnumerable<TSource> source,
		Func<TSource, bool> predicate
	)
	{
		return source.Reverse().FirstNot(predicate);
	}
 
	public static TSource LastNotOrDefault<TSource>(
		this IEnumerable<TSource> source,
		Func<TSource, bool> predicate
	)
	{
		return source.Reverse().FirstNotOrDefault(predicate);
	}
 
	public static IEnumerable<TSource> WhereAdjacent<TSource>(
		this IEnumerable<TSource> source
	)
	{
		foreach (var cur in source.WhereAdjacent((a, b) => a.Equals(b)))
		{
			yield return cur;
		}
	}
 
	public static IEnumerable<TSource> WhereAdjacent<TSource>(
		this IEnumerable<TSource> source,
		Func<TSource, TSource, bool> predicate
	)
	{
		var e = source.GetEnumerator();
		if (e.MoveNext())
		{
			var cur = e.Current;
			while (e.MoveNext())
			{
				var next = e.Current;
				if (predicate(cur, next))
				{
					yield return cur;
					yield return next;
					while (e.MoveNext())
					{
						yield return e.Current;
					}
					yield break;
				}
				cur = next;
			}
		}
	}
 
	public static IEnumerable<TSource> WhereMismatch<TSource>(
		this IEnumerable<TSource> source
	)
	{
		foreach (var cur in source.WhereAdjacent((a, b) => a.Equals(b) == false))
		{
			yield return cur;
		}
	}
 
	public static IEnumerable<TSource> WhereMismatch<TSource>(
		this IEnumerable<TSource> source,
		Func<TSource, TSource, bool> predicate
	)
	{
		foreach (var cur in source.WhereAdjacent(predicate))
		{
			yield return cur;
		}
	}
}

To see how to use these functions, here’s a little sample script:

using System;
using UnityEngine;
 
public class TestScript : MonoBehaviour
{
	void Start()
	{
		var list = new[] { 1, 2, 3, 3, 4, 4, 5, 5, 6, 7 };
		var report = "";
		Action<object> log = msg => report += msg + "\n";
 
		log("None() even: " + list.None(x => x % 2 == 0));
 
		log("None() > 10: " + list.None(x => x > 10));
 
		log("ForEach()...");
		list.ForEach(x => log(x));
 
		log("FirstNot() odd: " + list.FirstNot(x => x % 2 == 1));
 
		log("FirstNotOrDefault() < 10: " + list.FirstNotOrDefault(x => x < 10));
 
		log("LastNot() odd: " + list.LastNot(x => x % 2 == 1));
 
		log("LastNotOrDefault() < 10: " + list.LastNotOrDefault(x => x < 10));
 
		log("WhereAdjacent()...");
		list.WhereAdjacent().ForEach(x => log(x));
 
		log("WhereAdjacent() fours...");
		list.WhereAdjacent((a, b) => a == 4 && b == 4).ForEach(x => log(x));
 
		log("WhereMismatch()...");
		list.WhereMismatch().ForEach(x => log(x));
 
		log("WhereMismatch() 2-3...");
		list.WhereMismatch((a, b) => a == 2 && b == 3).ForEach(x => log(x));
 
		Debug.Log(report);
	}
}

If you run this, it’ll print this:

None() even: False
None() > 10: True
ForEach()...
1
2
3
3
4
4
5
5
6
7
FirstNot() odd: 2
FirstNotOrDefault() < 10: 0
LastNot() odd: 6
LastNotOrDefault() < 10: 0
WhereAdjacent()...
3
3
4
4
5
5
6
7
WhereAdjacent() fours...
4
4
5
5
6
7
WhereMismatch()...
1
2
3
3
4
4
5
5
6
7
WhereMismatch() 2-3...
2
3
3
4
4
5
5
6
7

As you can see, it’s very much like using the built-in extension functions that LINQ provides. Hopefully you’ll find these useful. Let me know in the comments if you do, if you find any bugs in any of them, or have any ideas for more that should be added.