Tại sao tôi có thể khởi tạo Danh sách như một mảng trong C #?


131

Hôm nay tôi rất ngạc nhiên khi thấy rằng trong C # tôi có thể làm:

List<int> a = new List<int> { 1, 2, 3 };

Tại sao tôi có thể làm điều này? Những gì xây dựng được gọi là? Làm thế nào tôi có thể làm điều này với các lớp học của riêng tôi? Tôi biết rằng đây là cách để khởi tạo mảng nhưng mảng là các mục ngôn ngữ và Danh sách là các đối tượng đơn giản ...


1
Câu hỏi này có thể hữu ích: stackoverflow.com/questions/1744967/ Kẻ
Bob Kaufman

10
Khá tuyệt vời hả? Bạn cũng có thể thực hiện mã tương tự để khởi tạo từ điển:{ { "key1", "value1"}, { "key2", "value2"} }
danludwig

Từ góc nhìn khác, mảng này như cú pháp khởi tạo cho chúng ta một lời nhắc nhở rằng kiểu dữ liệu cơ bản của a List<int>thực sự chỉ là một mảng. Tôi ghét đội C # vì thực tế là họ không đặt tên cho nó ArrayList<T>nghe có vẻ quá rõ ràng và tự nhiên.
RBT

Câu trả lời:


183

Đây là một phần của cú pháp khởi tạo bộ sưu tập trong .NET. Bạn có thể sử dụng cú pháp này trên bất kỳ bộ sưu tập nào bạn tạo miễn là:

  • Nó thực hiện IEnumerable(tốt nhất IEnumerable<T>)

  • Nó có một phương thức được đặt tên Add(...)

Điều gì xảy ra là hàm tạo mặc định được gọi, và sau đó Add(...)được gọi cho mỗi thành viên của trình khởi tạo.

Do đó, hai khối này giống hệt nhau:

List<int> a = new List<int> { 1, 2, 3 };

List<int> temp = new List<int>();
temp.Add(1);
temp.Add(2);
temp.Add(3);
List<int> a = temp;

Bạn có thể gọi một hàm tạo thay thế nếu bạn muốn, ví dụ để ngăn quá kích thước List<T>trong quá trình phát triển, v.v .:

// Notice, calls the List constructor that takes an int arg
// for initial capacity, then Add()'s three items.
List<int> a = new List<int>(3) { 1, 2, 3, }

Lưu ý rằng Add()phương thức không cần lấy một mục, ví dụ: Add()phương thức Dictionary<TKey, TValue>lấy hai mục:

var grades = new Dictionary<string, int>
    {
        { "Suzy", 100 },
        { "David", 98 },
        { "Karen", 73 }
    };

Gần giống với:

var temp = new Dictionary<string, int>();
temp.Add("Suzy", 100);
temp.Add("David", 98);
temp.Add("Karen", 73);
var grades = temp;

Vì vậy, để thêm phần này vào lớp của riêng bạn, tất cả những gì bạn cần làm, như đã đề cập, là triển khai IEnumerable(một lần nữa, tốt nhất là IEnumerable<T>) và tạo một hoặc nhiều Add()phương thức:

public class SomeCollection<T> : IEnumerable<T>
{
    // implement Add() methods appropriate for your collection
    public void Add(T item)
    {
        // your add logic    
    }

