C#:compruebe si una cadena contiene alguna subcadena de una lista

C#:compruebe si una cadena contiene alguna subcadena de una lista

Hay muchos escenarios diferentes en los que es posible que desee comparar una cadena con una lista de subcadenas. Tal vez esté lidiando con un manejo de excepciones desordenado y tenga que comparar el mensaje de excepción con una lista de mensajes de error conocidos para determinar si el error es transitorio o no.

Cuando necesite verificar una cadena para obtener una lista de subcadenas, el enfoque más simple es usar list.Any() y string.Contains(), así:

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)

En este artículo, mostraré el enfoque que no es de Linq para esto y luego discutiré el problema relacionado de devolver todas las subcadenas coincidentes.

Enfoque de bucle regular

Este es el enfoque que no es de Linq para este problema:

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)

No hay un beneficio real al usar esto sobre el enfoque de Linq. Ambos realizan lo mismo. Es una cuestión de preferencia.

Pruebas

Aquí están las pruebas que prueban que este código funciona. Note que comienza con los casos especiales. Por lo general, es una buena idea comenzar probando casos especiales, de esa manera no se olvida accidentalmente de manejarlos.

[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)

Devolver todas las subcadenas coincidentes

En lugar de preguntar "¿Esta cadena contiene estas subcadenas?", este problema relacionado pregunta "¿Cuál de las subcadenas contiene la cadena?". Esto puede ser útil si necesita mostrar una lista de las subcadenas coincidentes. Por ejemplo, supongamos que está revisando un cuadro de texto en busca de palabras restringidas y desea mostrar todas las palabras restringidas al usuario para que sepa cuáles borrar.

Uso de Linq

Puede usar list.Where() y string.Contains() para obtener todas las subcadenas coincidentes, como esta:

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)

Dado que este método devuelve IEnumerable, si desea regresar antes, debe devolver Enumerable.Empty().

Método generador no Linq

Esta es la forma no Linq de resolver el problema. Este es un método generador que usa retorno de rendimiento para transmitir subcadenas coincidentes con el código de llamada a medida que se encuentran:

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)

Para regresar antes de un método generador, debe usar yield break en lugar de un retorno regular , de lo contrario obtendrá el error del compilador: “Error CS1622 No se puede devolver un valor de un iterador. Utilice la instrucción yield return para devolver un valor, o yield break para finalizar la iteración”.

Esto funciona igual que el enfoque de Linq.

Nota:Podría devolver una Lista en lugar de devolver un enumerable, pero esto tiene un rendimiento ligeramente peor.

Pruebas

Estas son las pruebas unitarias de casos no especiales para este método WhereContains():

[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)