ToList () - nó có tạo một danh sách mới không?


176

Hãy nói rằng tôi có một lớp học

public class MyObject
{
   public int SimpleInt{get;set;}
}

Và tôi có một List<MyObject>, và tôi ToList()và sau đó thay đổi một trong những SimpleIntthay đổi của tôi sẽ được đưa trở lại danh sách ban đầu. Nói cách khác, đầu ra của phương pháp nào sau đây?

public void RunChangeList()
{
  var objs = new List<MyObject>(){new MyObject(){SimpleInt=0}};
  var whatInt = ChangeToList(objs );
}
public int ChangeToList(List<MyObject> objects)
{
  var objectList = objects.ToList();
  objectList[0].SimpleInt=5;
  return objects[0].SimpleInt;

}

Tại sao?

P / S: Tôi xin lỗi nếu có vẻ rõ ràng để tìm hiểu. Nhưng tôi không có trình biên dịch với tôi bây giờ ...


Một cách để phrasing nó, đó là .ToList()làm cho một bản sao nông . Các tham chiếu được sao chép, nhưng các tham chiếu mới vẫn trỏ đến cùng các trường hợp như các tham chiếu ban đầu trỏ đến. Khi bạn nghĩ về nó, ToListkhông thể tạo bất kỳ new MyObject()khi nào MyObjectlà một classloại.
Jeppe Stig Nielsen

Câu trả lời:


217

Có, ToListsẽ tạo một danh sách mới, nhưng vì trong trường hợp MyObjectnày là loại tham chiếu thì danh sách mới sẽ chứa các tham chiếu đến cùng các đối tượng như danh sách ban đầu.

Cập nhật thuộc SimpleInttính của một đối tượng được tham chiếu trong danh sách mới cũng sẽ ảnh hưởng đến đối tượng tương đương trong danh sách ban đầu.

(Nếu MyObjectđược khai báo là structthay vì classthì danh sách mới sẽ chứa các bản sao của các phần tử trong danh sách gốc và việc cập nhật thuộc tính của một phần tử trong danh sách mới sẽ không ảnh hưởng đến phần tử tương đương trong danh sách gốc.)


1
Cũng lưu ý rằng với một Listcấu trúc, một phép gán giống như objectList[0].SimpleInt=5sẽ không được phép (lỗi thời gian biên dịch C #). Đó là bởi vì giá trị trả về của trình truy cập của bộ chỉ mục danh sách getkhông phải là một biến (nó là bản sao được trả về của giá trị cấu trúc) và do đó không cho phép đặt thành viên của nó .SimpleIntvới biểu thức gán (nó sẽ làm thay đổi một bản sao không được giữ) . Vâng, ai sử dụng cấu trúc đột biến?
Jeppe Stig Nielsen

2
@Jeppe Stig Nielson: Vâng. Và tương tự, trình biên dịch cũng sẽ ngăn bạn làm những thứ như thế foreach (var s in listOfStructs) { s.SimpleInt = 42; }. Các thực sự Gotcha khó chịu là khi bạn cố gắng một cái gì đó như listOfStructs.ForEach(s => s.SimpleInt = 42): trình biên dịch cho phép nó và chạy mã mà không ngoại lệ, nhưng các cấu trúc trong danh sách sẽ ở lại không thay đổi!
LukeH

62

Từ nguồn Reflector'd:

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return new List<TSource>(source);
}

Vì vậy, có, danh sách ban đầu của bạn sẽ không được cập nhật (tức là bổ sung hoặc xóa) tuy nhiên các đối tượng được tham chiếu sẽ.


32

ToList sẽ luôn tạo một danh sách mới, sẽ không phản ánh bất kỳ thay đổi nào sau đó đối với bộ sưu tập.

Tuy nhiên, nó sẽ phản ánh các thay đổi đối với chính các đối tượng (Trừ khi chúng là các cấu trúc có thể thay đổi).

Nói cách khác, nếu bạn thay thế một đối tượng trong danh sách ban đầu bằng một đối tượng khác, thì ToListnó vẫn sẽ chứa đối tượng đầu tiên.
Tuy nhiên, nếu bạn sửa đổi một trong các đối tượng trong danh sách ban đầu, ToListthì vẫn sẽ chứa cùng một đối tượng (đã sửa đổi).


