Việc sử dụng lớp ArraySegment <T> là gì?


97

Tôi vừa xem qua ArraySegment<byte>loại trong khi phân MessageEncoderloại lớp.

Bây giờ tôi hiểu rằng đó là một phân đoạn của một mảng nhất định, có độ lệch, không thể liệt kê và không có trình chỉ mục, nhưng tôi vẫn không hiểu cách sử dụng của nó. Ai đó có thể vui lòng giải thích với một ví dụ?


8
Có vẻ như ArraySegmentcó thể liệt kê trong .Net 4.5.
svick

Đối với nỗ lực như câu hỏi này ..
Ken Kin

Câu trả lời:


55

ArraySegment<T>đã trở nên hữu ích hơn rất nhiều trong .NET 4.5 + và .NET Core vì nó hiện đang triển khai:

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

trái ngược với phiên bản .NET 4 không triển khai bất kỳ giao diện nào.

Giờ đây, lớp có thể tham gia vào thế giới tuyệt vời của LINQ để chúng ta có thể thực hiện những việc LINQ thông thường như truy vấn nội dung, đảo ngược nội dung mà không ảnh hưởng đến mảng ban đầu, lấy mục đầu tiên, v.v.

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
Mặc dù chúng được đặt ở chế độ GetEnumeratorriêng tư một cách khó hiểu, có nghĩa là bạn buộc phải chuyển sang IEnumerable<T>(chuyển đổi quyền anh) để gọi nó. Ặc!
BlueRaja - Danny Pflughoeft

27
  1. Phân vùng bộ đệm cho các lớp IO - Sử dụng cùng một bộ đệm cho các thao tác đọc và ghi đồng thời và có một cấu trúc duy nhất mà bạn có thể chuyển xung quanh mô tả toàn bộ hoạt động của mình.
  2. Đặt hàm - Nói một cách toán học, bạn có thể biểu diễn bất kỳ tập hợp con liền kề nào bằng cách sử dụng cấu trúc mới này. Về cơ bản, điều đó có nghĩa là bạn có thể tạo các phân vùng của mảng, nhưng bạn không thể đại diện cho tất cả các tỷ lệ cược và tất cả các tỷ lệ. Lưu ý rằng đoạn giới thiệu điện thoại do The1 đề xuất có thể đã được giải quyết một cách thanh lịch bằng cách sử dụng phân vùng ArraySegment và cấu trúc cây. Những con số cuối cùng có thể được viết ra bằng cách đi qua độ sâu của cây trước. Tôi tin rằng đây sẽ là một kịch bản lý tưởng về bộ nhớ và tốc độ.
  3. Đa luồng - Giờ đây bạn có thể tạo ra nhiều luồng để hoạt động trên cùng một nguồn dữ liệu trong khi sử dụng các mảng được phân đoạn làm cổng điều khiển. Các vòng lặp sử dụng các phép tính rời rạc giờ đây có thể được tạo ra khá dễ dàng, điều mà các trình biên dịch C ++ mới nhất đang bắt đầu thực hiện như một bước tối ưu hóa mã.
  4. Phân đoạn giao diện người dùng - Hạn chế hiển thị giao diện người dùng của bạn bằng cách sử dụng cấu trúc được phân đoạn. Bây giờ bạn có thể lưu trữ các cấu trúc đại diện cho các trang dữ liệu có thể nhanh chóng được áp dụng cho các chức năng hiển thị. Mảng liền kề đơn có thể được sử dụng để hiển thị các khung nhìn rời rạc, hoặc thậm chí cấu trúc phân cấp như các nút trong TreeView bằng cách phân đoạn kho dữ liệu tuyến tính thành các phân đoạn thu thập nút.

Trong ví dụ này, chúng ta xem xét cách bạn có thể sử dụng mảng ban đầu, các thuộc tính Offset và Count, cũng như cách bạn có thể lặp qua các phần tử được chỉ định trong 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]);
        }
    }
}

Cấu trúc mảng - họ đang nghĩ gì?


3
ArraySegment chỉ là một cấu trúc. Dự đoán tốt nhất của tôi là mục đích của nó là cho phép một đoạn của mảng được truyền xung quanh mà không cần phải tạo bản sao của nó.
Brian

1
Tôi tin rằng câu lệnh điều kiện của vòng lặp for nên là i < segment.Offset + segment.Count.
Eren Ersönmez

1
+1 cho các sự kiện bạn đã đề cập nhưng @Eren nói đúng: Bạn không thể lặp lại các phần tử của phân đoạn như vậy.
Şafak Gür

3
Thường sẽ thích hợp để ghi nhận tác giả khi bạn sử dụng mã của ai đó. Đó chỉ là cách cư xử tốt. Ví dụ của bạn bắt nguồn từ dotnetperls.com/arraysegment .

1
Tất nhiên, trừ khi họ mượn câu trả lời của bạn. Trong trường hợp đó, họ sẽ cung cấp cho bạn các khoản tín dụng. :)

26

Nó là một cấu trúc lính nhỏ không làm gì khác ngoài việc giữ một tham chiếu đến một mảng và lưu trữ một phạm vi chỉ mục. Nguy hiểm một chút, hãy cẩn thận rằng nó không tạo bản sao dữ liệu mảng và không làm cho mảng bất biến theo bất kỳ cách nào hoặc thể hiện nhu cầu về tính bất biến. Mẫu lập trình điển hình hơn là chỉ giữ hoặc truyền mảng và một biến độ dài hoặc tham số, giống như nó được thực hiện trong các phương thức .NET BeginRead (), String.SubString (), Encoding.GetString (), v.v.

