There are many different scenarios where you might want to check a string against a list of substrings. Perhaps you’re dealing with messy exception handling and have to compare the exception message against a list of known error messages to determine if the error is transient or not.
When you need to check a string for a list of substrings, the simplest approach is to use list.Any() and string.Contains(), like this:
using System.Linq;
public static bool ContainsAny(string s, List<string> substrings)
{
if (string.IsNullOrEmpty(s) || substrings == null)
return false;
return substrings.Any(substring => s.Contains(substring, StringComparison.CurrentCultureIgnoreCase));
}
Code language: C# (cs)
Note: This is doing a case-insensitive comparison with string.Contains().
In this article, I’ll show the non-Linq approach to this and then discuss the related problem of returning all matching substrings.
Table of Contents
Regular loop approach
Here’s the non-Linq approach to this problem:
public static bool ContainsAny(string stringToTest, List<string> substrings)
{
if (string.IsNullOrEmpty(stringToTest) || substrings == null)
return false;
foreach (var substring in substrings)
{
if (stringToTest.Contains(substring, StringComparison.CurrentCultureIgnoreCase))
return true;
}
return false;
}
Code language: C# (cs)
There’s no real benefit to using this over the Linq approach. They both perform the same. It’s a matter of preference.
Tests
Here are the tests that prove this code works. Notice it starts with the special cases. It’s usually a good idea to start by testing special cases, that way you don’t accidently forget to handle them.
[TestClass()]
public class StringUtilTests
{
#region Special cases
[DataRow(null)]
[DataRow("")]
[TestMethod()]
public void ContainsAny_WhenStringIsNullOrEmpty_ReturnsFalse(string stringToTest)
{
//arrange
var substrings = new List<string>() { "a" };
//act
var actual = StringUtil.ContainsAny(stringToTest, substrings);
//assert
Assert.IsFalse(actual);
}
[TestMethod()]
public void ContainsAny_WhenSubstringsListIsNull_ReturnsFalse()
{
//arrange
string stringToTest = "a";
List<string> substrings = null;
//act
var actual = StringUtil.ContainsAny(stringToTest, substrings);
//assert
Assert.IsFalse(actual);
}
[TestMethod()]
public void ContainsAny_WhenSubstringsListIsEmpty_ReturnsFalse()
{
//arrange
string stringToTest = "a";
List<string> substrings = new List<string>();
//act
var actual = StringUtil.ContainsAny(stringToTest, substrings);
//assert
Assert.IsFalse(actual);
}
#endregion
[TestMethod()]
public void ContainsAny_WhenContainsASubstring_ReturnsTrue()
{
//arrange
string stringToTest = "abc";
List<string> substrings = new List<string>() { "a" };
//act
var actual = StringUtil.ContainsAny(stringToTest, substrings);
//assert
Assert.IsTrue(actual);
}
[TestMethod()]
public void ContainsAny_WhenContainsASubstringWithDifferentCasing_ReturnsTrue()
{
//arrange
string stringToTest = "ABC";
List<string> substrings = new List<string>() { "a" };
//act
var actual = StringUtil.ContainsAny(stringToTest, substrings);
//assert
Assert.IsTrue(actual);
}
[TestMethod()]
public void ContainsAny_WhenDoesntContainASubtring_ReturnsFalse()
{
//arrange
string stringToTest = "abc";
List<string> substrings = new List<string>() { "d" };
//act
var actual = StringUtil.ContainsAny(stringToTest, substrings);
//assert
Assert.IsFalse(actual);
}
}
Code language: C# (cs)
Return all matching substrings
Instead of asking “Does this string contain of these substring?”, this related problem asks “Which of the substrings does the string contain?”. This may be useful if you need to display a list of the matching substrings. For example, let’s say you are checking a textbox for any restricted words and you want to display all the restricted words to the user so they know which ones to erase.
Using Linq
You can use list.Where() and string.Contains() to get all the matching substrings, like this:
using System.Linq;
public static IEnumerable<string> WhereContains(string stringToTest, List<string> substrings)
{
if (string.IsNullOrEmpty(stringToTest) || substrings == null)
return Enumerable.Empty<string>();
return substrings.Where(substring => stringToTest.Contains(substring, StringComparison.CurrentCultureIgnoreCase));
}
Code language: C# (cs)
Since this method is returning IEnumerable<string>, if you want to return early, you have to return Enumerable.Empty<string>().
Non-Linq, generator method
Here’s the non-Linq way to solve the problem. This is a generator method that uses yield return to stream matching substrings to the calling code as they are found:
public static IEnumerable<string> WhereContains(string stringToTest, List<string> substrings)
{
if (string.IsNullOrEmpty(stringToTest) || substrings == null)
yield break;
foreach (var substring in substrings)
{
if (stringToTest.Contains(substring, StringComparison.CurrentCultureIgnoreCase))
yield return substring;
}
}
Code language: C# (cs)
To return early from a generator method, you have to use yield break instead of a regular return, otherwise you’ll get the compiler error: “Error CS1622 Cannot return a value from an iterator. Use the yield return statement to return a value, or yield break to end the iteration.”
This performs the same as the Linq approach.
Note: You could return a List<string> instead of returning an enumerable, but this has slightly worse performance.
Tests
Here are the non-special case unit tests for this WhereContains() method:
[TestMethod()]
public void WhereContains_WhenContainsASubstring_ReturnsIt()
{
//arrange
string stringToTest = "abc";
var substrings = new List<string>() { "a" };
//act
var actual = SubstringUtil.WhereContains(stringToTest, substrings);
//assert
CollectionAssert.AreEqual(substrings, actual.ToList());
}
[TestMethod()]
public void WhereContains_OnlyReturnsMatchingSubstrings()
{
//arrange
string stringToTest = "abc";
var substrings = new List<string>() { "a", "d" };
var expected = new List<string>() { "a" };
//act
var actual = SubstringUtil.WhereContains(stringToTest, substrings);
//assert
CollectionAssert.AreEqual(expected, actual.ToList());
}
[TestMethod()]
public void WhereContains_WhenNoMatching_ReturnEmptyList()
{
//arrange
string stringToTest = "abc";
var substrings = new List<string>() { "d" };
var expected = new List<string>();
//act
var actual = SubstringUtil.WhereContains(stringToTest, substrings);
//assert
CollectionAssert.AreEqual(expected, actual.ToList());
}
Code language: C# (cs)
Comments are closed.