65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的范围

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (13投票s)

2013年2月1日

CPOL

5分钟阅读

viewsIcon

98778

介绍 C# 中实现的范围这一知名概念。

我必须承认,尽管我非常热爱 C# 编程语言,但我有时会觉得它缺少一些东西。当这种情况发生时,我通常会选择生闷气,或者,更好的是,开始一次次地实现类似 monad 的东西,以便按照我的方式做事。这篇文章又是一个关于我想要的一个功能的叙述,这个功能叫做范围

注意:本文没有附带源代码。事实上,这都非常琐碎。

范围?范围是什么意思?

好吧,让我简要解释一下:假设你有一个字符串,你想删除它的最后一个字符。你会怎么做?嗯,典型的解决方案是写

s.Substring(0, s.Length-1)

这在技术上是正确的,但感觉就像你用手术刀在砍僵尸(与“用链锯做脑部手术”相反)。事实上,像 Python(以及 Boo,间接)这样的某些语言允许你更简洁地表达这种切片数组的想法。具体来说,你可以使用负范围值来从数组末尾测量距离。(或者,视情况而定,从数组之后的一个元素末尾测量。)

不幸的是,默认情况下 C# 让我们很无奈,因为我们无法用像 s[0:-1] 这样漂亮的写法来替换前面的表达式。或者有办法吗?

字符串扩展

让我们稍微推测一下。假设你决定是时候用一个简单的扩展方法来结束 Substring() 的专横跋扈了,这个方法能让你输入各种奇怪的值,甚至留空

public static string Substring(this string obj, int start = 0, int end = -1)
{
  if (end < 0) end = obj.Length + end;
  return obj.Substring(start, end-start);
}

但这行不通,因为已经有一个名为 Substring() 的方法,它接受两个参数(尽管名称不同)。那么,我们能做什么呢?嗯,我们可以放弃并认输,将方法重命名为类似 SubString 的名字(愿 VB.NET 用户宽恕我们),或者,更好的是,尝试将范围性(或带范围性)的整个概念封装在一个单独的结构中。

范围结构

public struct Range
{
  public int Start;
  public int End;
}

很棒,对吧?我们现在可以在我们的 API 中使用这个东西了,我们之前写的那个奇怪的扩展方法现在可以更有意义了

public static string Substring(this string obj, Range range)
{
  if (range.End < 0) range.End = obj.Length + range.End;
  return obj.Substring(range.Start, range.End - range.Start);
}

然而,调用仍然很奇怪,因为我们需要写类似这样的东西

string s = "abcde";
string ss = s.Substring(new Range{Start=1,End=-2});

我相信你同意这很难看,而且不值得付出努力。我们可以将其缩小到一个集合初始化器 Range{1,-2},但这需要 Range 实现 IEnumerable,这是不符合习惯的,并且通常是坏的。一定还有其他方法。

范围构造

确实有更好的方法。还记得扩展方法吗?嗯,我们可以对任何类型使用它们,包括,你猜对了,int。所以,事不宜迟,我为你带来流畅的范围构建器

public static Range to(this int start, int end)
{
  return new Range {Start = start, End = end};
}

很琐碎,对吧?现在你就有了一种方法可以快速定义一个具有奇怪负值的范围(如果你必须的话),如下所示

string ss = s.Substring(1.to(-2));

这难道不是更好吗?是的,我知道它不完美,但猜猜怎么着,我们生活在一个不完美的世界里。

就是这样吗?

这个问题的答案是哲学性的,取决于你到底想要多少。我展示了一个使用范围和 Substring 扩展的非常简单的例子,但通常,范围用于从数组/矩阵/向量中提取切片。例如,将这个概念应用于枚举,我们可以得到类似这样的东西

public static IEnumerable<T> At<T>(this IEnumerable<T> obj, Range range)
{
  var list = obj.ToList();
  if (range.Start < 0) range.Start += list.Count;
  if (range.End < 0) range.End += list.Count;
  return list.Skip(range.Start).Take(range.End - range.Start);
}

上面的代码从任何 IEnumerable(列表、数组等)中提取一个切片,给出了提供的范围。显然,在实际场景中,对 range 参数会有更严格的检查,以及有关将整个集合具体化的明显问题。以上仅为说明目的。

不过,情况会变得更好。例如,如果上面的 range 参数实际上是 params Range[] 类型呢?那么,就像变魔术一样,你可以从一个集合中提取多个切片并将它们全部连接起来。那不是很棒吗?

但是范围最终是非常脆弱的东西,只对表示某物的开始和结束有用。如果你想,比如说,将值 0 分配给数组中的特定元素怎么办?你会创建一个 Set() 扩展方法吗?也许吧,但有一个更强大的选择。

视图

视图只是一个范围加上它所关联的对象。这个区别至关重要,因为一旦你同时拥有了范围和目标对象,你就可以做各种各样不乖的事情,比如将范围内的所有值设置为一个特定的值。让我们来充实一下这个视图的东西。首先,这个类看起来很容易理解

public class ArrayView<T>
{
  public Range Range;
  public T[] Array;
  public ArrayView(T[] array, Range range)
  {
    Array = array;
    if (range < 0) // you know the drill
    Range = range;
  }
}

我用数组作为说明,也可能是别的东西!重点是,现在我们不再有 At 方法,而是可以有一个 View 方法

public static ArrayView<T> View<T>(this T[] array, Range range)
{
  return new ArrayView<T>(array, range);
}

视图作用于一个预先转换的范围(负值,还记得吗?),但它能让我们做什么呢?嗯,首先,我们可以实现一个基本的索引器

public T this[int i]
{
  get {
    // don't forget to pre-transform, then
    return Array[Range.Start + i];
  }
  set {
    // same here, then
    Array[Range.Start + i] = value;
  }
}

以下是使用方法

var a = new[] {1, 2, 3, 4};
var b = a.View(2.to(3));
b[0] = 42;
Console.WriteLine(a[2]);

是的,a[2] 确实等于 42,正如你所期望的。

视图的乐趣

那么,你实际上可以用视图做什么呢?嗯,视图的视图怎么样?毕竟,没有什么能阻止我们再创建一个接受另一个 Range 的索引器,所以…

public ArrayView<T> this[int start, int end]
{
  get
  {
    // pretransform, as always
    return new ArrayView<T>(Array,
      new Range
      {
        Start = start + Range.Start,
        End = end + Range.End
      });
  }
}

是的,我实际上在该索引器中使用了两个参数来更流畅地表示范围。如果你更喜欢使用 x.to(y) 的表示法,只需创建一个重载。事实上,对于一般情况,有一个重载是个好主意。但是,设置器呢?嗯,在这种特殊情况下,唯一能救你的就是 Set() 方法,它会设置每个元素的。在这种情况下,使用 = 运算符没有多大意义。

以下是一些你可以用视图做的其他有趣的事情

  • 实现集合操作。显然,这只在视图引用的数组类型相同时才有意义。

  • 实现 IEnumerable。这有点道理,这样你就可以迭代数组的视图了。

结论

在蝙蝠侠电影中的一部电影里,小丑角色说的话大致是“看看你只用汽油能制造多少混乱?”。嗯,这篇文章是扩展方法能做多少事情的另一个例子——一个不合理强大的功能,可以让你创造出具有魔法般能力的对象。

你的使用体验,不可避免地,会有所不同。但是范围可以很有用,其方式超出了本文的范围。祝你好运,并敬请期待更多关于扩展方法的不合理用法!

© . All rights reserved.