【问题标题】:Create an array that points to only part of another array?创建一个仅指向另一个数组的一部分的数组?
【发布时间】:2013-09-04 19:52:40
【问题描述】:

我有一个包含引用类型元素的巨大数组,我想创建许多其他数组,它们基本上只是指向这个大数组的特定部分。

换句话说,我想创建“索引器”或“带长度的指针”。

在 C++ 中,使用指针很容易做到这一点,并为每个指针分配一个长度,例如创建一个包含具有长度的指针的结构。

如何在 C#/.NET 中实现这一点?

关键是要避免复制任何东西,我只想要指向内存中已经存在的数组中特定部分的指针。

有什么想法吗?

【问题讨论】:

  • 你试过skip..take吗?
  • 如果你愿意,你可以使用指针
  • 数组使用整数进行索引,因此显然“指向数组中特定位置的指针”是一个整数。长度也表示为整数。所以你在这里谈论一对整数。您可以将它们打包在一个 Tuple<int, int> 中,或者如果您喜欢更具描述性的名称,也可以创建自己的 struct
  • @wudzik,我认为 c# 中的指针与 goto 语句一样被回避。
  • 这可能与stackoverflow.com/questions/2333574/… 相关,但@Jon 准确地描述了我的建议。

标签: c# arrays pointers


【解决方案1】:

Jon 使用ArraySegment<T> 的建议可能是您想要的。但是,如果您想用 C++ 中的方式表示指向数组内部的指针,这里有一些代码。不提供任何明示或暗示的保证,使用风险自负。

此代码不会以任何方式跟踪内部指针的“长度”,但如果您愿意,可以很容易地添加该功能。

internal struct ArrayPtr<T>
{
  public static ArrayPtr<T> Null { get { return default(ArrayPtr<T>); } }
  private readonly T[] source;
  private readonly int index;

  private ArrayPtr(ArrayPtr<T> old, int delta)
  {
    this.source = old.source;
    this.index = old.index + delta;
    Debug.Assert(index >= 0);
    Debug.Assert(index == 0 || this.source != null && index < this.source.Length);
  }

  public ArrayPtr(T[] source)
  {
    this.source = source;
    index = 0;
  }

  public bool IsNull()
  {
    return this.source == null;
  }

  public static bool operator <(ArrayPtr<T> a, ArrayPtr<T> b)
  {
    Debug.Assert(Object.ReferenceEquals(a.source, b.source));
    return a.index < b.index;
  }

  public static bool operator >(ArrayPtr<T> a, ArrayPtr<T> b)
  {
    Debug.Assert(Object.ReferenceEquals(a.source, b.source));
    return a.index > b.index;
  }

  public static bool operator <=(ArrayPtr<T> a, ArrayPtr<T> b)
  {
    Debug.Assert(Object.ReferenceEquals(a.source, b.source));
    return a.index <= b.index;
  }

  public static bool operator >=(ArrayPtr<T> a, ArrayPtr<T> b)
  {
    Debug.Assert(Object.ReferenceEquals(a.source, b.source));
    return a.index >= b.index;
  }

  public static int operator -(ArrayPtr<T> a, ArrayPtr<T> b)
  {
    Debug.Assert(Object.ReferenceEquals(a.source, b.source));
    return a.index - b.index;
  }

  public static ArrayPtr<T> operator +(ArrayPtr<T> a, int count)
  {
    return new ArrayPtr<T>(a, +count);
  }

  public static ArrayPtr<T> operator -(ArrayPtr<T> a, int count)
  {
    return new ArrayPtr<T>(a, -count);
  }

  public static ArrayPtr<T> operator ++(ArrayPtr<T> a)
  {
    return a + 1;
  }

  public static ArrayPtr<T> operator --(ArrayPtr<T> a)
  {
    return a - 1;
  }

  public static implicit operator ArrayPtr<T>(T[] x)
  {
    return new ArrayPtr<T>(x);
  }

