ผ่านรายการเดียวเป็น IEnumerable <T>


377

มีวิธีการทั่วไปในการส่งรายการประเภทเดียวTไปยังวิธีที่คาดว่า IEnumerable<T>พารามิเตอร์หรือไม่ ภาษาคือ C #, กรอบงานเวอร์ชัน 2.0

ขณะนี้ฉันกำลังใช้วิธีผู้ช่วย (เป็น. Net 2.0 ดังนั้นฉันจึงมีวิธีการคัดเลือก / ฉายผู้ช่วยที่คล้ายกับ LINQ ทั้งหมด) แต่วิธีนี้ดูเหมือนโง่:

public static class IEnumerableExt
{
    // usage: IEnumerableExt.FromSingleItem(someObject);
    public static IEnumerable<T> FromSingleItem<T>(T item)
    {
        yield return item; 
    }
}

วิธีการอื่น ๆ ของหลักสูตรจะมีการสร้างและเติมList<T>หรือและผ่านมันแทนArrayIEnumerable<T>

[แก้ไข]เป็นวิธีการขยายอาจตั้งชื่อ:

public static class IEnumerableExt
{
    // usage: someObject.SingleItemAsEnumerable();
    public static IEnumerable<T> SingleItemAsEnumerable<T>(this T item)
    {
        yield return item; 
    }
}

ฉันทำอะไรบางอย่างหายไปหรือเปล่า

[แก้ไข 2]เราพบsomeObject.Yield()(ตามที่ @Peter แนะนำในความคิดเห็นด้านล่าง) เป็นชื่อที่ดีที่สุดสำหรับวิธีการขยายนี้ส่วนใหญ่เป็นเรื่องสั้นดังนั้นที่นี่มันพร้อมกับความคิดเห็น XML หากใครต้องการคว้ามัน:

public static class IEnumerableExt
{
    /// <summary>
    /// Wraps this object instance into an IEnumerable&lt;T&gt;
    /// consisting of a single item.
    /// </summary>
    /// <typeparam name="T"> Type of the object. </typeparam>
    /// <param name="item"> The instance that will be wrapped. </param>
    /// <returns> An IEnumerable&lt;T&gt; consisting of a single item. </returns>
    public static IEnumerable<T> Yield<T>(this T item)
    {
        yield return item;
    }
}

6
ฉันจะทำให้การปรับเปลี่ยนเล็กน้อยในร่างกายของวิธีการขยาย: if (item == null) yield break; ตอนนี้คุณกำลังหยุดผ่าน null เช่นเดียวกับการใช้ประโยชน์จาก (เล็กน้อย) รูปแบบวัตถุ null IEnumerableสำหรับ ( foreach (var x in xs)จัดการกับความว่างเปล่าได้xsดี) บังเอิญฟังก์ชั่นนี้เป็นหน่วย monadic สำหรับรายการ monad ที่เป็นIEnumerable<T>และได้รับ monad love-fest ที่ Microsoft ฉันประหลาดใจบางอย่างเช่นนี้ไม่ได้อยู่ในกรอบในสถานที่แรก
Matt Enright

2
สำหรับวิธีการขยายที่คุณไม่ควรตั้งชื่อมันAsEnumerableเพราะในตัวส่วนขยายที่มีชื่อที่มีอยู่แล้ว (เมื่อTดำเนินการIEnumerableเช่น, string.)
Jon-Eric

22
วิธีการเกี่ยวกับการตั้งชื่อวิธีYield? ไม่มีอะไรจะเต้นเร็ว
Philip Guin

4
คำแนะนำการตั้งชื่อที่นี่ "SingleItemAsEnumerable" เป็น verbose เล็กน้อย "Yield" อธิบายการใช้งานมากกว่าส่วนต่อประสาน - ซึ่งไม่ดี สำหรับชื่อที่ดีกว่าฉันแนะนำ "AsSingleton" ซึ่งสอดคล้องกับความหมายที่แท้จริงของพฤติกรรม
Earth Engine

