ArraySegment <T>类的用途是什么?


97

我只是ArraySegment<byte>在子MessageEncoder类化时遇到了类型。

我现在知道这是给定数组的一部分,具有偏移量,不可枚举,并且没有索引器,但是我仍然无法理解其用法。有人可以举例说明吗?


8
看起来ArraySegment在.Net 4.5中是可枚举的。
svick

对于像这样的问题的尝试..
Ken Kin

Answers:


55

ArraySegment<T>.NET 4.5 +和.NET Core中已实现的功能变得更加有用:

  • IList<T>
  • ICollection<T>
  • IEnumerable<T>
  • IEnumerable
  • IReadOnlyList<T>
  • IReadOnlyCollection<T>

.NET 4版本相反,该版本未实现任何接口。

该类现在可以参与LINQ的精彩世界,因此我们可以执行LINQ的常规操作,例如查询内容,在不影响原始数组的情况下反转内容,获取第一项,等等:

var array = new byte[] { 5, 8, 9, 20, 70, 44, 2, 4 };
array.Dump();
var segment = new ArraySegment<byte>(array, 2, 3);
segment.Dump(); // output: 9, 20, 70
segment.Reverse().Dump(); // output 70, 20, 9
segment.Any(s => s == 99).Dump(); // output false
segment.First().Dump(); // output 9
array.Dump(); // no change

4
尽管他们莫名其妙地将其GetEnumerator私有化,IEnumerable<T>但这意味着您被迫强制转换(拳击转换)以进行调用。啊!
BlueRaja-Danny Pflughoeft

27
  1. IO类的缓冲区分配-使用相同的缓冲区进行同时的读取和写入操作,并具有一个单一的结构,您可以通过它来描述整个操作。
  2. 集合函数-从数学上讲,您可以使用此新结构表示任何连续的子集。基本上,这意味着您可以创建数组的分区,但不能表示所有的奇数和偶数。请注意,使用ArraySegment分区和树结构可以很好地解决The1提出的电话预告片。最终数字可以通过先遍历树的深度来写出。我相信,就内存和速度而言,这将是理想的方案。
  3. 多线程-现在,您可以在使用分段数组作为控制门的同时,产生多个线程以对同一数据源进行操作。现在,使用离散计算的循环可以很容易地实现,最新的C ++编译器已开始将其作为代码优化步骤来进行。
  4. UI细分-使用细分结构约束UI显示。现在,您可以存储代表可快速应用于显示功能的数据页面的结构。通过将线性数据存储分割为节点集合段,可以使用单个连续数组来显示离散视图,甚至显示分层结构,例如TreeView中的节点。

在此示例中,我们研究如何使用原始数组,Offset和Count属性,以及如何遍历ArraySegment中指定的元素。

using System;

class Program
{
    static void Main()
    {
        // Create an ArraySegment from this array.
        int[] array = { 10, 20, 30 };
        ArraySegment<int> segment = new ArraySegment<int>(array, 1, 2);

        // Write the array.
        Console.WriteLine("-- Array --");
        int[] original = segment.Array;
        foreach (int value in original)
        {
            Console.WriteLine(value);
        }

        // Write the offset.
        Console.WriteLine("-- Offset --");
        Console.WriteLine(segment.Offset);

        // Write the count.
        Console.WriteLine("-- Count --");
        Console.WriteLine(segment.Count);

        // Write the elements in the range specified in the ArraySegment.
        Console.WriteLine("-- Range --");
        for (int i = segment.Offset; i < segment.Count+segment.Offset; i++)
        {
            Console.WriteLine(segment.Array[i]);
        }
    }
}

ArraySegment结构-他们在想什么?


3
ArraySegment只是一个结构。我最好的猜测是,它的目的是允许传递数组的一部分,而不必复制它。
布莱恩

1
我相信for循环的条件语句应为i < segment.Offset + segment.Count
ErenErsönmez2012年

1
为您提到的事实+1,但@Eren是正确的:您不能像这样迭代一个细分的元素。
Şafak古尔

3
当您使用别人的代码时,通常应该注明出处。这只是好礼貌。您的示例源自dotnetperls.com/arraysegment

1
当然,除非他们从您的答案中借用了它。在这种情况下,他们应该给您信用。:)

26

这是一个微不足道的小兵结构,除了保留对数组的引用并存储索引范围外,什么也不做。有点危险,请注意它不会复制数组数据,也不会以任何方式使数组不可变或表达对不可变性的需求。更典型的编程模式是仅保留或传递数组以及长度变量或参数,就像在.NET BeginRead()方法,String.SubString(),Encoding.GetString()等中完成的一样。

在.NET Framework中,它没有多大用处,只是看起来像一位专门研究Web套接字并喜欢它的WCF的Microsoft程序员。如果您喜欢它,那么使用它可能是正确的指导。它确实在.NET 4.6中做了一个peek-a-boo,添加的MemoryStream.TryGetBuffer()方法使用了它。out我认为比有两个参数更可取。

