关于代码的一些背景... 我创建了一个称为
ISlice<T>
的类型,它提供了对源项目的部分引用,该源项目可以是集合(例如数组、列表)或字符串。核心支持来自几个实现类,这些实现类支持使用切片的开始和结束标记快速索引原始源中的项。目的是提供类似于Go语言提供的切片功能,同时使用Python样式的索引(即支持正数和负数索引)。为了使切片(
ISlice<T>
的实例)的创建更加容易和“流畅”,我创建了一组扩展方法。例如:static public ISlice<T> Slice<T>(this IList<T> source, int begin, int end)
{
return new ListSlice<T>(source, begin, end);
}
static public ISlice<char> Slice(this string source, int begin, int end)
{
return new StringSlice(source, begin, end);
}
还有其他方法,例如提供可选的开始/结束参数,但以上内容已足够满足我的需求。
这些例程很好用,可以轻松地切割集合或字符串。我还需要一种将切片复制为数组、列表或字符串的方法。这就是事情变得“有趣”的地方。最初,我认为我需要创建ToArray、ToList扩展方法,但后来想起LINQ变体如果您的集合实现了ICollection<T>
,则会执行优化。在我的情况下,ISlice<T>
继承了它,尽管让我感到非常不愉快,因为我不喜欢从Add等方法中抛出NotSupportedExceptions。无论如何,我免费获得了这些。太好了。
那么如何将其转换回字符串呢?由于没有内置支持将IEnumerable<char>
轻松转换回字符串,所以最接近的东西是其中一个string.Concat重载,但它不能像它应该那样高效地处理字符。从设计的角度来看,同样重要的是它不会成为一个“转换”例程。
第一个想法是创建一个ToString扩展方法,但这不起作用,因为ToString是一个实例方法,这意味着它胜过扩展方法,并且永远不会被调用。我可以覆盖ToString,但行为将是不一致的,因为ListSlice<T>
需要为T是char时特别处理其ToString。我不喜欢这个,因为当类型参数是char时,ToString会给出有用的东西,但在其他情况下会给出类名。此外,如果将来创建其他切片类型,则必须创建一个公共基类以确保相同的行为,否则每个类都必须实现此相同的检查。接口上的扩展方法将更加优雅地处理它。
扩展方法引导我进入了命名约定问题。显然使用ToString是最好的选择,但正如前面所述,它是不允许的。我可以给它取一个不同的名字,但是什么?ToNewString?NewString?CreateString?在To系列方法中的某个东西将使它与ToArray/ToList例程一起使用,但在智能感知和代码编辑器中,ToNewString看起来很“奇怪”。NewString/CreateString不像你必须知道要查找它们那样易于发现。它不符合To系列方法提供的“转换方法”模式。
采用覆盖ToString并接受硬编码到ListSlice<T>
实现和其他实现中的不一致行为?采用更灵活但命名可能更差的扩展方法路线?有我没有考虑过的第三种选择吗?
我的直觉告诉我要采用ToString,尽管我对此有所保留,但它也让我想到了...您是否会考虑ToString在集合/可枚举类型上给您提供有用的输出?那是否违反了最小惊讶原则?
更新
大多数切片操作的实现都提供了数据的副本,尽管是一个子集,来自用于该切片的任何源。这在大多数情况下是完全可以接受的,并且使得 API 更加清晰简洁,因为你可以简单地返回相同的数据类型。如果你切片一个列表,你会返回一个仅包含指定范围内项目的列表。如果你切片一个字符串,你会返回一个字符串。以此类推。上述我所描述的切片操作解决了在使用约束时产生的问题,这种行为是不可取的。例如,如果你处理大型数据集,切片操作将导致不必要的额外内存分配,更不用说复制数据的性能影响了。特别是在切片在到达最终结果之前需要进一步处理的情况下,这一点尤其明显。因此,切片实现的目标是引用较大数据集中的数据,以避免在没有必要的情况下复制信息,直到有益为止。
问题在于,在处理结束时,希望将基于切片的处理数据转换回更易于传递到其他 API 的 API 和 .NET 友好类型,如列表、数组和字符串。它还允许你丢弃切片,从而也丢弃了切片所引用的大型数据集。
IEnumerable<char>
这样的语句。 - StriplingWarrior