C# – Check if a string contains any substring from a list

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)

In this article, I’ll show the non-Linq approach to this and then discuss the related problem of returning all matching substrings.

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)

Leave a Comment