Làm thế nào để khởi tạo Danh sách <T> với kích thước nhất định (trái ngược với dung lượng)?


111

.NET cung cấp một vùng chứa danh sách chung có hiệu suất gần như giống nhau (xem câu hỏi Hiệu suất của Mảng so với Danh sách). Tuy nhiên chúng khá khác nhau trong việc khởi tạo.

Mảng rất dễ khởi tạo với giá trị mặc định và theo định nghĩa thì chúng đã có kích thước nhất định:

string[] Ar = new string[10];

Điều này cho phép một người chỉ định các mục ngẫu nhiên một cách an toàn, giả sử:

Ar[5]="hello";

với danh sách mọi thứ phức tạp hơn. Tôi có thể thấy hai cách thực hiện cùng một lần khởi tạo, cả hai cách đều không được bạn gọi là thanh lịch:

List<string> L = new List<string>(10);
for (int i=0;i<10;i++) L.Add(null);

hoặc là

string[] Ar = new string[10];
List<string> L = new List<string>(Ar);

Cách nào sẽ sạch hơn?

CHỈNH SỬA: Các câu trả lời cho đến nay đề cập đến dung lượng, một cái gì đó khác với việc điền trước một danh sách. Ví dụ, trên một danh sách vừa tạo với dung lượng 10, người ta không thể làmL[2]="somevalue"

CHỈNH SỬA 2: Mọi người thắc mắc tại sao tôi muốn sử dụng danh sách theo cách này, vì nó không phải là cách chúng được dự định sử dụng. Tôi có thể thấy hai lý do:

  1. Người ta có thể lập luận khá thuyết phục rằng danh sách là mảng "thế hệ tiếp theo", thêm tính linh hoạt mà hầu như không bị phạt. Do đó người ta nên sử dụng chúng theo mặc định. Tôi chỉ ra rằng chúng có thể không dễ khởi tạo.

  2. Những gì tôi hiện đang viết là một lớp cơ sở cung cấp chức năng mặc định như một phần của một khuôn khổ lớn hơn. Trong chức năng mặc định mà tôi cung cấp, kích thước của Danh sách được biết đến ở mức nâng cao và do đó tôi có thể đã sử dụng một mảng. Tuy nhiên, tôi muốn cung cấp cho bất kỳ lớp cơ sở nào cơ hội để tự động mở rộng nó và do đó tôi chọn một danh sách.


1
"CHỈNH SỬA: Các câu trả lời cho đến nay đề cập đến dung lượng, một số khác so với việc điền trước một danh sách. Ví dụ: trên một danh sách vừa được tạo với dung lượng 10, người ta không thể thực hiện L [2] =" somevalue "" sửa đổi, có lẽ bạn nên xây dựng lại và câu hỏi Tiêu đề ...
aranasaurus

Nhưng, việc điền trước một danh sách với các giá trị trống có ích gì, vì đó là những gì mà topicstarter đang cố gắng làm?
Frederik Gheysels

1
Nếu ánh xạ vị trí là điều quan trọng, thì việc sử dụng Từ điển <int, string> sẽ hợp lý hơn phải không?
Greg D

7
Listkhông phải là một thay thế cho Array. Họ giải quyết các vấn đề riêng biệt rõ ràng. Nếu bạn muốn một kích thước cố định, bạn muốn một Array. Nếu bạn sử dụng a List, bạn đang làm sai.
Zenexer

5
Tôi luôn tìm thấy những câu trả lời khiến những lập luận như "Tôi không thể hiểu tại sao tôi lại cần ..." trở nên trầm trọng hơn. Nó chỉ có nghĩa là: bạn không thể nhìn thấy nó. Nó không nhất thiết có nghĩa gì khác. Tôi tôn trọng rằng mọi người muốn đề xuất các cách tiếp cận tốt hơn cho một vấn đề, nhưng nó nên được diễn đạt một cách khiêm tốn hơn, chẳng hạn như "Bạn có chắc là bạn cần một danh sách không? Có lẽ nếu bạn đã cho chúng tôi biết thêm về vấn đề của mình ...". Bằng cách này, nó trở nên dễ chịu, hấp dẫn khuyến khích OP cải thiện câu hỏi của họ. Hãy là người chiến thắng - hãy khiêm tốn.
AnorZaken

Câu trả lời:


79

