ไวยากรณ์ที่สั้นลงสำหรับการส่งจากรายการ <X> ไปยังรายการ <Y>?


237

ฉันรู้ว่าเป็นไปได้ที่จะส่งรายการจากประเภทหนึ่งไปอีกประเภทหนึ่ง (เนื่องจากวัตถุของคุณมีวิธีดำเนินการแบบคงที่อย่างชัดเจนในที่สาธารณะเพื่อทำการแคสต์) ทีละรายการดังต่อไปนี้:

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

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

แต่เป็นไปไม่ได้ไหมที่จะส่งรายชื่อทั้งหมดในครั้งเดียว? ตัวอย่างเช่น,

ListOfY = (List<Y>)ListOfX;

@Oded: ฉันแค่พยายามทำให้ชัดเจนขึ้นเล็กน้อย ไม่ต้องกังวลคุณไม่ใช่ฉันเข้าใจ :)
BoltClock

1
การสันนิษฐาน X มาจาก Y และ Z มาจาก Y คิดว่าจะเกิดอะไรขึ้นถ้าคุณเพิ่ม Z ลงในรายการ <Y> ซึ่งเป็นรายการ <X> จริงๆ
Richard Friend

คำตอบ:


497

หากXสามารถโยนให้กับYคุณได้จริงๆควรจะสามารถใช้

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

บางสิ่งที่ต้องระวัง (H / T ถึงผู้แสดงความคิดเห็น!)

  • คุณต้องรวมusing System.Linq;เพื่อรับวิธีส่วนขยายนี้
  • สิ่งนี้จะปลดเปลื้องแต่ละรายการในรายการ - ไม่ใช่รายการเอง ใหม่จะถูกสร้างขึ้นโดยการเรียกร้องให้List<Y>ToList()
  • วิธีนี้ไม่รองรับผู้ให้บริการการแปลงที่กำหนดเอง (ดูhttp://stackoverflow.com/questions/14523530/why-does-the-linq-cast-helper-not-work-with-the-implicit-cast-operator )
  • วิธีนี้ไม่ทำงานสำหรับวัตถุที่มีวิธีการดำเนินการอย่างชัดเจน (กรอบ 4.0)

12
มีป้ายทองคำอีกอัน มันมีประโยชน์มาก
ouflak

6
ต้องรวมบรรทัดต่อไปนี้เพื่อให้คอมไพเลอร์รู้จักวิธีการขยายเหล่านั้น: using System.Linq;
hypehuman

8
นอกจากนี้โปรดทราบว่าแม้ว่าสิ่งนี้จะปลดเปลื้องแต่ละรายการในรายการ แต่รายการนั้นไม่ได้ทำการส่ง ค่อนข้างรายการใหม่จะถูกสร้างขึ้นด้วยประเภทที่ต้องการ
hypehuman

4
โปรดระวังด้วยว่าCast<T>วิธีนี้ไม่รองรับผู้ให้บริการการแปลงที่กำหนดเอง ทำไม Linq Cast Helper ไม่ทำงานกับผู้ประกอบการนำแสดงโดยปริยาย
clD

มันไม่ทำงานสำหรับวัตถุที่มีวิธีการดำเนินการอย่างชัดเจน (framework 4.0)
Adrian

100

การโยนโดยตรงvar ListOfY = (List<Y>)ListOfXเป็นไปไม่ได้เพราะมันจะต้องมีการร่วมกัน / contravarianceของList<T>ประเภทและไม่สามารถรับประกันได้ว่าจะมีในทุกกรณี โปรดอ่านต่อไปเพื่อดูวิธีแก้ไขปัญหาของการคัดเลือกนักแสดง

ดูเหมือนว่าปกติจะสามารถเขียนโค้ดดังนี้:

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

เพราะเราสามารถรับรองได้ว่าสัตว์เลี้ยงลูกด้วยนมทุกตัวจะเป็นสัตว์นี่เป็นข้อผิดพลาดที่เห็นได้ชัด:

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

เนื่องจากไม่ใช่สัตว์ทุกชนิดเป็นสัตว์เลี้ยงลูกด้วยนม

อย่างไรก็ตามการใช้ C # 3 ขึ้นไปคุณสามารถใช้

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

ทำให้การหล่อน้อยลง สิ่งนี้เทียบเท่ากับการเพิ่มรหัสแบบหนึ่งต่อหนึ่งเนื่องจากใช้การส่งแบบชัดแจ้งเพื่อส่งแต่ละMammalรายการในรายการAnimalและจะล้มเหลวหากการส่งไม่สำเร็จ

หากคุณต้องการควบคุมกระบวนการชี้ขาด / การแปลงมากขึ้นคุณสามารถใช้ConvertAllวิธีการของList<T>คลาสซึ่งสามารถใช้นิพจน์ที่ให้มาเพื่อแปลงรายการ มันมีประโยชน์เพิ่มเติมที่จะคืนค่าListแทนที่จะเป็นIEnumerableดังนั้นจึงไม่.ToList()จำเป็น

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
ฉันไม่อยากเชื่อเลยว่า ive จะไม่ได้ +1 คำตอบนี้จนกระทั่งตอนนี้ มันดีกว่าของฉันมาก
Jamiec

6
@Jamiec ฉันไม่ได้ +1 เพราะเขาเริ่มต้นด้วย "ไม่เป็นไปไม่ได้" ในขณะที่ฝังคำตอบหลายคนที่พบคำถามนี้กำลังมองหา ในทางเทคนิคเขาตอบคำถามของ OP ให้ละเอียดยิ่งขึ้น
Dan Bechard

13

วิธีเพิ่มไปยังจุดของ Sweko:

สาเหตุที่ทำให้นักแสดง

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

เป็นไปไม่ได้เพราะList<T>เป็นค่าคงที่ในประเภท Tและดังนั้นจึงไม่สำคัญว่าXเกิดจากY) - นี่เป็นเพราะList<T>มีการกำหนดเป็น:

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

