如何用泛型实现string与自定义StringEditor的通用IndexOf扩展方法?
解决方案
针对你的问题,确实无法直接通过单一泛型约束同时兼容string和自定义StringEditor(因为string是密封类且无法修改),但可以通过以下几种方式消除代码重复,同时满足直接索引访问的性能与语法需求:
1. 提取核心逻辑到私有方法(最简方案)
把重复的遍历逻辑抽成独立的私有方法,让两个扩展方法仅负责传递类型相关的参数(长度和字符访问委托),既保留原有的性能,又彻底消除代码重复:
public static class MyExtensions { public static int IndexOf(this string s, Func<char, bool> predicate, int startIndex) { return IndexOfCore(s.Length, i => s[i], predicate, startIndex); } public static int IndexOf(this StringEditor s, Func<char, bool> predicate, int startIndex) { return IndexOfCore(s.Length, i => s[i], predicate, startIndex); } // 核心逻辑仅实现一次 private static int IndexOfCore(int length, Func<int, char> getChar, Func<char, bool> predicate, int startIndex) { for (int i = startIndex; i < length; i++) { if (predicate(getChar(i))) return i; } return -1; } }
这种方式无需修改StringEditor的结构,委托直接绑定到索引器访问,性能与原代码完全一致。
2. 定义索引访问接口+适配包装(泛型友好方案)
如果希望对外提供统一的泛型接口,可以定义包含长度属性和字符索引器的接口,让StringEditor实现它,再通过内部包装类适配string:
步骤1:定义索引访问接口
public interface ICharIndexed { int Length { get; } char this[int index] { get; } }
步骤2:修改StringEditor实现接口
public class StringEditor : ICharIndexed { // 原有成员保持不变,只需实现接口的Length和索引器 public int Length { get; /* 原有实现逻辑 */ } public char this[int index] { get { /* 原有索引访问逻辑 */ } } }
步骤3:编写泛型扩展方法
public static class MyExtensions { public static int IndexOf<T>(this T s, Func<char, bool> predicate, int startIndex) where T : notnull { return s switch { string str => IndexOfCore(new StringWrapper(str), predicate, startIndex), ICharIndexed indexed => IndexOfCore(indexed, predicate, startIndex), _ => throw new ArgumentException("类型不支持字符索引访问", nameof(s)) }; } private static int IndexOfCore(ICharIndexed s, Func<char, bool> predicate, int startIndex) { for (int i = startIndex; i < s.Length; i++) { if (predicate(s[i])) return i; } return -1; } // 内部包装类,将string适配为ICharIndexed接口 private class StringWrapper : ICharIndexed { private readonly string _str; public StringWrapper(string str) => _str = str; public int Length => _str.Length; public char this[int index] => _str[index]; } }
这种方式对外提供了统一的泛型调用入口,StringWrapper作为内部类不会暴露给外部代码,同时保留了直接索引访问的性能。
3. 利用C# 10+静态抽象成员(进阶方案)
如果使用C# 10或更高版本,可以借助静态抽象接口成员定义通用的字符序列约束,适配string和StringEditor:
步骤1:定义带静态抽象成员的接口
public interface ICharSequence { static abstract int GetLength<T>(T sequence) where T : ICharSequence; static abstract char GetCharAt<T>(T sequence, int index) where T : ICharSequence; }
步骤2:让StringEditor实现接口
public class StringEditor : ICharSequence { public int Length { get; /* 原有实现逻辑 */ } public char this[int index] { get { /* 原有索引访问逻辑 */ } } public static int GetLength<StringEditor>(StringEditor sequence) => sequence.Length; public static char GetCharAt<StringEditor>(StringEditor sequence, int index) => sequence[index]; }
步骤3:编写泛型方法并适配string
public static class MyExtensions { public static int IndexOf<T>(this T s, Func<char, bool> predicate, int startIndex) where T : notnull { if (typeof(T) == typeof(string)) { var str = (string)(object)s; for (int i = startIndex; i < str.Length; i++) { if (predicate(str[i])) return i; } return -1; } if (s is ICharSequence seq) { int length = ICharSequence.GetLength<T>(s); for (int i = startIndex; i < length; i++) { char c = ICharSequence.GetCharAt<T>(s, i); if (predicate(c)) return i; } return -1; } throw new ArgumentException("不支持的类型", nameof(s)); } }
这种方式适合需要大量类似泛型方法的场景,但对C#版本有要求,实现相对复杂。
内容的提问来源于stack exchange,提问作者Jonathan Wood
相关产品推荐
相关产品推荐