4
ฉันเกลียดการleft==nullตรวจสอบที่นี่ มันแบ่งความสวยงามของรหัสและหยุดรหัสที่ยืดหยุ่นได้มากขึ้น - ถ้าวันใดคุณเปิดออกจำเป็นต้องสร้างซิงเกิลตันด้วยสิ่งที่สามารถเป็นโมฆะได้? ฉันหมายความว่าnew T[] { null }ไม่เหมือนกันnew T[] {}และบางวันคุณอาจจำเป็นต้องแยกแยะพวกเขา
Earth Engine

คำตอบ:


100

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


ดีถ้ารายการ / อาร์เรย์ที่ถูกสร้างขึ้นเฉพาะกิจขอบเขตสิ้นสุดลงหลังจากที่โทรวิธีการดังนั้นจึงไม่ควรทำให้เกิดปัญหา
มาริโอ F

หากส่งเป็นอาเรย์จะเปลี่ยนได้อย่างไร? ฉันเดาว่ามันอาจถูกส่งไปยังอาร์เรย์แล้วเปลี่ยนการอ้างอิงเป็นอย่างอื่น แต่สิ่งที่ดีจะทำอย่างไร (ฉันอาจจะหายไปบางอย่าง แต่ ... )
Svish

2
สมมติว่าคุณตัดสินใจที่จะสร้างหนึ่งวิธีนับผ่านสองวิธีที่แตกต่างกัน ... แล้วคนแรกที่โยนมันไปยังอาร์เรย์และเปลี่ยนเนื้อหา จากนั้นคุณส่งผ่านเป็นอาร์กิวเมนต์ไปยังวิธีอื่นโดยไม่ทราบถึงการเปลี่ยนแปลง ฉันไม่ได้บอกว่าเป็นไปได้ว่าการมีบางสิ่งบางอย่างไม่แน่นอนเมื่อไม่จำเป็นต้องเป็นระเบียบน้อยกว่าการไม่เปลี่ยนรูป naturla
Jon Skeet

3
นี่เป็นคำตอบที่ยอมรับได้และเป็นไปได้มากที่จะอ่านดังนั้นฉันจะเพิ่มข้อกังวลของฉันที่นี่ ฉันพยายามวิธีนี้ แต่ที่ยากจนโทรก่อนหน้านี้การรวบรวมของฉันไปmyDict.Keys.AsEnumerable()ที่เป็นประเภทmyDict Dictionary<CheckBox, Tuple<int, int>>หลังจากเปลี่ยนชื่อวิธีการขยายเป็นSingleItemAsEnumerableทุกอย่างเริ่มทำงาน ไม่สามารถแปลง IEnumerable <Dictionary <CheckBox, System.Tuple <int, int >>. KeyCollection> 'เป็น' IEnumerable <CheckBox> 'ได้' มีการแปลงอย่างชัดเจนอยู่ (คุณหายตัวไปจากการร่ายหรือไม่) ฉันสามารถใช้ชีวิตอยู่กับแฮ็คได้ซักพัก แต่แฮกเกอร์พลังงานอื่นสามารถหาวิธีที่ดีกว่าได้ไหม
Hamish Grubijan

3
@HamishGrubijan: ดูเหมือนว่าคุณต้องการที่dictionary.Valuesจะเริ่มต้นด้วย โดยทั่วไปมันไม่ชัดเจนว่าเกิดอะไรขึ้น แต่ฉันขอแนะนำให้คุณเริ่มต้นคำถามใหม่เกี่ยวกับมัน
Jon Skeet

133

ถ้าวิธีการคาดหวังว่าIEnumerableคุณจะต้องผ่านสิ่งที่เป็นรายการแม้ว่ามันจะมีเพียงองค์ประกอบเดียวเท่านั้น

ที่ผ่านไป

new[] { item }

เพราะฉันคิดว่าควรจะเพียงพอ


32
ผมคิดว่าคุณก็อาจจะลดลงทีเพราะมันจะได้รับการอนุมาน (ถ้าฉันผิดมากที่นี่: p)
Svish