12

Câu trả lời được chấp nhận giải quyết chính xác câu hỏi của OP dựa trên ví dụ của anh ấy. Tuy nhiên, nó chỉ áp dụng khi ToListđược áp dụng cho một bộ sưu tập cụ thể; nó không giữ được khi các phần tử của chuỗi nguồn chưa được khởi tạo (do thực thi bị trì hoãn). Trong trường hợp sau, bạn có thể nhận được một bộ vật phẩm mới mỗi lần bạn gọi ToList(hoặc liệt kê chuỗi).

Dưới đây là bản phóng tác của mã OP để thể hiện hành vi này:

public static void RunChangeList()
{
    var objs = Enumerable.Range(0, 10).Select(_ => new MyObject() { SimpleInt = 0 });
    var whatInt = ChangeToList(objs);   // whatInt gets 0
}

public static int ChangeToList(IEnumerable<MyObject> objects)
{
    var objectList = objects.ToList();
    objectList.First().SimpleInt = 5;
    return objects.First().SimpleInt;
}

Trong khi đoạn mã trên có thể xuất hiện, hành vi này có thể xuất hiện dưới dạng một lỗi nhỏ trong các tình huống khác. Xem ví dụ khác của tôi cho một tình huống trong đó nó khiến các nhiệm vụ được lặp đi lặp lại.


11

Vâng, nó tạo ra một danh sách mới. Đây là do thiết kế.

Danh sách này sẽ chứa các kết quả tương tự như chuỗi liệt kê ban đầu, nhưng được cụ thể hóa thành một bộ sưu tập (trong bộ nhớ) liên tục. Điều này cho phép bạn tiêu thụ kết quả nhiều lần mà không phải chịu chi phí tính toán lại chuỗi.

Vẻ đẹp của chuỗi LINQ là chúng có thể ghép lại được. Thông thường, những IEnumerable<T>gì bạn nhận được là kết quả của việc kết hợp nhiều hoạt động lọc, đặt hàng và / hoặc chiếu. Các phương thức mở rộng thích ToList()ToArray()cho phép bạn chuyển đổi chuỗi được tính toán thành một bộ sưu tập tiêu chuẩn.


6

Một danh sách mới được tạo nhưng các mục trong đó là các tham chiếu đến các mục gốc (giống như trong danh sách gốc). Thay đổi đối với danh sách là độc lập, nhưng đối với các mục sẽ tìm thấy thay đổi trong cả hai danh sách.


6

Chỉ cần vấp ngã bài cũ này và nghĩ đến việc thêm hai xu của tôi. Nói chung, nếu tôi nghi ngờ, tôi nhanh chóng sử dụng phương thức GetHashCode () trên bất kỳ đối tượng nào để kiểm tra danh tính. Vì vậy, ở trên -

    public class MyObject
{
    public int SimpleInt { get; set; }
}


class Program
{

    public static void RunChangeList()
    {
        var objs = new List<MyObject>() { new MyObject() { SimpleInt = 0 } };
        Console.WriteLine("objs: {0}", objs.GetHashCode());
        Console.WriteLine("objs[0]: {0}", objs[0].GetHashCode());
        var whatInt = ChangeToList(objs);
        Console.WriteLine("whatInt: {0}", whatInt.GetHashCode());
    }

    public static int ChangeToList(List<MyObject> objects)
    {
        Console.WriteLine("objects: {0}", objects.GetHashCode());
        Console.WriteLine("objects[0]: {0}", objects[0].GetHashCode());
        var objectList = objects.ToList();
        Console.WriteLine("objectList: {0}", objectList.GetHashCode());
        Console.WriteLine("objectList[0]: {0}", objectList[0].GetHashCode());
        objectList[0].SimpleInt = 5;
        return objects[0].SimpleInt;

    }

    private static void Main(string[] args)
    {
        RunChangeList();
        Console.ReadLine();
    }

Và trả lời trên máy của tôi -