(โปรดทราบว่าในการประกาศนี้พิมพ์Tที่นี่ไม่มีตัวดัดแปลงผลต่างเพิ่มเติม)

อย่างไรก็ตามหากคอลเลกชันที่ไม่สามารถเปลี่ยนแปลงได้นั้นไม่จำเป็นในการออกแบบของคุณการอัปโหลดในคอลเลกชันที่ไม่เปลี่ยนรูปแบบจำนวนมากนั้นเป็นไปได้เช่นที่จัดทำGiraffeขึ้นจากAnimal:

IEnumerable<Animal> animals = giraffes;

นี่เป็นเพราะIEnumerable<T>สนับสนุนความแปรปรวนร่วมในT- ทำให้รู้สึกว่าIEnumerableหมายถึงว่าคอลเลกชันไม่สามารถเปลี่ยนแปลงได้เนื่องจากมันไม่ได้รับการสนับสนุนสำหรับวิธีการเพิ่มหรือลบองค์ประกอบจากคอลเลกชัน หมายเหตุoutคำหลักในการประกาศของIEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable

( นี่คือคำอธิบายเพิ่มเติมสำหรับเหตุผลที่คอลเลกชันที่ไม่แน่นอนเช่นListไม่สามารถรองรับได้covarianceในขณะที่ตัวทำซ้ำและคอลเลกชันที่ไม่เปลี่ยนรูปสามารถ)

หล่อด้วย .Cast<T>()

ดังที่คนอื่น ๆ กล่าวถึง.Cast<T>()สามารถนำไปใช้กับคอลเลกชันเพื่อฉายคอลเลกชันใหม่ขององค์ประกอบที่โยนไป T อย่างไรก็ตามการทำเช่นนี้จะโยนInvalidCastExceptionถ้าการโยนบนองค์ประกอบหนึ่งหรือมากกว่าเป็นไปไม่ได้ (ซึ่งจะเป็นพฤติกรรมเดียวกันกับการทำชัดเจน ร่ายในforeachวงของ OP )

การกรองและการหล่อด้วย OfType<T>()

ถ้ารายการการป้อนข้อมูลที่มีองค์ประกอบที่แตกต่างกันชนิดเข้ากันไม่ได้ที่มีศักยภาพInvalidCastExceptionสามารถหลีกเลี่ยงได้โดยใช้แทน.OfType<T>() .Cast<T>()( .OfType<>()ตรวจสอบเพื่อดูว่าองค์ประกอบสามารถแปลงเป็นประเภทเป้าหมายได้หรือไม่ก่อนลองแปลงและกรองประเภทที่เข้ากันไม่ได้ออก)

แต่ละ

โปรดทราบด้วยว่าหาก OP ได้เขียนสิ่งนี้แทน: (โปรดสังเกตอย่างชัดเจนY yในforeach)

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

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

ว่าจะทำการคัดเลือกนักแสดงด้วย อย่างไรก็ตามหากไม่สามารถร่ายได้InvalidCastExceptionจะส่งผลให้

ตัวอย่าง