Tôi không thể nói rằng tôi cần cái này thường xuyên - bạn có thể cho biết thêm chi tiết tại sao bạn muốn cái này không? Tôi có thể đặt nó như một phương thức tĩnh trong một lớp trợ giúp:

public static class Lists
{
    public static List<T> RepeatedDefault<T>(int count)
    {
        return Repeated(default(T), count);
    }

    public static List<T> Repeated<T>(T value, int count)
    {
        List<T> ret = new List<T>(count);
        ret.AddRange(Enumerable.Repeat(value, count));
        return ret;
    }
}

Bạn có thể sử dụng Enumerable.Repeat(default(T), count).ToList()nhưng điều đó sẽ không hiệu quả do thay đổi kích thước bộ đệm.

Lưu ý rằng nếu Tlà một kiểu tham chiếu, nó sẽ lưu trữ các countbản sao của tham chiếu được truyền cho valuetham số - vì vậy tất cả chúng sẽ tham chiếu đến cùng một đối tượng. Đó có thể là điều bạn muốn hoặc không, tùy thuộc vào trường hợp sử dụng của bạn.

CHỈNH SỬA: Như đã lưu ý trong nhận xét, bạn có thể Repeatedsử dụng một vòng lặp để điền danh sách nếu bạn muốn. Điều đó cũng sẽ nhanh hơn một chút. Cá nhân tôi thấy mã sử dụng Repeatmô tả nhiều hơn và nghi ngờ rằng trong thế giới thực, sự khác biệt về hiệu suất sẽ không liên quan, nhưng số dặm của bạn có thể thay đổi.


1
Tôi nhận ra đây là một bài viết cũ, nhưng tôi tò mò. Enumerable.Repeat kém hơn nhiều so với vòng lặp for, theo phần cuối cùng của liên kết ( dotnetperls.com/initialize-array ). Ngoài ra AddRange () có độ phức tạp O (n) theo msdn. Không phải là hơi phản tác dụng khi sử dụng giải pháp đã cho thay vì một vòng lặp đơn giản?
Jimmy

@Jimmy: Cả hai cách tiếp cận sẽ là O (n) và tôi thấy cách tiếp cận này mô tả nhiều hơn những gì tôi đang cố gắng đạt được. Nếu bạn thích một vòng lặp, hãy sử dụng nó.
Jon Skeet

@Jimmy: Cũng lưu ý rằng điểm chuẩn ở đó đang sử dụng Enumerable.Repeat(...).ToArray(), đó không phải là cách tôi đang sử dụng nó.
Jon Skeet