Nó không được sử dụng nhiều bên trong .NET Framework, ngoại trừ những gì có vẻ như một lập trình viên Microsoft cụ thể đã làm việc trên các ổ cắm web và WCF thích nó. Đó có lẽ là hướng dẫn thích hợp, nếu bạn thích nó thì hãy sử dụng nó. Nó đã thực hiện một trò chơi ú òa trong .NET 4.6, phương thức MemoryStream.TryGetBuffer () được thêm vào sử dụng nó. Tôi thích có hai outđối số.

Nói chung, khái niệm về các lát cắt phổ biến hơn nằm trong danh sách mong muốn của các kỹ sư .NET chính như Mads Torgersen và Stephen Toub. Cái thứ hai đã khởi động array[:]đề xuất cú pháp một thời gian trước, bạn có thể xem họ đã nghĩ gì trong trang Roslyn này . Tôi giả định rằng nhận được hỗ trợ CLR là điều mà điều này cuối cùng là bản lề. Điều này đang được tích cực xem xét cho C # phiên bản 7 afaik, hãy để ý đến System.Slices .

Cập nhật: liên kết chết, điều này được xuất bản trong phiên bản 7.2 dưới dạng Span .

Update2: hỗ trợ nhiều hơn trong C # phiên bản 8.0 với các loại Phạm vi và Chỉ mục và phương thức Slice ().


"Nó không hữu ích lắm" - Tôi thấy nó cực kỳ hữu ích trong một hệ thống không may yêu cầu tối ưu hóa vi mô do giới hạn bộ nhớ. Thực tế là cũng có những giải pháp "điển hình" khác không làm giảm đi tiện ích của nó
AaronHS 14/02/17

5
Được rồi, được rồi, tôi thực sự không cần lời chứng thực từ những người có thói quen sử dụng nó :) Tốt nhất hãy ủng hộ nhận xét của @ CRice. Như đã lưu ý, "nếu bạn thích nó thì hãy sử dụng nó". Vì vậy, hãy sử dụng nó. Slices sẽ tuyệt vời, không thể chờ đợi.
Hans Passant

Có một ReadOnlySpan cho những người theo chủ nghĩa thuần túy bất biến ngoài kia.
Arek Bal

7

Còn về một lớp wrapper là gì? Chỉ để tránh sao chép dữ liệu vào bộ đệm tạm thời.

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

Thí dụ:

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

Ouput:

3
4

3
4

Bạn rất hữu ích, cảm ơn, lưu ý bạn có thể làm cho nó thực hiện IEnumerable<T>sau đó thêm IEnumeratorIEnumerable.GetEnumerator() { return GetEnumerator(); }
Maya

5

ArraySegment hữu ích hơn rất nhiều so với những gì bạn nghĩ. Hãy thử chạy bài kiểm tra đơn vị sau và chuẩn bị để ngạc nhiên!

    [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("}");
        }
    }

Bạn thấy đấy, tất cả những gì bạn phải làm là truyền một ArraySegment tới IList và nó sẽ thực hiện tất cả những điều mà bạn có thể mong đợi nó sẽ làm ngay từ đầu. Lưu ý rằng kiểu vẫn là ArraySegment, mặc dù nó đang hoạt động như một danh sách bình thường.

ĐẦU RA:

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

4
Thật tiếc là cần phải truyền nó đến IList<T>. Tôi mong đợi người lập chỉ mục được public.
xmedeko

2
Đối với bất kỳ ai tìm thấy câu trả lời này và nghĩ rằng nó là một giải pháp kỳ diệu, trước tiên tôi khuyên bạn nên xem xét nhu cầu hiệu suất của mình và đánh giá điểm này so với truy cập trực tiếp vào mảng ban đầu bằng cách sử dụng các ràng buộc chỉ mục từ phân đoạn mảng. Truyền tới một IList yêu cầu các lệnh gọi phương thức tiếp theo (bao gồm cả trình chỉ mục) phải nhảy qua giao diện IList trước khi thực hiện. Có rất nhiều cuộc thảo luận trên internet, nơi mọi người nói về chi phí hiệu suất của việc sử dụng các cuộc gọi trừu tượng trong các vòng lặp chặt chẽ. Đọc ở đây: github.com/dotnet/coreclr/issues/9105
JamesHoux

3

Nói một cách dễ hiểu: nó giữ tham chiếu đến một mảng, cho phép bạn có nhiều tham chiếu đến một biến mảng duy nhất, mỗi biến có một phạm vi khác nhau.

Trên thực tế, nó giúp bạn sử dụng và chuyển các phần của mảng theo cách có cấu trúc hơn, thay vì có nhiều biến, để giữ chỉ số bắt đầu và độ dài. Ngoài ra, nó cung cấp giao diện bộ sưu tập để làm việc dễ dàng hơn với các phần mảng.

Ví dụ: hai ví dụ mã sau đây làm điều tương tự, một với ArraySegment và một không có:

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

và,

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

Rõ ràng đoạn mã đầu tiên được ưu tiên hơn, đặc biệt khi bạn muốn chuyển các phân đoạn mảng cho một hàm.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.