ตัวอย่างเช่นกำหนดลำดับชั้นของคลาสแบบง่าย (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) { }
}

เมื่อทำงานกับคอลเล็กชันประเภทผสม:

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

โดย:

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

กรองเฉพาะ Elephants - เช่น Zebras จะถูกกำจัด

Re: การใช้ตัวดำเนินการคาสต์โดยนัย

หากไม่มีตัวแปลงแบบไดนามิกตัวดำเนินการแปลงที่ผู้ใช้กำหนดจะใช้ในการคอมไพล์เวลา * เท่านั้นดังนั้นแม้ว่าตัวดำเนินการแปลงระหว่างบอกว่า Zebra และ Elephant พร้อมใช้งานพฤติกรรมของเวลาทำงานด้านบนของวิธีการแปลงจะไม่เปลี่ยนแปลง

หากเราเพิ่มตัวดำเนินการแปลงเพื่อแปลง Zebra เป็น Elephant:

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

แต่ด้วยตัวดำเนินการแปลงข้างต้นคอมไพเลอร์จะสามารถเปลี่ยนประเภทของอาร์เรย์ด้านล่างจากAnimal[]เป็นElephant[]ได้เนื่องจาก Zebras สามารถแปลงเป็นชุดของช้างที่เป็นเนื้อเดียวกันได้แล้ว:

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

การใช้ตัวดำเนินการแปลงโดยนัยในเวลาทำงาน

* ตามที่เอริคเอ่ยถึงผู้ดำเนินการแปลงสามารถเข้าถึงได้ในเวลาทำงานโดยหันไปที่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

สวัสดีฉันลองใช้ตัวอย่าง "การใช้ foreach () สำหรับการกรองประเภท" โดยใช้: var list = new List <object> () {1, "a", 2, "b", 3, "c", 4, " d "}; foreach (int i ในรายการ) Console.WriteLine (i); และเมื่อฉันเรียกใช้ฉันจะได้รับ "Cast ที่ระบุไม่ถูกต้อง" ฉันพลาดอะไรไปรึเปล่า? ฉันไม่คิดว่า foreach ทำงานด้วยวิธีนี้ซึ่งเป็นสาเหตุที่ฉันลองทำ
Brent Rittenhouse

นอกจากนี้ยังไม่ใช่การอ้างอิงกับสิ่งที่ประเภทค่า ฉันแค่ลองใช้คลาสฐานของ 'Thing' และคลาสที่ได้รับสองคลาส: 'Person' และ 'Animal' เมื่อฉันทำสิ่งเดียวกันกับที่ฉันได้รับ: "ไม่สามารถโยนวัตถุประเภท 'สัตว์' เพื่อพิมพ์ 'บุคคล' ได้ ดังนั้นมันจึงซ้ำไปซ้ำมาในแต่ละองค์ประกอบ ถ้าฉันจะทำ OfType ในรายการแล้วมันจะทำงาน ForEach อาจจะช้ามากหากต้องตรวจสอบเรื่องนี้จนกว่าคอมไพเลอร์จะปรับให้เหมาะสม
Brent Rittenhouse

ขอบคุณเบรนต์ - ฉันไม่อยู่ที่นั่น foreachไม่กรอง แต่ใช้ประเภทที่ได้รับมากขึ้นเนื่องจากตัวแปรการทำซ้ำจะบีบบังคับคอมไพเลอร์ในการพยายามส่งซึ่งจะล้มเหลวในองค์ประกอบแรกที่ไม่สอดคล้อง
StuartLC


3

นี่ไม่ใช่คำตอบสำหรับคำถามนี้ แต่อาจมีประโยชน์สำหรับบางคน: @SWeko พูดด้วยความแปรปรวนร่วมและความแตกต่างList<X>ไม่สามารถโยนเข้าไปList<Y>ได้ แต่List<X>สามารถถูกโยนลงไปIEnumerable<Y>ได้

ตัวอย่าง:

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

แต่

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

ข้อดีคือมันไม่ได้สร้างรายการใหม่ในหน่วยความจำ


1
ฉันชอบสิ่งนี้เพราะถ้าคุณมีรายการแหล่งข้อมูลขนาดใหญ่จะไม่มีการเข้าชมในช่วงแรก แต่จะมีการโยนที่ไม่สามารถสังเกตเห็นได้เล็กน้อยสำหรับแต่ละรายการที่ประมวลผลโดยผู้รับ ยังไม่มีหน่วยความจำขนาดใหญ่สร้างขึ้น สมบูรณ์แบบสำหรับการประมวลผลสตรีม
Johan Franzén

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.