2
ฉันคิดว่ามันไม่ได้อนุมานใน C # 2.0
Groo

4
"ภาษาคือ C #, รุ่นกรอบ 2" C # 3 คอมไพเลอร์สามารถอนุมาน T, และรหัสที่จะเข้ากันได้อย่างเต็มที่กับ .NET 2.
sisve

คำตอบที่ดีที่สุดอยู่ที่ด้านล่าง!
Falco Alexander

2
@Svish ฉันแนะนำและแก้ไขสำหรับคำตอบให้ทำเช่นนั้น - เราอยู่ในปี 2020 ดังนั้นจึงควรเป็น "มาตรฐาน" imho
OschtärEi

120

ใน C # 3.0 คุณสามารถใช้คลาส System.Linq.Enumerable:

// using System.Linq

Enumerable.Repeat(item, 1);

สิ่งนี้จะสร้าง IEnumerable ใหม่ที่มีเฉพาะรายการของคุณ


26
ฉันคิดว่าวิธีนี้จะทำให้การอ่านรหัสยากขึ้น :( ทำซ้ำในองค์ประกอบเดียวค่อนข้างเคาน์เตอร์คุณไม่คิดอย่างนั้นเหรอ
Drahakar

6
ทำซ้ำเมื่ออ่านได้ดีกับฉัน ทางออกที่ง่ายกว่าโดยไม่ต้องกังวลเกี่ยวกับการเพิ่มอีกวิธีการขยายอีกวิธีหนึ่งและมั่นใจได้ว่าจะมีเนมสเปซรวมอยู่ทุกที่ที่คุณต้องการใช้
si618

13
ใช่ฉันแค่ใช้new[]{ item }ตัวเองฉันแค่คิดว่านั่นเป็นทางออกที่น่าสนใจ
luksan

7
วิธีนี้คือเงิน
ProVega

IMHO นี่ควรเป็นคำตอบที่ยอมรับได้ ง่ายต่อการอ่านกระชับและนำมาใช้ในบรรทัดเดียวบน BCL โดยไม่มีวิธีการขยายที่กำหนดเอง
Ryan

35

ใน C # 3 (ฉันรู้ว่าคุณพูด 2) คุณสามารถเขียนวิธีการขยายทั่วไปซึ่งอาจทำให้ไวยากรณ์เป็นที่ยอมรับมากขึ้น:

static class IEnumerableExtensions
{
    public static IEnumerable<T> ToEnumerable<T>(this T item)
    {
        yield return item;
    }
}

item.ToEnumerable()รหัสลูกค้าแล้ว


ขอบคุณฉันทราบดีว่า (ทำงานใน. Net 2.0 หากฉันใช้ C # 3.0) ฉันแค่สงสัยว่ามีกลไกในตัวสำหรับสิ่งนี้หรือไม่
Groo

12
นี่เป็นทางเลือกที่ดีจริงๆ ฉันเพียงต้องการแนะนำให้เปลี่ยนชื่อเพื่อToEnumerableหลีกเลี่ยงการสับสนกับSystem.Linq.Enumerable.AsEnumerable
Fede

3
ในฐานะที่เป็นบันทึกด้านข้างมีToEnumerableวิธีส่วนขยายอยู่แล้วIObservable<T>ดังนั้นชื่อวิธีนี้จึงรบกวนการประชุมที่มีอยู่ จริงๆแล้วฉันขอแนะนำไม่ให้ใช้วิธีการขยายเลยเพียงเพราะมันธรรมดาเกินไป
cwharris

14

วิธีการช่วยเหลือนี้ใช้ได้กับไอเท็มหรือหลายรายการ

public static IEnumerable<T> ToEnumerable<T>(params T[] items)
{
    return items;
}    

1
รูปแบบที่มีประโยชน์เนื่องจากสนับสนุนมากกว่าหนึ่งรายการ ฉันชอบที่มันต้องอาศัยparamsการสร้างอาร์เรย์ดังนั้นโค้ดผลลัพธ์จึงดูสะอาดตา ยังไม่ได้ตัดสินใจว่าฉันชอบมากกว่าหรือน้อยกว่าnew T[]{ item1, item2, item3 };สำหรับหลาย ๆ รายการ
ToolmakerSteve

ฉลาด ! รักมัน
ซูรุ Furuta

10

ฉันประหลาดใจที่ไม่มีใครแนะนำวิธีการโอเวอร์โหลดใหม่พร้อมอาร์กิวเมนต์ประเภท T เพื่อทำให้ API ของลูกค้าง่ายขึ้น

public void DoSomething<T>(IEnumerable<T> list)
{
    // Do Something
}

public void DoSomething<T>(T item)
{
    DoSomething(new T[] { item });
}

ตอนนี้รหัสลูกค้าของคุณสามารถทำได้ดังนี้

MyItem item = new MyItem();
Obj.DoSomething(item);

หรือกับรายการ:

List<MyItem> itemList = new List<MyItem>();
Obj.DoSomething(itemList);

19
ยิ่งไปกว่านั้นคุณสามารถมีDoSomething<T>(params T[] items)ซึ่งหมายความว่าคอมไพเลอร์จะจัดการการแปลงจากรายการเดียวไปยังอาร์เรย์ (นอกจากนี้ยังจะช่วยให้คุณสามารถที่จะผ่านในหลายรายการแยกต่างหากและอีกครั้งคอมไพเลอร์จะจัดการแปลงให้อาร์เรย์สำหรับคุณ.)
LukeH

7

อย่างใดอย่างหนึ่ง (ตามที่ได้รับการกล่าวก่อนหน้านี้)

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

หรือ

MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(myObject, 1));

ในฐานะที่เป็นบันทึกด้านข้างรุ่นสุดท้ายอาจดีถ้าคุณต้องการรายการว่างของวัตถุที่ไม่ระบุชื่อเช่น

var x = MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(new { a = 0, b = "x" }, 0));