1
Tôi đã sử dụng Enumerable.Repeat()theo cách sau (được triển khai Pairgiống như C ++ tương đương): Enumerable.Repeat( new Pair<int,int>(int.MaxValue,-1), costs.Count)nhận thấy tác dụng phụ, rằng có Listđầy các bản sao được tham chiếu đến một đối tượng duy nhất. Thay đổi một phần tử giống như myList[i].First = 1thay đổi từng phần tử trong toàn bộ List. Tôi đã mất hàng giờ để tìm ra lỗi này. Do you guys biết bất kỳ giải pháp cho vấn đề này (trừ trường hợp chỉ sử dụng một vòng lặp và sử dụng chung .Add(new Pair...)?
00zetti

1
@ Pac0, tôi vừa thêm một câu trả lời bên dưới. Các chỉnh sửa có thể bị từ chối.
Jeremy Ray Brown

136
List<string> L = new List<string> ( new string[10] );

3
Cá nhân tôi nghĩ rằng đây là cách sạch nhất - mặc dù nó đã được đề cập trong cơ thể câu hỏi là có khả năng vụng về - không biết tại sao
hello_earth

4
+1: Chỉ gặp một tình huống mà tôi cần một danh sách có kích thước thay đổi được khởi tạo với một tập hợp các giá trị rỗng cố định trước khi điền thông qua một chỉ mục (và thêm các phần bổ sung sau đó, vì vậy một mảng không phù hợp). Câu trả lời này nhận được sự bình chọn của tôi vì thực tế và đơn giản.
Gone Coding

Câu trả lời tuyệt vời. @GoneCoding Tôi chỉ tự hỏi liệu bên trong danh sách mới khởi tạo Lsẽ đơn giản sử dụng lại bộ nhớ string[10]như hiện tại (lưu trữ sao lưu của một danh sách cũng là một mảng) hay nó sẽ cấp phát một bộ nhớ mới của riêng nó và sau đó sao chép nội dung của string[10]? string[10]sẽ được thu gom rác tự động nếu Lchọn tuyến đường sau.
RBT

3
@RBT Đó là nhược điểm duy nhất của cách tiếp cận này: Mảng sẽ không được sử dụng lại, vì vậy bạn sẽ có hai bản sao của mảng trong giây lát (Danh sách sử dụng mảng bên trong mà bạn dường như đã biết). Trong một số trường hợp hiếm hoi, đó có thể là vấn đề nếu bạn có một số lượng lớn các phần tử hoặc hạn chế về bộ nhớ. Và có, mảng bổ sung sẽ đủ điều kiện để thu gom rác ngay sau khi phương thức khởi tạo Danh sách được thực hiện (nếu không thì sẽ là một sự cố rò rỉ bộ nhớ khủng khiếp). Lưu ý rằng đủ điều kiện không có nghĩa là "được thu thập ngay lập tức", mà là trong lần thu rác tiếp theo.
AnorZaken

2
Câu trả lời này phân bổ 10 chuỗi mới - sau đó lặp qua chúng sao chép chúng theo yêu cầu. Nếu bạn đang làm việc với các mảng lớn; sau đó thậm chí không xem xét điều này vì nó cần gấp đôi bộ nhớ so với câu trả lời được chấp nhận.
UKMonkey

22

Sử dụng hàm tạo lấy int ("dung lượng") làm đối số:

List<string> = new List<string>(10);

CHỈNH SỬA: Tôi nên nói thêm rằng tôi đồng ý với Frederik. Bạn đang sử dụng Danh sách theo cách đi ngược lại toàn bộ lý do đằng sau việc sử dụng nó ngay từ đầu.

EDIT2:

CHỈNH SỬA 2: Những gì tôi hiện đang viết là một lớp cơ sở cung cấp chức năng mặc định như một phần của một khuôn khổ lớn hơn. Trong chức năng mặc định mà tôi cung cấp, kích thước của Danh sách được biết đến ở mức nâng cao và do đó tôi có thể đã sử dụng một mảng. Tuy nhiên, tôi muốn cung cấp cho bất kỳ lớp cơ sở nào cơ hội để tự động mở rộng nó và do đó tôi chọn một danh sách.

Tại sao mọi người cần biết kích thước của một Danh sách với tất cả các giá trị rỗng? Nếu không có giá trị thực nào trong danh sách, tôi sẽ mong đợi độ dài là 0. Nhưng dù sao, thực tế là điều này thật khó hiểu chứng tỏ rằng nó đang đi ngược lại mục đích sử dụng của lớp.


46
Câu trả lời này không phân bổ 10 mục nhập rỗng trong danh sách (đó là yêu cầu), nó chỉ phân bổ không gian cho 10 mục nhập trước khi yêu cầu thay đổi kích thước của danh sách (tức là dung lượng), vì vậy điều này không có gì khác biệt đối new List<string>()với vấn đề . Rất tốt khi nhận được rất nhiều phiếu bầu :)
Gone Coding

2
rằng constructor quá tải là "công suất ban đầu" giá trị không phải là "kích thước" hay "chiều dài", và nó không khởi các mục hoặc
Matt Wilko

Để trả lời "tại sao ai đó cần cái này": Tôi cần cái này ngay bây giờ cho các cấu trúc dữ liệu cây nhân bản sâu. Một nút có thể có hoặc không điền các nút con của nó, nhưng lớp nút cơ sở của tôi cần có khả năng sao chép chính nó với tất cả các nút con của nó. Blah, blah nó thậm chí còn phức tạp hơn. Nhưng tôi cần cả hai để điền vào danh sách vẫn trống của mình thông qua list[index] = obj;và sử dụng một số khả năng khác của danh sách.
Bitterblue

3
@Bitterblue: Ngoài ra, bất kỳ loại ánh xạ được phân bổ trước nào mà bạn có thể không có tất cả các giá trị trước. Chắc chắn có những công dụng; Tôi đã viết câu trả lời này trong những ngày ít kinh nghiệm của tôi.
Ed S.

8

Tạo một mảng với số lượng mục bạn muốn trước tiên và sau đó chuyển đổi mảng thành một Danh sách.

int[] fakeArray = new int[10];

List<int> list = fakeArray.ToList();

7