    // implement your enumerators for IEnumerable<T> (and IEnumerable)
    public IEnumerator<T> GetEnumerator()
    {
        // your implementation
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Sau đó, bạn có thể sử dụng nó giống như các bộ sưu tập BCL làm:

public class MyProgram
{
    private SomeCollection<int> _myCollection = new SomeCollection<int> { 13, 5, 7 };    

    // ...
}

(Để biết thêm thông tin, xem MSDN )


29
Câu trả lời hay, mặc dù tôi lưu ý rằng việc nói "chính xác" trong ví dụ đầu tiên của bạn không hoàn toàn chính xác. Nó hoàn toàn giống với List<int> temp = new List<int>(); temp.Add(1); ... List<int> a = temp; Nghĩa là, abiến không được khởi tạo cho đến khi tất cả các phép cộng được gọi. Nếu không, sẽ là hợp pháp để làm một cái gì đó như List<int> a = new List<int>() { a.Count, a.Count, a.Count };là một điều điên rồ để làm.
Eric Lippert

1
@ user606723: Không có bất kỳ sự khác biệt thực sự nào giữa List<int> a; a = new List<int>() { a.Count };List<int> a = new List<int>() { a.Count };
Joren

4
@Joren: Bạn đúng rồi; trong thực tế, đặc tả kỹ thuật C # T x = y;giống như T x; x = y;, Thực tế này có thể dẫn đến một số tình huống kỳ lạ. Ví dụ, int x = M(out x) + x;là hoàn toàn hợp pháp vì int x; x = M(out x) + x;là hợp pháp.
Eric Lippert

1
@JamesMichaelHare không cần thiết phải thực hiện IEnumerable<T>; không chung chung IEnumerablelà đủ để cho phép sử dụng cú pháp khởi tạo bộ sưu tập.
phoog

2
Quan điểm của Eric rất quan trọng nếu bạn đang sử dụng một số loại bộ sưu tập thực hiện IDis Dùng một lần. using (var x = new Something{ 1, 2 })sẽ không xử lý đối tượng nếu một trong các Addcuộc gọi thất bại.
porges

11

Nó được gọi là đường cú pháp .

List<T> là lớp "đơn giản", nhưng trình biên dịch đưa ra một cách xử lý đặc biệt cho nó để làm cho cuộc sống của bạn dễ dàng hơn.

Cái này được gọi là bộ khởi tạo bộ sưu tập . Bạn cần thực hiện IEnumerable<T>Addphương pháp.


8

Theo Thông số kỹ thuật C # Phiên bản 3.0 "Đối tượng bộ sưu tập được áp dụng trình khởi tạo bộ sưu tập phải thuộc loại thực hiện System.Collections.Generic.ICollection cho chính xác một T."

Tuy nhiên, thông tin này dường như không chính xác khi viết bài này; xem làm rõ của Eric Lippert trong các ý kiến ​​dưới đây.


10
Trang đó không chính xác. Cảm ơn vì đã mang đến sự chú ý của tôi. Đó phải là một số tài liệu tiền phát hành sơ bộ mà bằng cách nào đó không bao giờ bị xóa. Thiết kế ban đầu là yêu cầu ICollection, nhưng đó không phải là thiết kế cuối cùng mà chúng tôi đã giải quyết.
Eric Lippert

1
Cảm ơn bạn đã làm rõ.
Olivier Jacot-Descombes


6

Một điều thú vị khác về bộ khởi tạo bộ sưu tập là bạn có thể có quá nhiều Addphương thức và bạn có thể gọi tất cả chúng trong cùng một bộ khởi tạo! Ví dụ: công việc này:

public class MyCollection<T> : IEnumerable<T>
{
    public void Add(T item, int number)
    {

    }
    public void Add(T item, string text) 
    {

    }
    public bool Add(T item) //return type could be anything
    {

    }
}

var myCollection = new MyCollection<bool> 
{
    true,
    { false, 0 },
    { true, "" },
    false
};

Nó gọi là quá tải chính xác. Ngoài ra, nó chỉ tìm phương thức có tên Add, kiểu trả về có thể là bất cứ thứ gì.


0

Mảng như cú pháp đang được bật trong một loạt các Add()cuộc gọi.

Để thấy điều này trong một ví dụ thú vị hơn nhiều, hãy xem xét đoạn mã sau trong đó tôi thực hiện hai điều thú vị nghe có vẻ bất hợp pháp đầu tiên trong C #, 1) đặt thuộc tính chỉ đọc, 2) thiết lập danh sách với một mảng như trình khởi tạo.

public class MyClass
{   
    public MyClass()
    {   
        _list = new List<string>();
    }
    private IList<string> _list;
    public IList<string> MyList 
    { 
        get
        { 
            return _list;
        }
    }
}
//In some other method
var sample = new MyClass
{
    MyList = {"a", "b"}
};

Mã này sẽ hoạt động hoàn hảo, mặc dù 1) MyList chỉ đọc và 2) Tôi thiết lập một danh sách với trình khởi tạo mảng.

Lý do tại sao điều này hoạt động là bởi vì trong mã là một phần của trình xác định đối tượng, trình biên dịch luôn biến bất kỳ {}cú pháp giống nào thành một chuỗi các Add()cuộc gọi hoàn toàn hợp pháp ngay cả trên trường chỉ đọc.

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.