ฉันจะเพิ่มรายการลงในคอลเลกชัน IEnumerable <T> ได้อย่างไร


405

คำถามของฉันเป็นชื่อด้านบน ตัวอย่างเช่น,

IEnumerable<T> items = new T[]{new T("msg")};
items.ToList().Add(new T("msg2"));

แต่หลังจากทั้งหมดมันมีเพียง 1 รายการภายใน

เรามีวิธีได้items.Add(item)ไหม

ชอบ List<T>


4
IEnumerable<T>มีวัตถุประสงค์เพื่อสอบถามคอลเลกชันเท่านั้น มันเป็นกระดูกสันหลังสำหรับกรอบ LINQ มันก็มักจะเป็นนามธรรมของคอลเลกชันอื่น ๆ บางอย่างเช่นCollection<T>, หรือList<T> Arrayอินเทอร์เฟซให้GetEnumeratorวิธีการที่ส่งคืนอินสแตนซ์IEnumerator<T>ที่เปิดใช้งานการเดินการขยายคอลเลกชันทีละรายการเท่านั้น เมธอดส่วนขยาย LINQ ถูก จำกัด โดยอินเตอร์เฟสนี้ IEnumerable<T>ถูกออกแบบมาเพื่อให้อ่านได้เพียงเพราะมันอาจเป็นตัวแทนของการรวมส่วนของคอลเลกชันหลาย ๆ
จอร์แดน

3
สำหรับตัวอย่างนี้ให้ประกาศIList<T>แทนIEnumerable<T>:IList<T> items = new T[]{new T("msg")}; items.Add(new T("msg2"));
NightOwl888

คำตอบ:


480

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

IEnumerable<string> ReadLines()
{
     string s;
     do
     {
          s = Console.ReadLine();
          yield return s;
     } while (!string.IsNullOrEmpty(s));
}

IEnumerable<string> lines = ReadLines();
lines.Add("foo") // so what is this supposed to do??

อย่างไรก็ตามสิ่งที่คุณสามารถทำได้คือการสร้างออบเจ็กต์ใหม่ IEnumerable (ที่ไม่ได้ระบุประเภท) ซึ่งเมื่อระบุจะจัดเตรียมรายการทั้งหมดของสิ่งเก่ารวมทั้งของคุณเอง คุณใช้Enumerable.Concatสิ่งนั้น

 items = items.Concat(new[] { "foo" });

สิ่งนี้จะไม่เปลี่ยนวัตถุอาร์เรย์ (คุณไม่สามารถแทรกรายการลงในอาร์เรย์ได้) แต่มันจะสร้างวัตถุใหม่ที่จะแสดงรายการทั้งหมดในอาร์เรย์จากนั้น "Foo" นอกจากนี้วัตถุใหม่นั้นจะติดตามการเปลี่ยนแปลงในอาร์เรย์ (เช่นเมื่อใดก็ตามที่คุณระบุมันคุณจะเห็นค่าปัจจุบันของรายการ)


3
การสร้างอาร์เรย์เพื่อเพิ่มค่าเดียวค่อนข้างสูงในค่าใช้จ่าย มันสามารถใช้งานได้อีกครั้งเพื่อกำหนดวิธีการขยายสำหรับ Concat ค่าเดียว
JaredPar

4
มันขึ้นอยู่กับ - มันอาจจะคุ้มค่า แต่ฉันอยากจะเรียกมันว่าการเพิ่มประสิทธิภาพก่อนวัยอันควรเว้นแต่Concatเรียกว่าในวงซ้ำ ๆ และถ้าเป็นเช่นนั้นคุณมีปัญหาที่ใหญ่กว่าอยู่ดีเพราะทุกครั้งที่คุณConcatคุณจะได้รับ enumerator อีกชั้นหนึ่งดังนั้นเมื่อสิ้นสุดการเรียกแบบครั้งเดียวMoveNextจะส่งผลให้สายการโทรมีความยาวเท่ากับจำนวนการวนซ้ำ
Pavel Minaev

2
@Pavel เฮ้ โดยปกติแล้วมันจะไม่ใช่ค่าใช้จ่ายในการสร้างอาเรย์ที่รบกวนจิตใจฉัน มันคือการพิมพ์พิเศษที่ฉันพบว่าน่ารำคาญ :)
JaredPar

2
ดังนั้นฉันจึงเข้าใจอีกครั้งว่า IEnumerable ทำอะไรได้บ้าง ควรดูเท่านั้นไม่ได้ตั้งใจที่จะแก้ไข ขอบคุณพวกคุณ
ldsenow

