BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

LINQ
.NET 3.5+

A LINQ Style Replace Operator

It can be useful to search a sequence of items for a specific value or object and replace all matching instances with a new value. Language-Integrated Query (LINQ) does not provide a standard query operator for this purpose but creating one is simple.

Replace

The string class includes a method, named Replace, that searches through a string for a specific character or substring. Wherever a match is found, the item is replaced with an alternative value.

It can be useful to follow a similar process with collections. Rather than searching within the characters of a string, you may need to look for items in a sequence that match a given value, reference or set of criteria, and replace these with a substitute. Unfortunately, neither the standard collection types nor the extension methods of Language-Integrated Query (LINQ) provide this functionality.

In this article we'll create a Replace method. It will behave in the usual manner of a LINQ operator, acting upon and returning IEnumerable<T> and using deferred execution. We'll allow matching of discrete values for simple search and replace situations. For more complex scenarios we'll allow the specification of an equality comparer. You could use a custom comparer class or a LambdaEqualityComparer<T> to apply any criteria whilst performing matching.

Creating the Class

To begin we need a static class in which to create our extension method. Create a new class named, "ReplaceExtensions", and modify the class definition as follows:

public static class ReplaceExtensions
{
}

Creating the Public Methods

We need to create two public extension methods. The more complex one, which we will create first, allows the search and replace operation to work with a custom comparer. The method has four parameters, which accept the sequence to search, the item to find, the substitute value and the equality comparer. The body of the method is simple; it validates the parameters and calls ReplaceImpl, which performs the replacement operations using an iterator for deferred execution.

public static IEnumerable<T> Replace<T>(
    this IEnumerable<T> sequence, T find, T replaceWith, IEqualityComparer<T> comparer)
{
    if (sequence == null) throw new ArgumentNullException("sequence");
    if (comparer == null) throw new ArgumentNullException("comparer");

    return ReplaceImpl(sequence, find, replaceWith, comparer);
}

The second overloaded version of Replace omits the equality comparer parameter. This makes for simpler, more readable code when searching for discrete values using the default comparer for the type specified in the generic type parameter. We don't need to validate the provided arguments in this method, as it simply calls the previous overload, adding the comparer.

public static IEnumerable<T> Replace<T>(
    this IEnumerable<T> sequence, T find, T replaceWith)
{
    return Replace(sequence, find, replaceWith, EqualityComparer<T>.Default);
}

Creating the Implementation Method

To complete the class we need to add the private implementation method. This loops through all of the items in the provided sequence and uses the comparer to determine if each element matches the value we are looking for. When we find a match, we return the alternative value using yield return. If the item does not meet the criteria of the comparer, we return the original value.

private static IEnumerable<T> ReplaceImpl<T>(
    IEnumerable<T> sequence, T find, T replaceWith, IEqualityComparer<T> comparer)
{
    foreach (T item in sequence)
    {
        bool match = comparer.Equals(find, item);
        T x = match ? replaceWith : item;
        yield return x;
    }
}

Testing the Method

Let's perform a couple of tests of the new extension method. The first takes an array of integers and replaces all of the threes with zeroes.

int[] values = new int[] { 1, 2, 3, 4, 5, 4, 3, 2, 1 };
int[] replaced = values.Replace(3, 0).ToArray();

/* RESULTS

1, 2, 0, 4, 5, 4, 0, 2, 1

*/

For a more complex example we can use a sequence of strings as our source. In the following code we start by replacing all instances of the lower case 'b' with a hyphen. In the second replacement operation we add a case-insensitive string comparer to replace all letter B's, whether capitalised or lower case.

string[] strings = new string[] { "A", "B", "C", "D", "a", "b", "c", "d" };
string[] replacedCS = strings.Replace("b", "-").ToArray();
string[] replacedCI = strings.Replace("b", "-", StringComparer.InvariantCultureIgnoreCase).ToArray();

/* RESULTS

replacedCS = { "A", "B", "C", "D", "a", "-", "c", "d" }
replacedCI = { "A", "-", "C", "D", "a", "-", "c", "d" }

*/
15 December 2012