通常,切片的更普遍的概念在主要的.NET工程师(如Mads Torgersen和Stephen Toub)的愿望清单中都很高。后者array[:]在不久前启动了语法建议,您可以在Roslyn页面上看到他们的想法。我认为获得CLR支持才是最终的目标。对于C#版本7 afaik,正在积极考虑这一点,请密切注意System.Slices

更新:无效链接,此链接在7.2版中以Span的形式提供

Update2:在C#版本8.0中,使用范围和索引类型以及Slice()方法提供了更多支持。


“这是不是非常有用” -我发现它非常有用的系统,不幸的是要求微优化由于内存限制的事实有,也是其他‘典型’的解决方案并不影响其效用
AaronHS

5
好的,好的,我真的不需要每个习惯使用它的人的推荐:)最好支持@CRice的评论。如前所述,“如果喜欢,请使用它”。因此使用它。切片会很棒,等不及了。
汉斯·帕桑

对于那些不可变的纯粹主义者,有一个ReadOnlySpan。
Arek Bal

7

包装类是什么?只是为了避免将数据复制到临时缓冲区。

public class SubArray<T> {
        private ArraySegment<T> segment;

        public SubArray(T[] array, int offset, int count) {
            segment = new ArraySegment<T>(array, offset, count);
        }
        public int Count {
            get { return segment.Count; }
        }

        public T this[int index] {
            get {
               return segment.Array[segment.Offset + index];
            }
        }

        public T[] ToArray() {
            T[] temp = new T[segment.Count];
            Array.Copy(segment.Array, segment.Offset, temp, 0, segment.Count);
            return temp;
        }

        public IEnumerator<T> GetEnumerator() {
            for (int i = segment.Offset; i < segment.Offset + segment.Count; i++) {
                yield return segment.Array[i];
            }
        }
    } //end of the class

例:

byte[] pp = new byte[] { 1, 2, 3, 4 };
SubArray<byte> sa = new SubArray<byte>(pp, 2, 2);

Console.WriteLine(sa[0]);
Console.WriteLine(sa[1]);
//Console.WriteLine(b[2]); exception

Console.WriteLine();
foreach (byte b in sa) {
    Console.WriteLine(b);
}

输出:

3
4

3
4

非常有用的朋友,谢谢,请注意您可以使其实现,IEnumerable<T>然后添加IEnumeratorIEnumerable.GetEnumerator() { return GetEnumerator(); }
MaYaN

5

ArraySegment比您想象的要有用得多。尝试运行以下单元测试,并准备惊讶!

    [TestMethod]
    public void ArraySegmentMagic()
    {
        var arr = new[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

        var arrSegs = new ArraySegment<int>[3];
        arrSegs[0] = new ArraySegment<int>(arr, 0, 3);
        arrSegs[1] = new ArraySegment<int>(arr, 3, 3);
        arrSegs[2] = new ArraySegment<int>(arr, 6, 3);
        for (var i = 0; i < 3; i++)
        {
            var seg = arrSegs[i] as IList<int>;
            Console.Write(seg.GetType().Name.Substring(0, 12) + i);
            Console.Write(" {");
            for (var j = 0; j < seg.Count; j++)
            {
                Console.Write("{0},", seg[j]);
            }
            Console.WriteLine("}");
        }
    }

您会看到,您所要做的就是将ArraySegment转换为IList,并且它将首先完成您可能期望的所有操作。请注意,该类型仍然是ArraySegment,即使它的行为类似于普通列表。

输出:

ArraySegment0 {0,1,2,}
ArraySegment1 {3,4,5,}
ArraySegment2 {6,7,8,}

4
可惜有必要将其转换为IList<T>。我希望索引器是public
xmedeko

2
对于任何想到此答案并认为这是一个奇迹解决方案的人,我建议首先考虑您的性能需求,并以此为基准进行比较,而不是使用数组段中的索引约束直接访问原始数组。强制转换为IList要求后续的方法调用(包括索引器)在实现之前跳过IList接口。互联网上有很多讨论,人们谈论在紧密循环中使用抽象调用的性能成本。在这里阅读: github.com/dotnet/coreclr/issues/9105
JamesHoux,

3

简而言之:它始终引用一个数组,使您可以对单个数组变量有多个引用,每个引用具有不同的范围。

实际上,它可以帮助您以更结构化的方式使用和传递数组的各个部分,而不是使用多个变量来保存起始索引和长度。它还提供了收集接口,可以更轻松地处理数组节。

例如,以下两个代码示例执行相同的操作,一个使用ArraySegment,另一个不使用:

        byte[] arr1 = new byte[] { 1, 2, 3, 4, 5, 6 };
        ArraySegment<byte> seg1 = new ArraySegment<byte>(arr1, 2, 2);
        MessageBox.Show((seg1 as IList<byte>)[0].ToString());

和,

        byte[] arr1 = new byte[] { 1, 2, 3, 4, 5, 6 };
        int offset = 2;
        int length = 2;
        byte[] arr2 = arr1;
        MessageBox.Show(arr2[offset + 0].ToString());

显然,首选第一个代码段,尤其是当您要将数组段传递给函数时。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.