Cú pháp ngắn hơn để truyền từ Danh sách <X> sang Danh sách <Y>?


236

Tôi biết có thể truyền danh sách các mục từ loại này sang loại khác (với điều kiện đối tượng của bạn có phương thức toán tử rõ ràng tĩnh công khai để thực hiện truyền) một lần như sau:

List<Y> ListOfY = new List<Y>();

foreach(X x in ListOfX)
    ListOfY.Add((Y)x);

Nhưng nó không thể bỏ toàn bộ danh sách cùng một lúc? Ví dụ,

ListOfY = (List<Y>)ListOfX;

@Oded: Tôi chỉ cố gắng để làm cho nó rõ ràng hơn một chút. Đừng lo lắng, bạn không hiểu, tôi hiểu :)
BoltClock

1
Giả sử X xuất phát từ Y và Z xuất phát từ Y, hãy nghĩ điều gì sẽ xảy ra nếu bạn thêm Z vào Danh sách <Y> thực sự là Danh sách <X>.
Bạn bè Richard

Câu trả lời:


496

Nếu Xthực sự có thể được đúc cho Ybạn nên có thể sử dụng

List<Y> listOfY = listOfX.Cast<Y>().ToList();

Một số điều cần chú ý (H / T cho người bình luận!)

  • Bạn phải bao gồm using System.Linq;để có được phương thức mở rộng này
  • Điều này phôi từng mục trong danh sách - không phải chính danh sách. Một cái mới List<Y>sẽ được tạo bởi cuộc gọi đến ToList().
  • Phương pháp này không hỗ trợ các toán tử chuyển đổi tùy chỉnh. (xem http://stackoverflow.com/questions/14523530/why-does-the-linq-cast-helper-not-work-with-the-implicit-cast-operator )
  • Phương thức này không hoạt động đối với một đối tượng có phương thức toán tử rõ ràng (khung 4.0)

12
Có một huy hiệu vàng khác. Điều này khá hữu ích.
ngerak

6
Phải bao gồm dòng sau để trình biên dịch nhận ra các phương thức mở rộng đó: sử dụng System.Linq;
hypehuman 21/07/2015

8
Cũng cần lưu ý rằng mặc dù điều này bỏ từng mục trong danh sách, nhưng danh sách đó không được chọn; thay vì một danh sách mới được tạo ra với loại mong muốn.
hypehuman 21/07/2015

4
Cũng lưu ý rằng Cast<T>phương thức này không hỗ trợ các toán tử chuyển đổi tùy chỉnh. Tại sao Trình trợ giúp diễn viên Linq không hoạt động với Toán tử diễn viên tiềm ẩn .
clD

Nó không hoạt động đối với một đối tượng có phương thức toán tử rõ ràng (khung 4.0)
Adrian

100

Diễn viên trực tiếp var ListOfY = (List<Y>)ListOfXlà không thể bởi vì nó sẽ yêu cầu đồng loại / chống chỉ định List<T>loại, và điều đó không thể được đảm bảo trong mọi trường hợp. Xin vui lòng đọc tiếp để xem các giải pháp cho vấn đề đúc này.

Mặc dù có vẻ bình thường để có thể viết mã như thế này:

List<Animal> animals = (List<Animal>) mammalList;

bởi vì chúng tôi có thể đảm bảo rằng mọi động vật có vú sẽ là động vật, đây rõ ràng là một sai lầm:

List<Mammal> mammals = (List<Mammal>) animalList;

vì không phải mọi động vật đều là động vật có vú.

Tuy nhiên, sử dụng C # 3 trở lên, bạn có thể sử dụng

IEnumerable<Animal> animals = mammalList.Cast<Animal>();

Điều đó làm giảm sự đúc một chút. Điều này tương đương về mặt cú pháp với mã thêm từng cái một của bạn, vì nó sử dụng một Mammalcụm từ rõ ràng để truyền từng cái trong danh sách thành một Animal, và sẽ thất bại nếu việc truyền không thành công.

Nếu bạn muốn kiểm soát nhiều hơn quá trình chuyển đổi / chuyển đổi, bạn có thể sử dụng ConvertAllphương thức của List<T>lớp, có thể sử dụng biểu thức được cung cấp để chuyển đổi các mục. Nó có thêm benifit mà nó trả về a List, thay vì IEnumerable, vì vậy không .ToList()cần thiết.

List<object> o = new List<object>();
o.Add("one");
o.Add("two");
o.Add(3);

IEnumerable<string> s1 = o.Cast<string>(); //fails on the 3rd item
List<string> s2 = o.ConvertAll(x => x.ToString()); //succeeds

2
Tôi không thể tin rằng tôi chưa bao giờ + 1 câu trả lời này cho đến bây giờ. Nó tốt hơn nhiều so với tôi ở trên.
Jamiec

6
@Jamiec Tôi đã không +1 vì anh ấy bắt đầu bằng "Không, không thể", trong khi chôn vùi câu trả lời mà nhiều người tìm thấy câu hỏi này đang tìm kiếm. Về mặt kỹ thuật, anh đã trả lời câu hỏi của OP kỹ lưỡng hơn.
Dan Bechard

13

Để thêm vào quan điểm của Sweko:

Lý do tại sao các diễn viên

var listOfX = new List<X>();
ListOf<Y> ys = (List<Y>)listOfX; // Compile error: Cannot implicitly cast X to Y

là không thể là bởi vì List<T>bất biến trong Type T và do đó nó không quan trọng cho dù Xxuất phát từ Y) - điều này là vì List<T>được định nghĩa là:

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T> ... // Other interfaces