ขอบคุณแม้ว่า Enumerable.Repeat นั้นเป็นสิ่งใหม่ใน. Net 3.5 ดูเหมือนว่าจะทำงานคล้ายกับวิธีการช่วยเหลือด้านบน
Groo

7

ตามที่ฉันเพิ่งค้นพบและเห็นว่าผู้ใช้ LukeH แนะนำเช่นกันวิธีง่ายๆในการทำเช่นนี้คือ:

public static void PerformAction(params YourType[] items)
{
    // Forward call to IEnumerable overload
    PerformAction(items.AsEnumerable());
}

public static void PerformAction(IEnumerable<YourType> items)
{
    foreach (YourType item in items)
    {
        // Do stuff
    }
}

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

ฉันไม่แน่ใจ 100% เกี่ยวกับประสิทธิภาพของการใช้วิธี AsEnumerable แต่มันใช้งานได้ดี

ปรับปรุง: ฟังก์ชั่น AsEnumerable ดูมีประสิทธิภาพมาก! ( อ้างอิง )


จริงๆแล้วคุณไม่ต้องการ.AsEnumerable()เลย อาร์เรย์การดำเนินการอยู่แล้วYourType[] IEnumerable<YourType>แต่คำถามของฉันอ้างถึงกรณีที่มีเฉพาะวิธีที่สอง (ในตัวอย่างของคุณ) และคุณกำลังใช้. NET 2.0 และคุณต้องการผ่านรายการเดียว
Groo

2
ใช่มันเป็นเช่นนั้น แต่คุณจะพบว่าคุณอาจได้รับสแต็กล้น ;-)
teppicymon

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

2
วิธีการเกี่ยวกับการกำหนดIEnumerable<T> MakeEnumerable<T>(params T[] items) {return items;}วิธีการเพียงแค่? หนึ่งแล้วสามารถใช้กับสิ่งที่คาดIEnumerable<T>สำหรับจำนวนของรายการใด ๆ ที่ไม่ต่อเนื่อง รหัสควรมีประสิทธิภาพเท่ากับการกำหนดคลาสพิเศษเพื่อส่งคืนรายการเดียว
supercat

