C#:controlla se una stringa contiene una sottostringa da un elenco

C#:controlla se una stringa contiene una sottostringa da un elenco

Esistono molti scenari diversi in cui potresti voler controllare una stringa rispetto a un elenco di sottostringhe. Forse hai a che fare con una gestione disordinata delle eccezioni e devi confrontare il messaggio di eccezione con un elenco di messaggi di errore noti per determinare se l'errore è temporaneo o meno.

Quando devi controllare una stringa per un elenco di sottostringhe, l'approccio più semplice consiste nell'usare list.Any() e string.Contains(), in questo modo:

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 questo articolo, mostrerò l'approccio non Linq a questo e poi discuterò il problema correlato della restituzione di tutte le sottostringhe corrispondenti.

Approccio ad anello regolare

Ecco l'approccio non Linq a questo 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)

Non c'è alcun vantaggio reale nell'usare questo rispetto all'approccio Linq. Entrambi si comportano allo stesso modo. È una questione di preferenza.

Prove

Ecco i test che dimostrano che questo codice funziona. Si noti che inizia con i casi speciali. Di solito è una buona idea iniziare testando casi speciali, in modo da non dimenticarti accidentalmente di gestirli.

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

Restituisce tutte le sottostringhe corrispondenti

Invece di chiedere "Questa stringa contiene queste sottostringhe?", questo problema correlato chiede "Quale delle sottostringhe contiene la stringa?". Questo può essere utile se è necessario visualizzare un elenco delle sottostringhe corrispondenti. Ad esempio, supponiamo che tu stia selezionando una casella di testo per eventuali parole limitate e desideri visualizzare tutte le parole limitate all'utente in modo che sappia quali cancellare.

Utilizzo di Linq

Puoi usare list.Where() e string.Contains() per ottenere tutte le sottostringhe corrispondenti, in questo modo:

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)

Poiché questo metodo restituisce IEnumerable, se vuoi restituire in anticipo, devi restituire Enumerable.Empty().

Non-Linq, metodo generatore

Ecco il modo non Linq per risolvere il problema. Questo è un metodo generatore che utilizza il rendimento rendimento per trasmettere le sottostringhe corrispondenti al codice chiamante quando vengono trovate:

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)

Per tornare in anticipo da un metodo generatore, devi utilizzare pausa rendimento invece di un normale ritorno , altrimenti otterrai l'errore del compilatore: "Errore CS1622 Impossibile restituire un valore da un iteratore. Usa l'istruzione yield return per restituire un valore o yield break per terminare l'iterazione."

Questo funziona come l'approccio Linq.

Nota:potresti restituire un List invece di restituire un enumerable, ma questo ha prestazioni leggermente peggiori.

Prove

Ecco gli unit test non speciali per questo metodo 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)