(Lưu ý rằng trong khai báo này, gõ Tở đây không có sửa đổi phương sai bổ sung)

Tuy nhiên, nếu các bộ sưu tập có thể thay đổi không được yêu cầu trong thiết kế của bạn, thì việc phát sóng nhiều bộ sưu tập bất biến là có thể , ví dụ với điều kiện là Giraffexuất phát từ Animal:

IEnumerable<Animal> animals = giraffes;

Điều này là do IEnumerable<T>hỗ trợ hiệp phương sai trong T- điều này có nghĩa là cho rằng IEnumerablebộ sưu tập không thể thay đổi, vì nó không hỗ trợ các phương thức để Thêm hoặc Loại bỏ các phần tử khỏi bộ sưu tập. Lưu ý outtừ khóa trong khai báo IEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable

( Dưới đây là giải thích thêm cho lý do tại sao các bộ sưu tập có thể thay đổi như Listkhông thể hỗ trợ covariance, trong khi các bộ lặp và bộ sưu tập bất biến có thể.)

Đúc với .Cast<T>()

Như những người khác đã đề cập, .Cast<T>()có thể được áp dụng cho một bộ sưu tập để chiếu một bộ phần tử mới được đúc cho T, tuy nhiên làm như vậy sẽ ném ra InvalidCastExceptionnếu việc sử dụng một hoặc nhiều phần tử là không thể (đó sẽ là hành vi tương tự như làm rõ ràng đúc trong foreachvòng lặp của OP ).

Lọc và đúc với OfType<T>()

Nếu danh sách đầu vào chứa các yếu tố thuộc các loại khác nhau, không thể kết hợp, tiềm năng InvalidCastExceptioncó thể tránh được bằng cách sử dụng .OfType<T>()thay vì .Cast<T>(). ( .OfType<>()kiểm tra xem liệu một phần tử có thể được chuyển đổi thành loại mục tiêu hay không, trước khi thử chuyển đổi và lọc ra các loại không phù hợp.)

cho mỗi

Cũng lưu ý rằng nếu OP đã viết điều này thay vào đó: (lưu ý rõ ràngY y trong foreach)

List<Y> ListOfY = new List<Y>();

foreach(Y y in ListOfX)
{
    ListOfY.Add(y);
}

rằng việc đúc cũng sẽ được cố gắng. Tuy nhiên, nếu không có diễn viên là có thể, một InvalidCastExceptionkết quả sẽ.

Ví dụ