6

แม้ว่าจะใช้วิธีมากเกินไป แต่ฉันเชื่อว่าบางคนอาจพบว่าส่วนขยายแบบโต้ตอบมีประโยชน์

Interactive Extensions (Ix) จาก Microsoft มีวิธีการดังต่อไปนี้

public static IEnumerable<TResult> Return<TResult>(TResult value)
{
    yield return value;
}

ซึ่งสามารถใช้ประโยชน์ได้ดังนี้:

var result = EnumerableEx.Return(0);

Ix เพิ่มฟังก์ชันการทำงานใหม่ที่ไม่พบในวิธีการขยาย Linq ดั้งเดิมและเป็นผลโดยตรงของการสร้างส่วนขยายที่มีปฏิกิริยา (Rx)

คิดว่าLinq Extension Methods+ Ix= สำหรับRxIEnumerable

คุณสามารถค้นหาทั้งRx และ Ix ใน CodePlex


5

นี่คือ 30% เร็วกว่าyieldหรือEnumerable.Repeatเมื่อใช้ในforeachเนื่องจากการเพิ่มประสิทธิภาพของคอมไพเลอร์ C #นี้และประสิทธิภาพเดียวกันในกรณีอื่น ๆ

public struct SingleSequence<T> : IEnumerable<T> {
    public struct SingleEnumerator : IEnumerator<T> {
        private readonly SingleSequence<T> _parent;
        private bool _couldMove;
        public SingleEnumerator(ref SingleSequence<T> parent) {
            _parent = parent;
            _couldMove = true;
        }
        public T Current => _parent._value;
        object IEnumerator.Current => Current;
        public void Dispose() { }

        public bool MoveNext() {
            if (!_couldMove) return false;
            _couldMove = false;
            return true;
        }
        public void Reset() {
            _couldMove = true;
        }
    }
    private readonly T _value;
    public SingleSequence(T value) {
        _value = value;
    }
    public IEnumerator<T> GetEnumerator() {
        return new SingleEnumerator(ref this);
    }
    IEnumerator IEnumerable.GetEnumerator() {
        return new SingleEnumerator(ref this);
    }
}

ในการทดสอบนี้:

    // Fastest among seqs, but still 30x times slower than direct sum
    // 49 mops vs 37 mops for yield, or c.30% faster
    [Test]
    public void SingleSequenceStructForEach() {
        var sw = new Stopwatch();
        sw.Start();
        long sum = 0;
        for (var i = 0; i < 100000000; i++) {
            foreach (var single in new SingleSequence<int>(i)) {
                sum += single;
            }
        }
        sw.Stop();
        Console.WriteLine($"Elapsed {sw.ElapsedMilliseconds}");
        Console.WriteLine($"Mops {100000.0 / sw.ElapsedMilliseconds * 1.0}");
    }

ขอขอบคุณมันสมเหตุสมผลที่จะสร้างโครงสร้างสำหรับกรณีนี้เพื่อลดภาระ GC ในลูปที่แคบ
Groo

1
ทั้งการแจงนับและนับจะได้รับบรรจุกล่องเมื่อกลับมา ....
สตีเฟ่นดรูว์

2
อย่าเปรียบเทียบโดยใช้ Stopwatch ใช้ Benchmark.NET github.com/dotnet/BenchmarkDotNet
NN_

1
เพียงแค่วัดมันและnew [] { i }ตัวเลือกจะเร็วขึ้นประมาณ 3.2 เท่าจากนั้นตัวเลือกของคุณ ยังมีตัวเลือกของคุณเกี่ยวกับนามสกุลแล้วได้เร็วขึ้น 1.2 yield returnเท่าด้วย
lorond