  • objs: 45653674
  • objs [0]: 41149443
  • đối tượng: 45653674
  • đối tượng [0]: 41149443
  • danh sách đối tượng: 39785641
  • objectList [0]: 41149443
  • những gì: 5

Vì vậy, về cơ bản các đối tượng mà danh sách mang vẫn giữ nguyên trong mã trên. Hy vọng cách tiếp cận sẽ giúp.


4

Tôi nghĩ rằng điều này tương đương với việc hỏi liệu ToList có sao chép sâu hay nông không. Vì ToList không có cách nào để sao chép MyObject, nó phải thực hiện một bản sao nông, vì vậy danh sách được tạo chứa các tham chiếu giống như tài liệu gốc, do đó mã trả về 5.


2

ToList sẽ tạo ra một danh sách hoàn toàn mới.

Nếu các mục trong danh sách là các loại giá trị, chúng sẽ được cập nhật trực tiếp, nếu chúng là các loại tham chiếu, mọi thay đổi sẽ được phản ánh lại trong các đối tượng được tham chiếu.


2

Trong trường hợp đối tượng nguồn là một IEnumerable thực sự (nghĩa là không chỉ là một bộ sưu tập được đóng gói dưới dạng vô số), ToList () có thể KHÔNG trả về cùng các tham chiếu đối tượng như trong IEnumerable ban đầu. Nó sẽ trả về một Danh sách các đối tượng mới, nhưng các đối tượng đó có thể không giống hoặc thậm chí bằng với các đối tượng mà IEnumerable mang lại khi được liệt kê lại


1
 var objectList = objects.ToList();
  objectList[0].SimpleInt=5;

Điều này sẽ cập nhật các đối tượng ban đầu là tốt. Danh sách mới sẽ chứa các tham chiếu đến các đối tượng có trong nó, giống như danh sách ban đầu. Bạn có thể thay đổi các yếu tố hoặc cập nhật sẽ được phản ánh trong các yếu tố khác.

Bây giờ nếu bạn cập nhật một danh sách (thêm hoặc xóa một mục) sẽ không được phản ánh trong danh sách khác.


1

Tôi không thấy bất cứ nơi nào trong tài liệu mà ToList () luôn được đảm bảo để trả về một danh sách mới. Nếu một IEnumerable là một Danh sách, có thể hiệu quả hơn để kiểm tra điều này và chỉ cần trả về cùng một Danh sách.

Điều đáng lo ngại là đôi khi bạn có thể muốn hoàn toàn chắc chắn rằng Danh sách được trả về! = Cho Danh sách gốc. Vì Microsoft không cung cấp tài liệu rằng ToList sẽ trả về Danh sách mới, chúng tôi không thể chắc chắn (trừ khi có ai đó tìm thấy tài liệu đó). Nó cũng có thể thay đổi trong tương lai, ngay cả khi nó hoạt động ngay bây giờ.

Danh sách mới (IEnumerable enumerablest) được đảm bảo trả về Danh sách mới. Tôi sẽ sử dụng điều này thay thế.


2
Nó nói trong tài liệu ở đây: " Phương thức ToList <TSource> (IEnumerable <TSource>) buộc đánh giá truy vấn ngay lập tức và trả về Danh sách <T> có chứa kết quả truy vấn. Bạn có thể thêm phương thức này vào truy vấn của mình để có được một bản sao được lưu trong bộ nhớ cache của kết quả truy vấn. "Câu thứ hai nhắc lại rằng mọi thay đổi đối với danh sách ban đầu sau khi truy vấn được đánh giá không ảnh hưởng đến danh sách được trả về.
Raymond Chen

1
@RaymondChen Điều này đúng với IEnumerables, nhưng không đúng với Danh sách. Mặc dù ToListdường như tạo ra một tham chiếu đối tượng danh sách mới khi được gọi trên a List, BenB đã đúng khi nói rằng điều này không được đảm bảo bởi tài liệu MS.
Teejay

@RaymondChen Theo nguồn của ChrisS, IEnumerable<>.ToList()thực sự được triển khai new List<>(source)và không có ghi đè cụ thể nào List<>, do đó, List<>.ToList()thực sự trả về một tham chiếu đối tượng danh sách mới. Nhưng một lần nữa, theo tài liệu MS, không có gì đảm bảo điều này sẽ không thay đổi trong tương lai, mặc dù nó có thể sẽ phá vỡ hầu hết các mã ngoài kia.
Teejay
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.