Ví dụ, được phân cấp lớp đơn giản (C # 6):

public abstract class Animal
{
    public string Name { get;  }
    protected Animal(string name) { Name = name; }
}

public class Elephant :  Animal
{
    public Elephant(string name) : base(name){}
}

public class Zebra : Animal
{
    public Zebra(string name)  : base(name) { }
}

Khi làm việc với một bộ sưu tập các loại hỗn hợp:

var mixedAnimals = new Animal[]
{
    new Zebra("Zed"),
    new Elephant("Ellie")
};

foreach(Animal animal in mixedAnimals)
{
     // Fails for Zed - `InvalidCastException - cannot cast from Zebra to Elephant`
     castedAnimals.Add((Elephant)animal);
}

var castedAnimals = mixedAnimals.Cast<Elephant>()
    // Also fails for Zed with `InvalidCastException
    .ToList();

Trong khi:

var castedAnimals = mixedAnimals.OfType<Elephant>()
    .ToList();
// Ellie

chỉ lọc ra Voi - tức là Ngựa vằn bị loại.

Re: Toán tử diễn viên tiềm ẩn

Không có động, các toán tử chuyển đổi do người dùng xác định chỉ được sử dụng tại thời gian biên dịch *, vì vậy ngay cả khi toán tử chuyển đổi giữa Zebra và Voi được cung cấp, hành vi thời gian chạy trên của các cách tiếp cận chuyển đổi sẽ không thay đổi.

Nếu chúng ta thêm toán tử chuyển đổi để chuyển đổi Zebra thành Voi:

public class Zebra : Animal
{
    public Zebra(string name) : base(name) { }
    public static implicit operator Elephant(Zebra z)
    {
        return new Elephant(z.Name);
    }
}

Thay vào đó, do các nhà điều hành chuyển đổi trên, trình biên dịch sẽ có thể thay đổi kiểu của các bên dưới mảng từ Animal[]đến Elephant[], cho rằng các Zebras bây giờ có thể được chuyển đổi sang một bộ sưu tập đồng nhất của Voi:

var compilerInferredAnimals = new []
{
    new Zebra("Zed"),
    new Elephant("Ellie")
};

Sử dụng toán tử chuyển đổi tiềm ẩn trong thời gian chạy

* Như Eric đã đề cập, toán tử chuyển đổi có thể được truy cập vào thời gian chạy bằng cách sử dụng dynamic:

var mixedAnimals = new Animal[] // i.e. Polymorphic collection
{
    new Zebra("Zed"),
    new Elephant("Ellie")
};

foreach (dynamic animal in mixedAnimals)
{
    castedAnimals.Add(animal);
}
// Returns Zed, Ellie

Xin chào, tôi vừa thử ví dụ "Sử dụng foreach () để lọc kiểu" bằng cách sử dụng: var list = new List <object> () {1, "a", 2, "b", 3, "c", 4, " d "}; foreach (int i trong danh sách) Console.WriteLine (i); và khi tôi chạy nó, tôi nhận được "Chỉ định diễn viên không hợp lệ." Tui bỏ lỡ điều gì vậy? Tôi không nghĩ foreach hoạt động theo cách này, đó là lý do tại sao tôi đã thử nó.
Brent Rittenhouse

Ngoài ra, nó không phải là một loại tham chiếu so với loại giá trị. Tôi vừa thử nó với một lớp cơ bản là 'Điều' và hai lớp dẫn xuất: 'Người' và 'Động vật'. Khi tôi làm điều tương tự với nó, tôi nhận được: "Không thể truyền đối tượng thuộc loại 'Động vật' để nhập 'Người'." Vì vậy, nó chắc chắn lặp đi lặp lại qua từng yếu tố. NẾU tôi đã làm một OfType trong danh sách thì nó sẽ hoạt động. ForEach có lẽ sẽ rất chậm nếu phải kiểm tra điều này, trừ khi trình biên dịch tối ưu hóa nó.
Brent Rittenhouse

Cảm ơn Brent - tôi đã nghỉ học ở đó. foreachkhông lọc, nhưng sử dụng loại dẫn xuất nhiều hơn làm biến lặp sẽ ép buộc trình biên dịch thử Cast, sẽ thất bại ở phần tử đầu tiên không tuân thủ.
StuartLC

7

Bạn có thể dùng List<Y>.ConvertAll<T>([Converter from Y to T]);


3

Đây không hoàn toàn là câu trả lời cho câu hỏi này, nhưng nó có thể hữu ích cho một số người: như @Sweko đã nói, nhờ hiệp phương sai và chống chỉ định, List<X>không thể bỏ vào List<Y>, nhưng List<X>có thể bỏ vào IEnumerable<Y>, và thậm chí với cả diễn viên ngầm.

Thí dụ:

List<Y> ListOfY = new List<Y>();
List<X> ListOfX = (List<X>)ListOfY; // Compile error

nhưng

List<Y> ListOfY = new List<Y>();
IEnumerable<X> EnumerableOfX = ListOfY;  // No issue

Ưu điểm lớn là nó không tạo ra một danh sách mới trong bộ nhớ.


1
Tôi thích điều này bởi vì nếu bạn có một danh sách nguồn lớn, không có hiệu suất nào ngay từ đầu. Thay vào đó, có một nhóm nhỏ không đáng chú ý cho mỗi mục được xử lý bởi người nhận. Cũng không có bộ nhớ lớn xây dựng. hoàn hảo để xử lý luồng.
Johan Franzé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.