  public static bool operator ==(ArrayPtr<T> x, ArrayPtr<T> y)
  {
    return x.source == y.source && x.index == y.index;
  }

  public static bool operator !=(ArrayPtr<T> x, ArrayPtr<T> y)
  {
    return !(x == y);
  }

  public override bool Equals(object x)
  {
    if (x == null) return this.source == null;
    var ptr = x as ArrayPtr<T>?;
    if (!ptr.HasValue) return false;
    return this == ptr.Value;
  }

  public override int GetHashCode()
  {
    unchecked
    {
      int hash = this.source == null ? 0 : this.source.GetHashCode();
      return hash + this.index;
    }
  }

  public T this[int index]
  {
    get { return source[index + this.index]; }
    set { source[index + this.index] = value; }
  }
}

现在我们可以做如下事情:

double[] arr = new double[10];
var p0 = (ArrayPtr<double>)arr;
var p5 = p0 + 5;
p5[0] = 123.4; // sets arr[5] to 123.4
var p7 = p0 + 7;
int diff = p7 - p5; // 2

【讨论】:

  • 我很惊讶它有各种我没想到的运算符(特别是比较运算符)但没有实现IEnumerable&lt;T&gt; - 我本来希望迭代一个段将比那些运算符更普遍有用。当您将其视为指针时,这是有道理的 - 如果您尝试将其视为数组的视图,则意义不大。
  • @JonSkeet:你说得很好。我的意图不是让这段代码功能齐全——我想要一个Length 属性和IEnumerable&lt;T&gt;,就像你注意到的那样,等等。我的意图是尽快将一大块复杂的 C++ 代码移植到 C#,以便我可以在闲暇时对其进行重构。有问题的代码做了指针比较、加法和减法,所以这就是我实现的全部。
  • 非常感谢,我想过这样做(创建我自己的类),但我希望有一个不会浪费我时间的内置程序。 Jon 建议我使用 ArraySegment,使用它会更快吗?我知道我很懒,因为我可以自己测试性能,但我猜你一定已经尝试过了。
  • @Spacemonkey:你是知道你的性能要求的人,而不是我。试试看吧!
  • 或者危险地生活并且变得不安全(如果你使用值类型);p
【解决方案2】:

听起来您正在寻找类似ArraySegment&lt;T&gt; 的内容。与我之前的想法相反,它确实有一个索引器并实现 IEnumerable&lt;T&gt; 等 - 它只是通过显式接口完成的。

示例代码:

using System;
using System.Collections.Generic;

static class Test
{
    static void Main()
    {
        string[] original = { "The", "quick", "brown", "fox", "jumped", "over",
                "the", "lazy", "dog" };

        IList<string> segment = new ArraySegment<string>(original, 3, 4);
        Console.WriteLine(segment[2]); // over
        foreach (var word in segment)
        {
            Console.WriteLine(word); // fox jumped over the
        }
    }
}

编辑:如 cmets 中所述,ArraySegment&lt;T&gt; 仅在 .NET 4.5 中真正“功能齐全”。 .NET 4 version 没有实现任何接口。

【讨论】:

  • 这个结构,ArraySegment&lt;&gt;,在 .NET 4.5 中得到了很大的扩展。在 4.5 之前,它缺少很多“自然”的功能。
  • @JeppeStigNielsen:啊,这很有意义。谢谢,会注意的。
  • 非常感谢,没想到会得到您的答复!不过有一个问题,将索引器与 ArraySegment 一起使用会带来任何性能损失吗?
  • @Spacemonkey:是的,我希望会有性能损失。因为接口是显式实现的,所以你必须通过接口,这意味着一个虚函数调用。数组元素访问可能会更快。但是,我希望大多数 应用程序实际上不会在这种访问中遇到瓶颈。与以往一样,设置一些性能标准,然后进行测试。
【解决方案3】:

你可以使用 LINQ:

yourArray.Skip(startIndex).Take(numberToTake)

查询是惰性求值的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-12-16
    • 2017-05-06
    • 2013-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-11
    相关资源
    最近更新 更多