C# LINQ is very powerful, but standard methods alone may not meet specific project requirements. In such cases, defining extension methods for the IEnumerable<T> interface allows you to add custom processing that can be called with the same feel as standard LINQ methods (like Where or Select).
Here, I will introduce an implementation example that extends the standard Contains method to accept a conditional expression (predicate). Note that while standard LINQ has the Any method with similar functionality, we will implement this as an overload of Contains for learning purposes and naming consistency.
Implementation Rules for Extension Methods
When extending LINQ to Objects (IEnumerable<T>), keep the following points in mind:
- Versatility: Use generics (
<T>) to support collections of any type. - Arguments: Specify
this IEnumerable<T> sourceas the first argument. - Condition: Use
Func<T, bool>(orPredicate<T>) to receive the logic.
Practical Code Example: Implementing Conditional Contains
The following code is an extension method that determines if an element satisfying a specified condition is included in the sequence.
using System;
using System.Collections.Generic;
namespace CustomLinqExtensions
{
class Program
{
static void Main()
{
// Array of strings (Data source)
var drinks = new[]
{
"wine", "sake", "beer", "whisky", "liqueur", "cocktail", "champagne"
};
// Using the custom Contains extension method
// Condition: Does it contain an element with 8 or more characters?
bool hasLongNameDrink = drinks.Contains(x => x.Length >= 8);
Console.WriteLine($"Is there a drink with 8+ chars?: {hasLongNameDrink}"); // True (champagne)
// Array of integers
var nums = new[] { 1, 3, 5, 7, 9, 11 };
// Condition: Does it contain an even number?
bool hasEvenNumber = nums.Contains(x => x % 2 == 0);
Console.WriteLine($"Is there an even number?: {hasEvenNumber}"); // False
}
}
// Static class defining extension methods
public static class EnumerableExtensions
{
/// <summary>
/// Determines whether a sequence contains any element that satisfies a condition.
/// (Functionally equivalent to standard Any(), but defined as Contains)
/// </summary>
/// <typeparam name="T">The type of the elements</typeparam>
/// <param name="source">Target sequence</param>
/// <param name="predicate">Function to test the condition</param>
/// <returns>true if any element satisfies the condition; otherwise, false</returns>
public static bool Contains<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
// Argument null checks (for robust implementation)
if (source == null) throw new ArgumentNullException(nameof(source));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
foreach (var element in source)
{
// Execute the condition function. If true, return true immediately.
if (predicate(element))
{
return true;
}
}
// Return false if no match is found after looping through all elements
return false;
}
}
}
Execution Result
Is there a drink with 8+ chars?: True
Is there an even number?: False
Technical Points and Logic
1. Adopting Func<T, bool>
Although Predicate<T> can be used, it is common practice in the current LINQ standard library to use Func<T, bool>. Both mean “accept T and return bool,” but Func has higher affinity with standard methods like Where and Select.
2. Correct Loop Logic
A common mistake in this type of implementation is to return false immediately if the first element does not match.
Incorrect Logic:
foreach (var element in source) {
if (predicate(element)) return true;
else return false; // This is wrong! It stops checking after the first item.
}
Correct Logic: The correct structure is “return true immediately if a match is found inside the loop,” and “return false only after finishing the loop.”
3. Extension via the this Keyword
By adding this to the first argument of the method definition, you can write source.Contains(...) as if it were an instance method of the source object. This significantly improves code readability.
Summary
By extending IEnumerable<T>, you can naturally incorporate custom search logic or conversion processing into LINQ that is not found in the standard library. If you have frequent loop processing patterns in your project, consider converting them into extension methods to improve reusability.
