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>またはを作成してデータを入力し、のArray代わりに渡す方法もありIEnumerable<T>ます。

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

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

ここで何か不足していますか?

[編集2]someObject.Yield()(以下のコメントで@Peterが示唆しているように)この拡張メソッドの主な名前は簡潔にするために最適な名前であることがわかりました。

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をうまく処理します)。ちなみに、この関数はリストモナドのモナド単位です。つまりIEnumerable<T>、Microsoftでのモナド愛祭を考えると、このようなものがそもそもフレームワークにないことに驚いています。
Matt Enright、

2
拡張メソッドの場合AsEnumerable、その名前の組み込み拡張がすでに存在するため、名前を付けないでください。(ときにT実装しIEnumerable、例えば、string。)
ジョン・エリック・

22
メソッドに名前を付けてみYieldませんか?簡潔さには勝るものはありません。
Philip Guin、

4
ここで名前の提案。「SingleItemAsEnumerable」は少し冗長です。「Yield」は、インターフェースではなく実装について説明しています。これは良くありません。より適切な名前を付けるには、動作の正確な意味に対応する「AsSingleton」をお勧めします。
Earth Engine

4
left==nullここで小切手嫌いです。それはコードの美しさを壊し、コードがより柔軟になるのを止めます-ある日、nullになる可能性のあるものでシングルトンを生成する必要があると判明した場合はどうでしょうか?new T[] { null }つまり、と同じではありませんnew T[] {}。いつかは区別する必要があるかもしれません。
Earth Engine

回答:


100

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


よく、それは問題発生することはありませんので、リスト/配列は、メソッド呼び出しの後に、その範囲の両端をアドホックに構築されている場合
マリオF

配列として送信された場合、どのように変更できますか?私はそれを配列にキャストしてから、参照を別のものに変更することができると思いますが、それは何が良いでしょうか?(私はおそらく何かが足りない...)
Svish

2
2つの異なるメソッドに渡す1つの列挙型を作成することを決定したとします。次に、最初のメソッドがそれを配列にキャストし、内容を変更しました。次に、変更を認識せずに、それを別のメソッドに引数として渡します。私はそれが可能性があると言っているわけではありません、それが必要でないときに何かが可変であることはnaturla不変性よりもきれいではないということだけです。
Jon Skeet、

3
これは受け入れられた回答であり、おそらく読まれる可能性が高いので、ここで私の懸念を追加します。私はこの方法を試しましたが、以前にコンパイルされたmyDict.Keys.AsEnumerable()where myDictがtypeの呼び出しを壊しましたDictionary<CheckBox, Tuple<int, int>>。拡張メソッドの名前をに変更した後SingleItemAsEnumerable、すべてが機能し始めました。IEnumerable <Dictionary <CheckBox、System.Tuple <int、int >>。KeyCollection> 'を' IEnumerable <CheckBox> 'に変換できません。明示的な変換が存在します(キャストを逃していますか?)私はしばらくハックに耐えることができますが、他のパワーハッカーはより良い方法を見つけることができますか?
Hamish Grubijan、2011年

3
@HamishGrubijan:最初dictionary.Valuesからやりたかったようですね。基本的に何が起こっているのかは不明ですが、それについて新しい質問を始めることをお勧めします。
Jon Skeet

133

まあ、もしメソッドが期待しているIEnumerableなら、たとえそれが一つの要素しか含んでいなくても、リストである何かを渡さなければなりません。

通過

new[] { item }

議論は十分だと思うので


32
Tが推測されるので、Tをドロップすることもできると思います(ここで非常に間違っている場合を除きます:p)
Svish

2
C#2.0では推測されていないと思います。
Groo

4
C#3コンパイラはTを推測することができ、そしてコードは、.NET 2と完全に互換性があります「言語はC#、フレームワークのバージョン2」
sisve

ベストアンサーは一番下にありますか?
Falco Alexander

2
それは「標準」私見でなければなりませんので、我々は今2020年だ- @Svish私はあることを示唆して行うための答えを編集
OschtärEi