7
ฉันมีคำขอคุณลักษณะการเชื่อมต่อสำหรับน้ำตาลIEnumerable<T>ในประโยคใน C # รวมถึงการโหลดมากเกินไปoperator+เป็นConcat(โดยวิธีคุณรู้หรือไม่ว่าใน VB คุณสามารถจัดทำดัชนีIEnumerableราวกับว่าเป็นอาร์เรย์ - ใช้ElementAtภายใต้ประทุน): connect.microsoft.com / VisualStudio / ข้อเสนอแนะ / ... หากเราได้รับเกินจำนวนอีกสองConcatรายการที่จะใช้รายการเดียวทั้งทางด้านซ้ายและด้านขวาสิ่งนี้จะลดการพิมพ์ลงไปxs +=x - ดังนั้นให้ไปที่ Mads (Torgersen) เพื่อจัดลำดับความสำคัญใน C # 5.0 :)
Pavel Minaev

98

ประเภทIEnumerable<T>ไม่รองรับการทำงานดังกล่าว วัตถุประสงค์ของIEnumerable<T>อินเทอร์เฟซคือเพื่อให้ผู้บริโภคสามารถดูเนื้อหาของคอลเลกชัน ที่จะไม่แก้ไขค่า

เมื่อคุณดำเนินการเช่น. ToList (). Add () คุณกำลังสร้างใหม่List<T>และเพิ่มมูลค่าให้กับรายการนั้น ไม่มีการเชื่อมต่อกับรายการเดิม

สิ่งที่คุณสามารถทำได้คือใช้วิธีการเพิ่มส่วนขยายเพื่อสร้างใหม่IEnumerable<T>ด้วยมูลค่าเพิ่ม

items = items.Add("msg2");

แม้ในกรณีนี้มันจะไม่แก้ไขIEnumerable<T>วัตถุต้นฉบับ สิ่งนี้สามารถตรวจสอบได้โดยถือการอ้างอิงถึงมัน ตัวอย่างเช่น

var items = new string[]{"foo"};
var temp = items;
items = items.Add("bar");

หลังจากชุดของการดำเนินการนี้ตัวแปรชั่วคราวจะยังคงอ้างอิงเฉพาะการนับด้วยองค์ประกอบ "foo" เดียวในชุดของค่าในขณะที่รายการจะอ้างอิงแตกต่างกันนับด้วยค่า "foo" และ "บาร์"

แก้ไข

ฉันลืมอย่างแน่นอนว่าการเพิ่มไม่ใช่วิธีการต่อเติมแบบทั่วไปIEnumerable<T>เพราะเป็นหนึ่งในวิธีแรกที่ฉันกำหนด นี่ไง

public static IEnumerable<T> Add<T>(this IEnumerable<T> e, T value) {
  foreach ( var cur in e) {
    yield return cur;
  }
  yield return value;
}

12
ที่เรียกว่าConcat:)
พาเวล Minaev

3
@Pavel ขอบคุณสำหรับการชี้ให้เห็นว่า แม้ว่า Concat จะไม่ทำงานเพราะใช้ IEt อีกจำนวนหนึ่ง ฉันวางวิธีการขยายแบบทั่วไปที่ฉันกำหนดไว้ในโครงการของฉัน
JaredPar

10
ฉันจะไม่เรียกว่าAddแม้ว่าเพราะAddประเภท. NET อื่น ๆ (ไม่ใช่แค่คอลเลกชัน) แทบจะกลายพันธุ์คอลเลกชันในสถานที่ อาจจะWith? Concatหรือแม้กระทั่งอาจเป็นเพียงแค่เกินพิกัดของผู้อื่น
Pavel Minaev

4
ฉันเห็นด้วยConcatเกินพิกัดอาจเป็นทางเลือกที่ดีกว่าตามAddปกติหมายถึงความไม่แน่นอน
Dan Abramov

15
สิ่งที่เกี่ยวกับการปรับเปลี่ยนร่างกายเพื่อreturn e.Concat(Enumerable.Repeat(value,1));?
Roy Tinker

58

คุณมีการพิจารณาการใช้ICollection<T>หรือIList<T>การเชื่อมต่อแทนพวกเขาที่มีอยู่สำหรับเหตุผลมากที่คุณต้องการที่จะมีวิธีการในAddIEnumerable<T>

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


31

