ArraySegment <T>クラスの用途は何ですか?


97

クラスArraySegment<byte>をサブクラス化しているときに型を見つけましたMessageEncoder

私はそれが特定の配列のセグメントであり、オフセットを取り、列挙可能ではなく、インデクサーがないことを理解しましたが、それでもその使用法を理解できません。誰かが例で説明できますか?


8
ArraySegment.Net 4.5で列挙可能のようです。
2013年

以下のような試みのため、この質問 ...
ケンキン

回答:


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 2017年

27
  1. IOクラスのバッファー分割-読み取りと書き込みの同時操作に同じバッファーを使用し、操作全体を記述できる単一の構造を渡します。
  2. 関数の設定-数学的に言えば、この新しい構造を使用して、連続するサブセットを表すことができます。これは基本的に配列のパーティションを作成できることを意味しますが、すべての確率とすべての偶数を表すことはできません。The1によって提案された電話ティーザーは、ArraySegmentパーティショニングとツリー構造を使用してエレガントに解決できた可能性があることに注意してください。最終的な数値は、最初にツリーの深さをトラバースすることで書き出すことができます。これは、メモリと速度の点で理想的なシナリオでした。
  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は正しいです:あなたはそのようなセグメントの要素を繰り返すことはできません。
–ŞafakGür2012

3
他人のコードを使用する場合は、通常、帰属を示すのが適切です。マナーだけです。あなたの例はdotnetperls.com/arraysegmentに由来します

1
もちろん、彼らはあなたの答えからそれを借りました。その場合、彼らはあなたに信用を与えるべきです。:)

26

これは、配列への参照を保持し、インデックスの範囲を格納する以外に何もしない、ちっぽけな小さな兵士の構造体です。少し危険です。配列データのコピーを作成せず、配列を不変にしたり、不変性の必要性を表現したりしないことに注意してください。より一般的なプログラミングパターンは、.NET BeginRead()メソッド、String.SubString()、Encoding.GetString()などで行われるように、配列と長さ変数またはパラメーターを保持または渡すだけです。

.NET Framework内ではあまり使用されませんが、Webソケットとそれに好意的なWCFに取り組んだ特定のMicrosoftプログラマーのように見える場合を除きます。これはおそらく適切なガイダンスですが、気に入った場合はそれを使用してください。これは.NET 4.6でプレビューを行いました。追加されたMemoryStream.TryGetBuffer()メソッドがそれを使用します。out私が仮定する2つの引数よりも優先されます。

一般的に、より普遍的なスライスの概念は、Mads TorgersenやStephen Toubのような主要な.NETエンジニアのウィッシュリストで高く評価されています。後者array[:]は少し前に構文の提案を開始しました。Roslynのこのページで、彼らが何を考えているかを確認できます。CLRサポートを取得することが、これが最終的に左右されるものだと思います。これは、積極的にあなたの目を保つ、私の知る限りでC#バージョン7のためにについて考えているSystem.Slices

更新:リンク切れ、これはバージョン7.2でSpanとして出荷されました。

Update2:RangeおよびIndexタイプとSlice()メソッドを使用したC#バージョン8.0でのサポートの強化。


「あまり役に立たない」-残念ながらメモリの制限によりマイクロ最適化が必要なシステムで非常に役立つことがわかりました。他の「典型的な」ソリューションあるという事実は、その有用性を損なうものではありません
AaronHS

5
わかりました、わかりました。実際に使用する習慣を身につけたすべての人からの推薦状は必要ありません:) @CRiceのコメントに賛成するのが最善です。述べたように、「気に入ったらそれを使ってください」。だからそれを使う。スライスは素晴らしいでしょう、待つことができません。
ハンスパッサント2017

そこには不変の純粋主義者のためのReadOnlySpanがあります。
Arek Bal 2018

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>からIEnumeratorを追加できることに注意してくださいIEnumerable.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

簡単に言うと、それは配列への参照を保持し、それぞれが異なる範囲を持つ単一の配列変数への複数の参照を持つことを可能にします。

実際、これは、開始インデックスと長さを保持するために、複数の変数を持つ代わりに、より構造化された方法で配列のセクションを使用して渡すのに役立ちます。また、配列セクションをより簡単に操作するためのコレクションインターフェイスを提供します。

たとえば、次の2つのコード例は同じことを行い、1つはArraySegmentを使用し、もう1つは使用しません。

        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());

明らかに配列のセグメントを関数に渡したい場合は特に、最初のコードスニペットがより好ましいです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.