คุณสามารถเร่งความเร็วได้โดยใช้การเพิ่มประสิทธิภาพคอมไพเลอร์ C #: เพิ่มวิธีการSingleEnumerator GetEnumerator()คืนค่าโครงสร้าง คอมไพเลอร์ C # จะใช้วิธีนี้ (มันดูผ่านการพิมพ์เป็ด) แทนที่จะเป็นอินเทอร์เฟซและหลีกเลี่ยงการชกมวย หลายตัวในคอลเลกชันใช้เคล็ดลับนี้เช่นรายการ หมายเหตุ: สิ่งนี้จะใช้ได้เฉพาะเมื่อคุณมีผู้แนะนำโดยตรงSingleSequenceเช่นในตัวอย่างของคุณ หากคุณเก็บไว้ในIEnumerable<>ตัวแปรเคล็ดลับจะไม่ทำงาน (จะใช้วิธีการเชื่อมต่อ)
enzi

5

IanGมีโพสต์ที่ดีในหัวข้อแนะนำEnumerableFrom()เป็นชื่อ (และกล่าวถึงว่า Haskell และ Rx เรียกมันว่าReturn) IIRC F # เรียกมันกลับเกินไป F # 's โทรผู้ประกอบการSeqsingleton<'T>

ดึงดูดใจหากคุณพร้อมที่จะเป็น C # - ศูนย์กลางคือการเรียกมันว่าYield[ยิ่งทำให้ผู้ที่yield returnเกี่ยวข้องตระหนักถึงมัน]

หากคุณสนใจในแง่มุมที่สมบูรณ์แบบของ James Michael Hare ก็มีศูนย์ส่งคืนหรือรายการหนึ่งรายการเช่นกันซึ่งคุ้มค่ากับการสแกน


4

นี่อาจจะไม่ดีไปกว่านี้ แต่มันเจ๋งมาก:

Enumerable.Range(0, 1).Select(i => item);

มันไม่ใช่. มันแตกต่าง.
nawfal

ewww นั่นเป็นรายละเอียดมากมายเพียงแค่เปลี่ยนไอเท็มให้เป็นแบบนับจำนวน
ToolmakerSteve

1
นี่เทียบเท่ากับการใช้เครื่อง Rube Goldbergทำอาหารเช้า ใช่มันใช้งานได้ แต่กระโดดได้ 10 ห่วงเพื่อไปที่นั่น สิ่งง่าย ๆ เช่นนี้อาจเป็นคอขวดของประสิทธิภาพเมื่อเสร็จในวงแคบ ๆ ที่ดำเนินการหลายล้านครั้ง ในทางปฏิบัติด้านประสิทธิภาพไม่ได้มีความสำคัญเท่าไรนักในกรณี 99% แต่โดยส่วนตัวแล้วฉันคิดว่ามันเป็นเรื่องที่ไม่จำเป็นมากเกินไป
enzi

4

ฉันเห็นด้วยกับความคิดเห็นของ @ EarthEngine ต่อโพสต์ดั้งเดิมซึ่งก็คือ 'AsSingleton' เป็นชื่อที่ดีกว่า ดูรายการวิกิพีเดียนี้ จากนั้นตามมาจากคำจำกัดความของ singleton ว่าหากค่า Null ถูกส่งผ่านเป็นอาร์กิวเมนต์ที่ 'AsSingleton' ควรคืนค่า IEnumerable ด้วยค่า Null เพียงค่าเดียวแทนที่จะเป็นค่าว่าง IEnumerable ซึ่งจะยุติการif (item == null) yield break;โต้วาที ฉันคิดว่าทางออกที่ดีที่สุดคือมีสองวิธี: 'AsSingleton' และ 'AsSingletonOrEmpty'; โดยที่ในกรณีที่มีการส่งผ่านค่า null เป็นอาร์กิวเมนต์ 'AsSingleton' จะส่งคืนค่า Null เพียงค่าเดียวและ 'AsSingletonOrEmpty' จะส่งคืน IEnumerable ที่ว่างเปล่า แบบนี้:

public static IEnumerable<T> AsSingletonOrEmpty<T>(this T source)
{
    if (source == null)
    {
        yield break;
    }
    else
    {
        yield return source;
    }
}

public static IEnumerable<T> AsSingleton<T>(this T source)
{
    yield return source;
}