คู่สั้นวิธีการต่อเติมหวานIEnumerableและIEnumerable<T>ทำเพื่อฉัน:

public static IEnumerable Append(this IEnumerable first, params object[] second)
{
    return first.OfType<object>().Concat(second);
}
public static IEnumerable<T> Append<T>(this IEnumerable<T> first, params T[] second)
{
    return first.Concat(second);
}   
public static IEnumerable Prepend(this IEnumerable first, params object[] second)
{
    return second.Concat(first.OfType<object>());
}
public static IEnumerable<T> Prepend<T>(this IEnumerable<T> first, params T[] second)
{
    return second.Concat(first);
}

Elegant (ดียกเว้นรุ่นที่ไม่ใช่ทั่วไป) น่าเสียดายที่วิธีการเหล่านี้ไม่ได้อยู่ใน BCL


วิธีการเตรียมการครั้งแรกทำให้ฉันมีข้อผิดพลาด "'object []' ไม่มีคำจำกัดความสำหรับ 'Concat' และวิธีการขยายที่ดีที่สุด 'System.Linq.Enumerable.Concat <TSource> (System.Collections.Generic.IEnumerable <TSource>, System.Collections.Generic IEs นับจำนวน <TSource>) มีอาร์กิวเมนต์ที่ไม่ถูกต้อง "
ทิมนิวตัน

@ TimNewton คุณทำผิด ใครจะรู้ว่าทำไมเพราะไม่มีใครสามารถบอกได้จากตัวอย่างของรหัสข้อผิดพลาด Protip: ตรวจจับข้อยกเว้นเสมอและทำToString()ตามที่จับรายละเอียดข้อผิดพลาดเพิ่มเติม ฉันเดาว่าคุณไม่ได้include System.Linq;อยู่ที่ด้านบนของไฟล์ของคุณ แต่ใครจะรู้ ลองหาด้วยตนเองสร้างต้นแบบขั้นต่ำที่แสดงข้อผิดพลาดเดียวกันถ้าคุณทำไม่ได้แล้วขอความช่วยเหลือจากคำถาม

ฉันเพิ่งคัดลอกและวางโค้ดด้านบน พวกเขาทั้งหมดทำงานได้ดียกเว้นภาคผนวกที่ให้ข้อผิดพลาดที่ฉันกล่าวถึง ไม่ใช่ข้อยกเว้นรันไทม์มันเป็นข้อยกเว้นเวลารวบรวม
ทิมนิวตัน

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

อาจเป็นสิ่งที่แตกต่างเกี่ยวกับสภาพแวดล้อมของเรา ฉันใช้ VS2012 และ. NET 4.5 อย่าเสียเวลาอีกต่อไปกับสิ่งนี้ฉันแค่คิดว่าฉันจะชี้ให้เห็นว่ามีปัญหากับรหัสข้างต้นแม้ว่าจะเป็นรหัสขนาดเล็ก ฉัน upvoted คำตอบนี้อย่างใดว่ามีประโยชน์ ขอบคุณ
ทิมนิวตัน

25

ใน. net Core มีวิธีการEnumerable.Appendที่ทำอย่างนั้น

ซอร์สโค้ดของเมธอดพร้อมใช้งานบน GitHub .... การนำไปปฏิบัติ (ความซับซ้อนมากกว่าคำแนะนำในคำตอบอื่น ๆ ) มีค่าดู :)


3
สิ่งนี้ไม่เพียง แต่ในคอร์เท่านั้นยังมีเฟรมเวิร์ก 4.7.1 และใหม่กว่า: docs.microsoft.com/en-us/dotnet/api/ …
sschoof

โปรดทราบว่า Prepend ยังมีอยู่: docs.microsoft.com/en-us/dotnet/api/…
aloisdg กำลังย้ายไปยัง codidact.com

22

ไม่มี IEnumerable ไม่รองรับการเพิ่มรายการ

คุณ 'ทางเลือก' คือ:

var List = new List(items);
List.Add(otherItem);

4
คำแนะนำของคุณไม่ใช่ทางเลือกเดียว ตัวอย่างเช่น ICollection และ IList เป็นทั้งตัวเลือกที่สมเหตุสมผลที่นี่
jason

6
แต่คุณไม่สามารถยกตัวอย่างได้
Paul van Brenk

16

ในการเพิ่มข้อความที่สองคุณต้อง -

IEnumerable<T> items = new T[]{new T("msg")};
items = items.Concat(new[] {new T("msg2")})

