You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何String.IndexOf仅支持StringComparison,不兼容自定义StringComparer?

Reusing Custom StringComparer Logic with String.IndexOf

Great question! I totally get the frustration of wanting to reuse your carefully crafted comparison logic across different parts of your app—especially when built-in methods like String.IndexOf don't play nice with StringComparer out of the box. Let's break down a clean way to solve this without writing a ton of repetitive extension methods.

Step 1: Refactor Your CustomComparer for Reusability

First, let's tweak your CustomComparer to extract the core string normalization logic into a public static method. This way, both the comparer itself and any future extension methods can share the exact same transformation rules:

using System.Text.RegularExpressions;

public class CustomComparer : StringComparer 
{
    // Compile the regex once for better performance (balance with startup cost if needed)
    private static readonly Regex WhitespaceRegex = new Regex(@"\s+", RegexOptions.Compiled | RegexOptions.CultureInvariant);

    // Make normalization logic public so other code can reuse it
    public static string NormalizeString(string s)
    {
        if (string.IsNullOrEmpty(s))
            return string.Empty;
        
        // Combine trim and normalization to reduce unnecessary string allocations
        var trimmed = s.Trim();
        if (string.IsNullOrEmpty(trimmed))
            return string.Empty;
        
        return WhitespaceRegex.Replace(trimmed.ToLowerInvariant(), " ");
    }

    public override int Compare(string x, string y) 
    {
        return StringComparer.Ordinal.Compare(NormalizeString(x), NormalizeString(y)); 
    } 

    public override bool Equals(string x, string y) 
    {
        // Use string.Equals with Ordinal for consistent, fast comparisons
        return string.Equals(NormalizeString(x), NormalizeString(y), StringComparison.Ordinal);
    } 

    public override int GetHashCode(string obj) 
    {
        return obj == null ? 0 : NormalizeString(obj).GetHashCode(); 
    } 
}

I made a couple small performance tweaks here:

  • Compiled the regex once (instead of every time it's called) to speed up repeated use
  • Simplified the Equals method to use string.Equals directly (cleaner and more consistent)
  • Added null/empty checks early to avoid unnecessary processing

Step 2: Build a Reusable IndexOf Extension Method

Now, let's create a single IndexOf extension method that accepts your CustomComparer (or any StringComparer) and reuses the normalization logic. The key challenge here is that normalizing the entire source string first would lose the original character indices—so we need to check substrings of the original string against the normalized target value:

public static class StringExtensions
{
    public static int IndexOf(this string source, string value, StringComparer comparer)
    {
        // Match .NET's default behavior for edge cases
        if (string.IsNullOrEmpty(source))
            return -1;
        if (string.IsNullOrEmpty(value))
            return 0;

        // Handle our CustomComparer specifically using shared normalization logic
        if (comparer is CustomComparer)
        {
            var normalizedTarget = CustomComparer.NormalizeString(value);
            int targetLength = value.Length;

            // Iterate through possible substrings in the original source
            for (int i = 0; i <= source.Length - targetLength; i++)
            {
                var substring = source.Substring(i, targetLength);
                var normalizedSubstring = CustomComparer.NormalizeString(substring);
                
                if (normalizedSubstring == normalizedTarget)
                {
                    return i; // Return the original index, not the normalized one
                }
            }
            return -1;
        }

        // Fallback for built-in StringComparers (map to matching StringComparison values)
        if (comparer == StringComparer.Ordinal)
            return source.IndexOf(value, StringComparison.Ordinal);
        if (comparer == StringComparer.OrdinalIgnoreCase)
            return source.IndexOf(value, StringComparison.OrdinalIgnoreCase);
        if (comparer == StringComparer.CurrentCulture)
            return source.IndexOf(value, StringComparison.CurrentCulture);
        if (comparer == StringComparer.CurrentCultureIgnoreCase)
            return source.IndexOf(value, StringComparison.CurrentCultureIgnoreCase);
        if (comparer == StringComparer.InvariantCulture)
            return source.IndexOf(value, StringComparison.InvariantCulture);
        if (comparer == StringComparer.InvariantCultureIgnoreCase)
            return source.IndexOf(value, StringComparison.InvariantCultureIgnoreCase);

        // Throw for unsupported comparers (or return -1 if you prefer silent failure)
        throw new NotSupportedException($"StringComparer type {comparer.GetType().Name} is not supported.");
    }
}

How to Use It

Now you can reuse your CustomComparer everywhere, including with IndexOf:

var myComparer = new CustomComparer();
string testSource = "  Hello   World  ";
string testValue = "hello world";

// Use with dictionaries (as before)
var dict = new Dictionary<string, int>(myComparer);
dict.Add(testSource, 1);
Console.WriteLine(dict.ContainsKey(testValue)); // True

// Use with IndexOf via the extension method
int index = testSource.IndexOf(testValue, myComparer);
Console.WriteLine(index); // 0 (since the normalized substring matches starting at index 0)

Bonus: Performance Considerations

If you're dealing with very long strings and need faster IndexOf performance, you could:

  • Precompute normalized versions of frequently used strings and cache them
  • Implement a more optimized substring normalization (e.g., using Span<char> to avoid extra string allocations)
  • Use a more efficient whitespace replacement algorithm than regex (if regex is a bottleneck)

But for most common use cases, the approach above will be fast enough while keeping your code clean and reusable.

内容的提问来源于stack exchange,提问作者Isaiah Shiner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:51:16