什么是比较 string.tolower 更好的选择?

What is a better alternative to comparing string.tolower?

根据我过去的经验和 Whosebug,我了解到 String.ToLower() 的性能很差。 现在我有以下问题,当特定单词在一个巨大的列表中时,我需要过滤掉或执行特定操作。

我要修复的旧方法:

if (input.Any(i => i.ToLower() == "alle" || i.ToLower() == "all" || i.ToLower() == "none") 
{
    // do something
}

我正在考虑使用哈希集,但我质疑性能以及它如何处理区分大小写,我基本上不关心区分大小写。使用哈希集对我来说有意义吗?

我目前的解决方案建议:

var unwantedInputsSet = new HashSet<string> {"alle", "all", "none"};
if (input.Any(i => i => unwantedInputsSet.Contains(i))) 
{
    // do something
}

有没有更好的选择。 您有什么想法可以更好地解决这个问题吗?

你可以通过comparer to the HashSet, for example StringComparer.InvariantCultureIgnoreCase:

var unwantedInputsSet = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) {"alle", "all", "none"};
if (input.Any(i => unwantedInputsSet.Contains(i))) 
{
    // do something
}

或者,按照评论中的建议,使用模式匹配:

if (input.Any(i => i.ToLower() is "alle" or "all" or "none") 
{
    // do something
}

这应该 turned by compiler 到类似于你的代码中(尽管 ToLower 应该被调用一次)。

至于性能 - 它可能在很大程度上取决于实际数据,您应该使用预期的数据集来衡量它。对于小型搜索集,HashSet 的性能可能比以下几个比较差:

var cmpr = StringComparison.InvariantCultureIgnoreCase;
if (input.Any(i => string.Equals(i, "alle", cmpr) || string.Equals(i, "all", cmpr) || string.Equals(i, "none", cmpr)))
{
    // do something
}

对于此类基准测试,我建议查看 BenchmarkDotNet

您可以在声明 HashSet<T> 时指定 IEqualityComparer<T>

static readonly HashSet<string> unwanted =
    new(StringComparer.OrdinalIgnoreCase) { "alle", "all", "none" };

此外,我会将其设为静态只读字段,以防止重复创建。

HashSet解决方案的优点是可以方便地扩展到更多的情况。它还允许您从配置文件中读取不需要的词。

从 C# 9.0 开始,您可以使用 Target-typed new expressions。如果您使用的是 C# 9.0 之前的版本:

static readonly HashSet<string> unwanted =
    new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "alle", "all", "none" };

用法:

if (input.Any(i => unwanted.Contains(i))) 
{
    // do something
}

没有 ToLower()!

如果您希望代码具有最高性能,请使用带有 StringComparison.OrdinalIgnoreCase 参数的 string.Equals() 进行字符串比较。

如果你想让代码更具可读性并且不太关心性能,你可以使用一个简单的扩展方法来比较一个字符串和多个目标字符串:

public static class StringExt
{
    public static bool EqualsAnyOf(this string value, params string[] targets)
    {
        return targets.Any(target => target.Equals(value, StringComparison.OrdinalIgnoreCase));
    }
}

那么你可以这样写你的代码:

if (input.Any(item => item.EqualsAnyOf("alle", "all", "none")))
{
    // ...
}

如果你真的想变得很花哨,你也可以写一个 AnyEqualsAnyOf() 扩展方法:

public static class StringExt
{
    public static bool EqualsAnyOf(this string value, params string[] targets)
    {
        return targets.Any(target => target.Equals(value, StringComparison.OrdinalIgnoreCase));
    }

    public static bool AnyEqualsAnyOf(this IEnumerable<string> sequence, params string[] targets)
    {
        return sequence.Any(item => item.EqualsAnyOf(targets));
    }
}

然后您的代码将是:

if (input.AnyEqualsAnyOf("alle", "all", "none"))
{
    // ...
}

我个人认为这样做不值得,除非您发现自己经常编写此类代码,但这当然是一种选择。