จากนั้นเหล่านี้จะมากหรือน้อยคล้ายกับส่วนขยาย 'First' และ 'FirstOrDefault' ใน IEnumerable ที่เพิ่งรู้สึกถูกต้อง


2

วิธีที่ง่ายที่สุดที่ฉันจะพูดnew T[]{item};คือ ไม่มีไวยากรณ์ในการทำเช่นนี้ สิ่งที่ใกล้เคียงที่สุดที่ฉันคิดได้คือparamsคำหลัก แต่แน่นอนว่าคุณต้องมีการเข้าถึงข้อกำหนดของเมธอดและใช้กับอาร์เรย์เท่านั้น


2
Enumerable.Range(1,1).Select(_ => {
    //Do some stuff... side effects...
    return item;
});

โค้ดด้านบนมีประโยชน์เมื่อใช้งาน like

var existingOrNewObject = MyData.Where(myCondition)
       .Concat(Enumerable.Range(1,1).Select(_ => {
           //Create my object...
           return item;
       })).Take(1).First();

ในตัวอย่างโค้ดข้างต้นไม่มีการตรวจสอบว่าง / ว่างเปล่าและรับประกันว่าจะส่งคืนวัตถุเดียวเท่านั้นโดยไม่ต้องกลัวข้อยกเว้น นอกจากนี้เนื่องจากขี้เกียจการปิดจะไม่ถูกดำเนินการจนกว่าจะพิสูจน์ว่าไม่มีข้อมูลที่มีอยู่ตรงกับเกณฑ์


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

ฉันเห็นคำตอบนี้จำนวนมากรวมถึงส่วนที่ฉันตอบกลับถูกแก้ไขโดยคนอื่นในภายหลัง แต่ถ้าเจตนาของตัวอย่างที่สองคือการให้ค่าเริ่มต้นถ้าMyData.Where(myCondition)ว่างที่มีอยู่แล้วเป็นไปได้ (และง่าย) ด้วย:DefaultIfEmpty() var existingOrNewObject = MyData.Where(myCondition).DefaultIfEmpty(defaultValue).First();ซึ่งสามารถทำให้ง่ายขึ้นvar existingOrNewObject = MyData.FirstOrDefault(myCondition);หากคุณต้องการdefault(T)และไม่ใช่ค่าที่กำหนดเอง
BACON

0

ฉันมาช้าไปงานปาร์ตี้ แต่ฉันจะแบ่งปันวิธีของฉันต่อไป ปัญหาของฉันคือฉันต้องการผูก ItemSource หรือ WPF TreeView กับวัตถุเดียว ลำดับชั้นมีลักษณะดังนี้:

โครงการ> แปลง (s)> ห้อง

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

public class ProjectClass : IEnumerable<ProjectClass>
{
    private readonly SingleItemEnumerator<AufmassProjekt> enumerator;

    ... 

    public IEnumerator<ProjectClass > GetEnumerator() => this.enumerator;

    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}

และสร้างตัวแจงนับของฉันเอง:

public class SingleItemEnumerator : IEnumerator
{
    private bool hasMovedOnce;

    public SingleItemEnumerator(object current)
    {
        this.Current = current;
    }

    public bool MoveNext()
    {
        if (this.hasMovedOnce) return false;
        this.hasMovedOnce = true;
        return true;
    }

    public void Reset()
    { }

    public object Current { get; }
}

public class SingleItemEnumerator<T> : IEnumerator<T>
{
    private bool hasMovedOnce;

    public SingleItemEnumerator(T current)
    {
        this.Current = current;
    }

    public void Dispose() => (this.Current as IDisposable).Dispose();

    public bool MoveNext()
    {
        if (this.hasMovedOnce) return false;
        this.hasMovedOnce = true;
        return true;
    }

    public void Reset()
    { }

    public T Current { get; }

    object IEnumerator.Current => this.Current;
}

นี่อาจไม่ใช่วิธีที่ "สะอาดที่สุด" แต่ใช้ได้สำหรับฉัน