Tại sao bạn lại sử dụng Danh sách nếu bạn muốn khởi tạo nó với một giá trị cố định? Tôi có thể hiểu rằng - vì lợi ích của hiệu suất - bạn muốn cung cấp cho nó một dung lượng ban đầu, nhưng một trong những lợi thế của danh sách so với mảng thông thường là nó có thể phát triển khi cần thiết sao?

Khi bạn làm điều này:

List<int> = new List<int>(100);

Bạn tạo một danh sách có dung lượng là 100 số nguyên. Điều này có nghĩa là Danh sách của bạn sẽ không cần phải 'phát triển' cho đến khi bạn thêm mục thứ 101. Mảng bên dưới của danh sách sẽ được khởi tạo với độ dài 100.


1
"Tại sao bạn lại sử dụng Danh sách nếu bạn muốn khởi tạo nó với một giá trị cố định" Một điểm tốt.
Ed S.

7
Anh ta yêu cầu một danh sách trong đó mỗi phần tử được khởi tạo và danh sách có kích thước chứ không chỉ dung lượng. Câu trả lời này không chính xác như hiện tại.
Nic Foster

Câu hỏi yêu cầu một câu trả lời, không phải chỉ trích khi được hỏi. Đôi khi có lý do để làm theo cách này, trái ngược với việc sử dụng một mảng. Nếu quan tâm đến lý do tại sao điều này có thể xảy ra, người ta có thể đặt câu hỏi về Stack Overflow cho nó.
Dino Dini

5

Khởi tạo nội dung của một danh sách như vậy không thực sự là danh sách dùng để làm gì. Danh sách được thiết kế để chứa các đối tượng. Nếu bạn muốn ánh xạ các số cụ thể với các đối tượng cụ thể, hãy cân nhắc sử dụng cấu trúc cặp khóa-giá trị như bảng băm hoặc từ điển thay vì danh sách.


Điều này không trả lời câu hỏi, mà chỉ đơn thuần là đưa ra một lý do để nó không được hỏi.
Dino Dini

5

Có vẻ như bạn đang nhấn mạnh sự cần thiết phải liên kết vị trí với dữ liệu của mình, vì vậy, một mảng kết hợp sẽ không phù hợp hơn sao?

Dictionary<int, string> foo = new Dictionary<int, string>();
foo[2] = "string";

Đây là câu trả lời cho một câu hỏi khác với câu hỏi được hỏi.
Dino Dini

4

Nếu bạn muốn khởi tạo danh sách với N phần tử có giá trị cố định:

public List<T> InitList<T>(int count, T initValue)
{
  return Enumerable.Repeat(initValue, count).ToList();
}

Xem câu trả lời của John Skeet về mối quan tâm về việc thay đổi kích thước bộ đệm bằng kỹ thuật này.
Amy B

1

Bạn có thể sử dụng Linq để khởi tạo danh sách của mình một cách thông minh với một giá trị mặc định. (Tương tự như câu trả lời của David B. )

var defaultStrings = (new int[10]).Select(x => "my value").ToList();

Đi xa hơn một bước và khởi tạo từng chuỗi với các giá trị riêng biệt "chuỗi 1", "chuỗi 2", "chuỗi 3", v.v.:

int x = 1;
var numberedStrings = (new int[10]).Select(x => "string " + x++).ToList();

1
string [] temp = new string[] {"1","2","3"};
List<string> temp2 = temp.ToList();

Làm thế nào về List <string> temp2 = new List <string> (temp); Giống như OP đã đề xuất.
Ed S.

Ed - điều này thực sự trả lời câu hỏi - không phải về năng lực.
Boaz

Nhưng OP đã tuyên bố rằng ông không thích giải pháp này.
Ed S.

1

Câu trả lời được chấp nhận (câu trả lời có dấu kiểm màu xanh lá cây) có vấn đề.

Vấn đề:

var result = Lists.Repeated(new MyType(), sizeOfList);
// each item in the list references the same MyType() object
// if you edit item 1 in the list, you are also editing item 2 in the list

Tôi khuyên bạn nên thay đổi dòng ở trên để thực hiện sao chép đối tượng. Có nhiều bài báo khác nhau về điều đó:

Nếu bạn muốn khởi tạo mọi mục trong danh sách của mình bằng hàm tạo mặc định, thay vì NULL, thì hãy thêm phương thức sau:

public static List<T> RepeatedDefaultInstance<T>(int count)
    {
        List<T> ret = new List<T>(count);
        for (var i = 0; i < count; i++)
        {
            ret.Add((T)Activator.CreateInstance(typeof(T)));
        }
        return ret;
    }

1
Đó không phải là một "vấn đề" - đó là hành vi bình thường của việc sao chép tài liệu tham khảo. Nếu không biết tình hình, bạn không thể biết liệu nó có phù hợp để sao chép đối tượng hay không. Câu hỏi không phải là về việc liệu danh sách có nên được phổ biến với các bản sao hay không - mà là về việc khởi tạo một danh sách có dung lượng. Tôi sẽ làm rõ câu trả lời của tôi về hành vi đó không có, nhưng tôi thực sự không nghĩ rằng nó được tính là một vấn đề trong bối cảnh của câu hỏi này.
Jon Skeet

@JonSkeet, tôi đã trả lời trình trợ giúp tĩnh, điều này ổn đối với các kiểu nguyên thủy nhưng không phải là kiểu tham chiếu. Một trường hợp trong đó danh sách được khởi tạo với cùng một mục được tham chiếu có vẻ không đúng. Trong trường hợp đó, tại sao thậm chí có danh sách nếu mọi mục trong đó trỏ đến cùng một đối tượng trên heap.
Jeremy Ray Brown,

Kịch bản mẫu: bạn muốn điền vào danh sách các chuỗi, ban đầu với mục nhập "Không xác định" cho mọi phần tử, sau đó bạn sửa đổi danh sách cho các phần tử cụ thể. Trong trường hợp đó, tất cả các giá trị "Không xác định" là tham chiếu đến cùng một chuỗi là hoàn toàn hợp lý. Sẽ là vô nghĩa nếu sao chép chuỗi mỗi lần. Việc điền vào một chuỗi có nhiều tham chiếu đến cùng một đối tượng không phải là "đúng" hoặc "sai" theo nghĩa chung. Miễn là người đọc biết hành vi là gì, họ sẽ quyết định xem nó có đáp ứng trường hợp sử dụng cụ thể của họ hay không .
Jon Skeet

0

Thông báo về IList: MSDN IList Nhận xét : "Việc triển khai IList được chia thành ba loại: chỉ đọc, kích thước cố định và kích thước thay đổi. (...). Đối với phiên bản chung của giao diện này, hãy xem System.Collections.Generic.IList<T> ."

IList<T>KHÔNG kế thừa từ IList(nhưng List<T>thực hiện cả IList<T>IList), nhưng luôn có kích thước thay đổi . Kể từ .NET 4.5, chúng tôi cũng có IReadOnlyList<T>nhưng AFAIK, không có Danh sách chung kích thước cố định sẽ là những gì bạn đang tìm kiếm.


0

Đây là mẫu tôi đã sử dụng cho bài kiểm tra đơn vị của mình. Tôi đã tạo một danh sách các đối tượng lớp. Sau đó, tôi sử dụng forloop để thêm 'X' số đối tượng mà tôi đang mong đợi từ dịch vụ. Bằng cách này, bạn có thể thêm / khởi tạo Danh sách cho bất kỳ kích thước nhất định nào.

public void TestMethod1()
    {
        var expected = new List<DotaViewer.Interface.DotaHero>();
        for (int i = 0; i < 22; i++)//You add empty initialization here
        {
            var temp = new DotaViewer.Interface.DotaHero();
            expected.Add(temp);
        }
        var nw = new DotaHeroCsvService();
        var items = nw.GetHero();

        CollectionAssert.AreEqual(expected,items);


    }

Hy vọng tôi đã giúp được cho các bạn.


-1

Hơi muộn nhưng giải pháp đầu tiên bạn đề xuất có vẻ rõ ràng hơn nhiều đối với tôi: bạn không phân bổ bộ nhớ hai lần. Công cụ tạo danh sách Even cần lặp qua mảng để sao chép nó; nó thậm chí không biết trước chỉ có các phần tử rỗng bên trong.

1. - phân bổ N - vòng lặp N Chi phí: 1 * phân bổ (N) + N * loop_iteration

2. - cấp phát N - cấp phát N + vòng lặp () Chi phí: 2 * cấp phát (N) + N * vòng lặp_iteration

Tuy nhiên, việc phân bổ của List, một vòng lặp có thể nhanh hơn vì List là một lớp dựng sẵn, nhưng C # là một lớp được biên dịch jit nên ...

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.