120

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

// using System.Linq

Enumerable.Repeat(item, 1);

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


26
この解決策により、コードが読みにくくなると思います。:(単一の要素で繰り返しを行うのは、直感に反することだと思いませんか?
Drahakar

6
私に問題がなければ、繰り返します。さらに別の拡張メソッドを追加することを心配する必要がなく、使用する場所に名前空間が含まれていることを確認する必要のない、はるかに単純なソリューション。
si618 2013

13
ええ、私は実際にはnew[]{ item }自分自身を使用していますが、それは興味深い解決策だと思いました。
luksan 2013

7
このソリューションはお金です。
ProVega 2013年

私見これは受け入れられる答えであるはずです。読みやすく、簡潔で、カスタム拡張メソッドなしでBCLの1行に実装されます。
Ryan

35

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

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

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


おかげで、私はそれを知っています(C#3.0を使用する場合は.Net 2.0で動作します)。このための組み込みメカニズムがあるかどうか疑問に思っていました。
Groo

12
これは本当に素晴らしい代替手段です。私はToEnumerable混乱を避けるために名前を変更することを提案したいと思いますSystem.Linq.Enumerable.AsEnumerable
Fede

3
補足として、のToEnumerable拡張メソッドはすでに存在するためIObservable<T>、このメソッド名は既存の規則にも干渉します。正直なところ、単純すぎるため、拡張メソッドをまったく使用しないことをお勧めします。
cwharris 2013年

14

このヘルパーメソッドは、1つ以上のアイテムで機能します。

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

1
複数のアイテムをサポートするため、便利なバリエーションです。params配列の作成に依存しているのが好きなので、結果のコードはクリーンに見えます。new T[]{ item1, item2, item3 };複数のアイテムよりも好きか嫌いかを決めていません。
ToolmakerSteve、2015年

スマート!Love it
古田充

10

クライアント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);

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
    }
}

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

AsEnumerableメソッドを使用する効率については100%わかりませんが、それで十分です。

更新:AsEnumerable関数はかなり効率的に見えます!(参考


実際には、まったく必要ありません.AsEnumerable()。配列はYourType[]すでに実装していますIEnumerable<YourType>。しかし、私の質問は、(例では)2番目の方法しか使用できず、.NET 2.0を使用していて、単一の項目を渡したい場合について言及していました。
Groo

2
はい、ありますが、スタックオーバーフローが発生する場合があります;-)
teppicymon

そして、実際にあなたの実際の質問に答えるには(実際に私が自分で答えようとしていたものではありません!)、ええ、マリオの答えに従ってそれを配列に変換する必要があります
teppicymon

2
IEnumerable<T> MakeEnumerable<T>(params T[] items) {return items;}メソッドを定義するだけではどうですか?その後IEnumerable<T>、任意の数の個別のアイテムに対して、を期待するものでそれを使用できます。コードは基本的に、単一の項目を返す特別なクラスを定義するのと同じくらい効率的でなければなりません。
スーパーキャット2012

6

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= Rxと考えてくださいIEnumerable

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


5

これは、30%より速いですyieldEnumerable.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
ストップウォッチを使用して比較しないでください。Benchmark.NET github.com/dotnet/BenchmarkDotNet
NN_

1
ちょうどそれを測定し、new [] { i }オプションはあなたのオプションよりも約3.2倍速いです。また、オプションはでの拡張よりも約1.2倍高速yield returnです。
lorond

これをスピードアップするには、別のC#コンパイラー最適化を使用SingleEnumerator GetEnumerator()します。構造体を返すメソッドを追加します。C#コンパイラーは、インターフェースのメソッドの代わりにこのメソッドを使用し(ダックタイピングでルックアップします)、ボックス化を回避します。Listのように、多くの組み込みコレクションがこのトリックを利用しています。注:これはSingleSequence、例のようにを直接参照している場合にのみ機能します。それをIEnumerable<>変数に格納すると、トリックは機能しません(インターフェースメソッドが使用されます)
enzi