แก้ไข
เพื่อรักษาหลักการความรับผิดชอบเดียวที่ @Groo ชี้ให้เห็นฉันได้สร้างคลาส wrapper ใหม่:

public class SingleItemWrapper : IEnumerable
{
    private readonly SingleItemEnumerator enumerator;

    public SingleItemWrapper(object item)
    {
        this.enumerator = new SingleItemEnumerator(item);
    }

    public object Item => this.enumerator.Current;

    public IEnumerator GetEnumerator() => this.enumerator;
}

public class SingleItemWrapper<T> : IEnumerable<T>
{
    private readonly SingleItemEnumerator<T> enumerator;

    public SingleItemWrapper(T item)
    {
        this.enumerator = new SingleItemEnumerator<T>(item);
    }

    public T Item => this.enumerator.Current;

    public IEnumerator<T> GetEnumerator() => this.enumerator;

    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}

ซึ่งฉันใช้แบบนี้

TreeView.ItemSource = new SingleItemWrapper(itemToWrap);

แก้ไข 2
ฉันแก้ไขข้อผิดพลาดด้วยMoveNext()วิธีการ


SingleItemEnumerator<T>ระดับทำให้รู้สึก แต่ทำให้ชั้นเป็น "รายการเดียวIEnumerableของตัวเอง" ดูเหมือนว่าการละเมิดหลักการรับผิดชอบเดียว บางทีมันอาจทำให้มันใช้งานได้จริง แต่ฉันก็ยังอยากห่อมันตามที่ต้องการ
Groo

0

ที่จะยื่นภายใต้ "ไม่จำเป็นต้องเป็นทางออกที่ดีแต่ก็ยัง ... วิธีการแก้ปัญหา" หรือ "เคล็ดลับ Stupid LINQ" คุณสามารถรวมEnumerable.Empty<>()กับEnumerable.Append<>()...

IEnumerable<string> singleElementEnumerable = Enumerable.Empty<string>().Append("Hello, World!");

... หรือEnumerable.Prepend<>()...

IEnumerable<string> singleElementEnumerable = Enumerable.Empty<string>().Prepend("Hello, World!");

วิธีการสองวิธีหลังพร้อมใช้งานตั้งแต่. NET Framework 4.7.1 และ. NET Core 1.0

นี้เป็นทางออกที่สามารถทำงานได้ถ้าคนใดคนหนึ่งจริงๆเจตนาในการใช้วิธีการที่มีอยู่แทนการเขียนของตัวเอง แต่ฉันลังเลว่านี้จะมากหรือน้อยกว่าที่ชัดเจนวิธีการแก้ปัญหา นี่เป็นโค้ดที่ยาวกว่าแน่นอน (ส่วนหนึ่งเนื่องจากการอนุมานพารามิเตอร์ไม่สามารถทำได้) และสร้างวัตถุตัวแจงนับจำนวนสองเท่าEnumerable.Repeat<>()Empty<>()

ปัดเศษออกนี้ "คุณรู้วิธีการเหล่านี้มีอยู่?" คำตอบArray.Empty<>()อาจถูกแทนที่Enumerable.Empty<>()ด้วย แต่มันยากที่จะโต้แย้งว่าทำให้สถานการณ์ ... ดีขึ้น


0

ฉันเพิ่งถามสิ่งเดียวกันในโพสต์อื่น ( มีวิธีที่จะเรียกวิธี C # ที่ต้องการ IEnumerable <T> ด้วยค่าเดียวหรือไม่? ... ด้วยการเปรียบเทียบ )

ฉันต้องการให้ผู้คนแวะมาที่นี่เพื่อดูการเปรียบเทียบเปรียบเทียบสั้น ๆ ที่แสดงที่โพสต์ใหม่สำหรับ 4 วิธีที่นำเสนอในคำตอบเหล่านี้

ดูเหมือนว่าการเขียนnew[] { x }ข้อโต้แย้งไปยังเมธอดนั้นเป็นวิธีที่สั้นและเร็วที่สุด

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