1
แต่คุณต้องกำหนดผลลัพธ์ของวิธี Concat ให้กับรายการวัตถุ
Tomasz Przychodzki

1
ใช่โทมัสคุณพูดถูก ฉันได้แก้ไขนิพจน์สุดท้ายเพื่อกำหนดค่าการต่อข้อมูลให้กับรายการ
Aamol

8

ไม่เพียง แต่คุณจะไม่สามารถเพิ่มรายการอย่างที่คุณระบุ แต่ถ้าคุณเพิ่มรายการลงในList<T>(หรือคอลเลกชันอื่น ๆ ที่ไม่ใช่แบบอ่านอย่างเดียว) ที่คุณมีตัวแจงนับที่มีอยู่แล้วตัวระบุจะไม่ถูกต้องInvalidOperationExceptionได้

หากคุณกำลังรวบรวมผลลัพธ์จากแบบสอบถามข้อมูลบางประเภทคุณสามารถใช้ Concatวิธีการขยายได้:

แก้ไข: เริ่มแรกฉันใช้Unionส่วนขยายในตัวอย่างซึ่งไม่ถูกต้องจริงๆ แอปพลิเคชันของฉันใช้อย่างกว้างขวางเพื่อให้แน่ใจว่าข้อความค้นหาที่ซ้อนทับกันไม่ได้ผลลัพธ์ซ้ำกัน

IEnumerable<T> itemsA = ...;
IEnumerable<T> itemsB = ...;
IEnumerable<T> itemsC = ...;
return itemsA.Concat(itemsB).Concat(itemsC);

8

ฉันเพิ่งมาที่นี่เพื่อบอกว่านอกเหนือจากEnumerable.Concatวิธีการขยายดูเหมือนว่าจะมีวิธีอื่นที่มีชื่อEnumerable.Appendใน. NET Core 1.1.1 หลังช่วยให้คุณสามารถเชื่อมต่อรายการเดียวกับลำดับที่มีอยู่ ดังนั้นคำตอบของอามอลจึงสามารถเขียนได้เช่นกัน

IEnumerable<T> items = new T[]{new T("msg")};
items = items.Append(new T("msg2"));

ยังโปรดทราบว่าฟังก์ชั่นนี้จะไม่เปลี่ยนลำดับการป้อนข้อมูลมันแค่คืนเสื้อคลุมที่ใส่ลำดับที่กำหนดและรายการที่ผนวกเข้าด้วยกัน


4

คนอื่น ๆ ได้รับแล้วคำอธิบายที่ดีเกี่ยวกับเหตุผลที่คุณไม่สามารถ (และควรไม่ได้!) IEnumerableจะสามารถเพิ่มรายการไปยัง ฉันจะเพิ่มเฉพาะที่ถ้าคุณกำลังมองหาการเขียนโปรแกรมต่อไปยังอินเทอร์เฟซที่แสดงถึงคอลเลกชันและต้องการวิธีการเพิ่มคุณควรรหัสเพื่อICollectionIListหรือ IEnumerableในฐานะที่เป็นโบนันซ่าเพิ่มการเชื่อมต่อเหล่านี้ดำเนินการ


1

คุณสามารถทำได้

//Create IEnumerable    
IEnumerable<T> items = new T[]{new T("msg")};

//Convert to list.
List<T> list = items.ToList();

//Add new item to list.
list.add(new T("msg2"));

//Cast list to IEnumerable
items = (IEnumerable<T>)items;

8
คำถามนี้ได้รับคำตอบในแบบที่สมบูรณ์มากขึ้น 5 ปีที่ผ่านมา ดูคำตอบที่ยอมรับเป็นตัวอย่างของคำตอบที่ดีสำหรับคำถาม
pquest

1
บรรทัดสุดท้ายคือรายการ items = (IEnumerable <T>)
Belen Martin

0

วิธีที่ง่ายที่สุดในการทำเช่นนั้นก็คือ

IEnumerable<T> items = new T[]{new T("msg")};
List<string> itemsList = new List<string>();
itemsList.AddRange(items.Select(y => y.ToString()));
itemsList.Add("msg2");

จากนั้นคุณสามารถส่งคืนรายการเป็น IEnumerable ได้เช่นกันเพราะใช้อินเทอร์เฟซ IEnumerable


0

ถ้าคุณสร้างวัตถุของคุณเป็นIEnumerable<int> Ones = new List<int>(); แล้วคุณสามารถทำได้((List<int>)Ones).Add(1);


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