5

IanGこのトピックについて良い投稿をしてEnumerableFrom()おり、名前として示唆しています(HaskellとRxがそれを呼び出すと述べていますReturn)。IIRC F#はそれもReturnと呼んでいます。F#Seqは演算子を呼び出しますsingleton<'T>

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

あなたがそれのパフォーマンスの側面に興味があるなら、ジェームズ・マイケル・ヘアはゼロまたは1つのアイテムの投稿返すので、スキャンする価値があります。


4

これは良くないかもしれませんが、それは一種のクールです:

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

そうではありません。違います。
nawfal 2014年

Ewww。これは、アイテムを列挙可能なものにするための詳細です。
ToolmakerSteve

1
これは、Rube Goldbergマシンを使用して朝食を作るのと同じです。はい、動作しますが、そこに到達するために10フープをジャンプします。このような単純なことは、何百万回も実行されるタイトなループで実行されると、パフォーマンスのボトルネックになる可能性があります。実際には、99%の場合、パフォーマンスの側面は問題になりませんが、個人的には、それは非常に不必要なやり過ぎだと思います。
enzi

4

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

次に、これらは、多かれ少なかれ、正しいと感じるIEnumerableの「First」および「FirstOrDefault」拡張メソッドに類似しています。


2

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


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

上記のコードは、

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

上記のコードスニペットでは、空/ nullチェックはなく、例外を恐れずに1つのオブジェクトのみが返されることが保証されています。さらに、遅延であるため、既存のデータが基準に適合しないことが証明されるまで、クロージャーは実行されません。


この回答には自動的に「低品質」のタグが付けられました。ヘルプで説明されているように(「簡潔さは許容されますが、完全な説明の方が良いです。」)、それを編集してOPに何が間違っているのか、コードが何であるのかを伝えてください。
サンドラロッシ

私が回答している部分を含め、この回答の大部分は後で他の誰かが編集したものだと思います。しかし、2番目のスニペットの目的が、MyData.Where(myCondition)空の場合にデフォルト値を提供することである場合は、DefaultIfEmpty():を使用することで既に可能です(そして簡単です)var existingOrNewObject = MyData.Where(myCondition).DefaultIfEmpty(defaultValue).First();。カスタム値ではなく、var existingOrNewObject = MyData.FirstOrDefault(myCondition);必要に応じてさらに簡略化できdefault(T)ます。
BACON

0

パーティーには少し遅れますが、とにかく自分の道を共有します。私の問題は、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);

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


SingleItemEnumerator<T>クラスは理にかなっているが、クラス「単一アイテム作りIEnumerable自体の」単一責任の原則の違反のように思えます。多分それはそれを渡すことをより実用的にしますが、私はそれでも必要に応じてそれをラップしたいと思います。
Groo、

0

「必ずしも良い解決策ではないが、それでも...解決策」または「愚かな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!");

後者の2つの方法は、.NET Framework 4.7.1および.NET Core 1.0以降で使用できます。

これは1つがあった場合に実行可能なソリューションであり、実際に既存のメソッドを使用しての代わりに、これはより多かれ少なかれ明確であれば、私は未定だが、自分自身の記述の意図ソリューション。これは間違いなく長いコードです(タイプパラメータの推論ができないためです)Enumerable.Repeat<>()Empty<>())、2倍の数の列挙子オブジェクトが作成されます。

この「これらの方法が存在することをご存知でしたか?」答えは、Array.Empty<>()に置き換えることができますEnumerable.Empty<>()が、それが状況を良くすることを主張するのは難しいです...


0

私は最近別の投稿で同じことを尋ねました(単一の値でIEnumerable <T>を必要とするC#メソッドを呼び出す方法はありますか?...ベンチマーク付き)。

私はここに立ち寄って、これらの回答で提示された4つのアプローチについて、新しい投稿で示されている簡単なベンチマーク比較を見てほしいと思いました。

new[] { x }メソッドに引数を書き込むだけが最短かつ最速の解決策のようです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.