web-dev-qa-db-ja.com

単一のアイテムをIEnumerable <T>として渡す

タイプ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>またはArrayを作成して入力し、IEnumerable<T>の代わりに渡すことです。

[編集]拡張メソッドとして、名前が付けられます:

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

ここに何かが足りませんか?

[Edit2]主に簡潔にするために、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;
    }
}
345
Groo

あなたのヘルパーメソッドはそれを行う最もクリーンな方法です、IMO。リストまたは配列を渡すと、悪意のないコードがそれをキャストして内容を変更する可能性があり、状況によっては奇妙な動作につながる可能性があります。読み取り専用のコレクションを使用することもできますが、さらにラップが必要になる可能性があります。私はあなたの解決策がそれと同じくらいきれいだと思います。

88
Jon Skeet

メソッドがIEnumerableを予期している場合、1つの要素のみが含まれている場合でも、リストであるものを渡す必要があります。

通過

new T[] { item }

議論は十分だと思うので

117
Mario F

C#3.0では、System.Linq.Enumerableクラスを利用できます。

// using System.Linq

Enumerable.Repeat(item, 1);

これにより、アイテムのみを含む新しいIEnumerableが作成されます。

111
luksan

C#3(私はあなたが2と言ったことを知っています)で、構文をもう少し受け入れられるようにする一般的な拡張メソッドを書くことができます:

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

クライアントコードはitem.ToEnumerable()です。

27
Ðаn

このヘルパーメソッドは、1つまたは複数のアイテムに対して機能します。

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

クライアントAPIを簡素化するために、型Tの引数を持つメソッドの新しいオーバーロードを提案する人が誰もいなかったことにちょっと驚きました。

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);
9
Joshua Starner

見つけたばかりで、ユーザー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
    }
}

このパターンを使用すると、同じ機能をさまざまな方法で呼び出すことができます。単一のアイテム。複数のアイテム(コンマ区切り);配列;リスト;列挙など.

ただし、AsEnumerableメソッドを使用する効率については100%確信はありませんが、うまく機能します。

更新:AsEnumerable関数は非常に効率的です! ( 参照

7
teppicymon

1つの方法ではやり過ぎですが、一部の人々はInteractive Extensionsが便利だと思うかもしれません。

MicrosoftのInteractive Extensions(Ix)には、次のメソッドが含まれています。

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

次のように利用できます:

var result = EnumerableEx.Return(0);

Ixは、元のLinq拡張メソッドにはない新しい機能を追加し、Reactive Extensions(Rx)を作成した直接の結果です。

Linq Extension Methods + Ix = RxIEnumerableであると考えてください。

CodePlexでのRxとIx の両方を見つけることができます。

6
cwharris

どちらか(前述のとおり)

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

または

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

補足として、匿名オブジェクトの空のリストが必要な場合は、最後のバージョンもニースになります。

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

これはyieldまたはEnumerable.Repeatforeachで使用した場合、 このC#コンパイラーの最適化 により、他の場合と同じパフォーマンスであるため、30%高速です。

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}");
    }
5
V.B.

私は元の投稿に対する@EarthEngineのコメントに同意します。つまり、「AsSingleton」の方が良い名前です。 このウィキペディアのエントリを参照 。次に、シングルトンの定義から、NULL値が引数として渡された場合、「AsSingleton」はif (item == null) yield break;ディベートを解決する空のIEnumerableではなく、単一のNULL値を持つIEnumerableを返す必要があります。最善の解決策は、「AsSingleton」と「AsSingletonOrEmpty」の2つのメソッドを持つことだと思います。ここで、引数として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;
}

それから、これらは多かれ少なかれ、IEnumerableの 'First'および 'FirstOrDefault'拡張メソッドに似ていますが、これらはちょうどいい感じです。

4
Jason Boyd

これは良くないかもしれませんが、それはちょっとクールです:

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

IanG には トピックに関する良い投稿 があり、EnumerableFrom()を名前として提案し、HaskellとRxがReturnと呼ぶことを議論が指摘していることに言及しています。 IIRC F#はそれもReturnと呼ぶ。 F#の Seqは演算子singleton<'T> を呼び出します。

C#中心になる準備ができているなら、それをYieldと呼びます[それを実現することに関与するyield returnをほのめかします]。

パフォーマンスの面に興味があるなら、James Michael Hareには 個または1個のアイテムを返す もあり、これはスキャンする価値があります。

2
Ruben Bartelink

私が言う最も簡単な方法はnew T[]{item};です。これを行う構文はありません。私が考えることができる最も近いものはparamsキーワードですが、もちろん、メソッド定義にアクセスする必要があり、配列でのみ使用できます。

1
FacticiusVir

私はパーティーに少し遅れましたが、とにかく自分の道を共有します。私の問題は、ItemSourceまたはWPF TreeViewを単一のオブジェクトにバインドすることでした。階層は次のようになります。

プロジェクト>プロット>ルーム

常に1つのプロジェクトのみが存在していましたが、いくつかの提案のように、その1つのオブジェクトのみを含むコレクションを渡すことなく、プロジェクトをツリーに表示したかったのです。
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が指摘したように 単一責任原則 を維持するために、新しいラッパークラスを作成しました。

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

EDIT 2
MoveNext()メソッドの間違いを修正しました。

0
IDarkCoder
Enumerable.Range(1,1).Select(_, item); 
0
frhack