将单个项目作为IEnumerable <t> </t>传递

时间:2009-10-16 12:40:17

标签: c# .net generics ienumerable

是否有一种通用的方法可以将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;
    }
}

20 个答案:

答案 0 :(得分:112)

好吧,如果方法需要IEnumerable,你必须传递一个列表,即使它只包含一个元素。

传递

new T[] { item }

因为论证应该足够我认为

答案 1 :(得分:107)

在C#3.0中,您可以使用System.Linq.Enumerable类:

// using System.Linq

Enumerable.Repeat(item, 1);

这将创建一个仅包含您的项目的新IEnumerable。

答案 2 :(得分:86)

你的助手方法是最干净的方法,IMO。如果你传入一个列表或一个数组,那么一段不道德的代码可能会抛出它并改变内容,在某些情况下会导致奇怪的行为。您可以使用只读集合,但这可能涉及更多包装。我认为你的解决方案尽可能整洁。

答案 3 :(得分:26)

在C#3中(我知道你说过2),你可以编写一个通用的扩展方法,它可以使语法更容易被接受:

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

客户端代码为item.ToEnumerable()

答案 4 :(得分:11)

此辅助方法适用于项目或许多项目。

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

答案 5 :(得分:9)

我很惊讶没有人建议使用类型为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);

答案 6 :(得分: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函数效率非常高! (reference

答案 7 :(得分:6)

虽然这对一种方法来说太过分了,但我相信有些人可能会发现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

您可以找到Rx and Ix on CodePlex

答案 8 :(得分:5)

要么(如前所述)

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

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

作为旁注,如果你想要一个匿名对象的空列表,最后一个版本也可以很好,例如。

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

答案 9 :(得分:5)

由于this C# compiler optimizationyield中使用时,这比Enumerable.Repeatforeach快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}");
    }

答案 10 :(得分:4)

这可能不是更好,但它很酷:

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

答案 11 :(得分:4)

我同意@ EarthEngine对原帖的评论,即AsSingleton&#39;是一个更好的名字。 See this wikipedia entry。然后从单身的定义得出,如果将一个空值作为参数传递,那么AsSingleton&#39;应该返回一个IEnumerable,其中包含一个空值而不是一个空的IEnumerable,这将解决if (item == null) yield break;辩论。我认为最好的解决方案是有两种方法:&#39; AsSingleton&#39;和&#39; AsSingletonOrEmpty&#39 ;;其中,如果将null作为参数传递,则AsSingleton&#39;将返回一个空值和&#39; AsSingletonOrEmpty&#39;将返回一个空的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;
}

然后,这些或多或少类似于&#39; First&#39;和&#39; FirstOrDefault&#39;对IEnumerable的扩展方法感觉恰到好处。

答案 12 :(得分:2)

IanGa good post on the topic,建议EnumerableFrom()作为名称,并提到讨论指出Haskell和Rx称之为Return IIRC F#称之为返回。 F#的Seq calls the operator singleton<'T>

如果你准备成为C#中心的诱惑就是称之为Yield [暗示参与实现它的yield return。)

如果您对它的性能感兴趣,James Michael Hare也有returning zero or one items post,这非常值得扫描。

答案 13 :(得分:1)

我说的最简单的方法是new T[]{item};;没有语法可以做到这一点。我能想到的最接近的等价物是params关键字,但当然要求您可以访问方法定义,并且只能用于数组。

答案 14 :(得分:1)

有时我会这样做,当我感到顽皮时:

"_".Select(_ => 3.14)  // or whatever; any type is fine

这与减少 shift 按键次数是一样的,呵呵:

from _ in "_" select 3.14

对于一个实用程序函数,我发现它是最不冗长的,或者至少比数组更能自我记录,尽管它会让多个值滑动;作为一个加号,它可以定义为本地函数:

static IEnumerable<T> Enumerate (params T[] v) => v;
// usage:
IEnumerable<double> example = Enumerate(1.234);

以下是我能想到的所有其他方法 (runnable here):

using System;
using System.Collections.Generic;
using System.Linq;

public class Program {
    
    public static IEnumerable<T> ToEnumerable1 <T> (T v) {
        yield return v;
    }
    
    public static T[] ToEnumerable2 <T> (params T[] vs) => vs;
    
    public static void Main () {
        static IEnumerable<T> ToEnumerable3 <T> (params T[] v) => v;
        p( new string[] { "three" } );
        p( new List<string> { "three" } );
        p( ToEnumerable1("three") ); // our utility function (yield return)
        p( ToEnumerable2("three") ); // our utility function (params)
        p( ToEnumerable3("three") ); // our local utility function (params)
        p( Enumerable.Empty<string>().Append("three") );
        p( Enumerable.Empty<string>().DefaultIfEmpty("three") );
        p( Enumerable.Empty<string>().Prepend("three") );
        p( Enumerable.Range(3, 1) ); // only for int
        p( Enumerable.Range(0, 1).Select(_ => "three") );
        p( Enumerable.Repeat("three", 1) );
        p( "_".Select(_ => "three") ); // doesn't have to be "_"; just any one character
        p( "_".Select(_ => 3.3333) );
        p( from _ in "_" select 3.0f );
        p( "a" ); // only for char
        // these weren't available for me to test (might not even be valid):
        //   new Microsoft.Extensions.Primitives.StringValues("three")
        
    }

    static void p <T> (IEnumerable<T> e) =>
        Console.WriteLine(string.Join(' ', e.Select((v, k) => $"[{k}]={v,-8}:{v.GetType()}").DefaultIfEmpty("<empty>")));

}

答案 15 :(得分:1)

要提交到“不一定是解决方案,但仍然是……解决方案”或“愚蠢的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开始,后两种方法可用。

如果真的打算使用现有方法而不是编写自己的方法,那么这是一个可行的解决方案,尽管我不确定是否比the Enumerable.Repeat<>() solution更加清晰。这肯定是更长的代码(部分是由于Empty<>()无法进行类型参数推断),并且创建了两倍的枚举器对象。

将其圈定为“您知道这些方法存在吗?”答案是,Array.Empty<>()可以代替Enumerable.Empty<>(),但是很难说情况会变得更好。

答案 16 :(得分:0)

Enumerable.Range(1,1).Select(_, item); 

答案 17 :(得分:0)

我最近在另一则帖子(Is there a way to call a C# method requiring an IEnumerable<T> with a single value? ...with benchmarking)上问了同样的事情。

我希望人们在这里停留,看看在较新的文章中对这些答案中提出的4种方法进行了简要的基准比较。

似乎简单地在方法的参数中写入new[] { x }是最短,最快的解决方案。

答案 18 :(得分:0)

我参加聚会有点晚了,但是我还是会分享我的方式。 我的问题是我想将ItemSource或WPF TreeView绑定到单个对象。层次结构如下所示:

项目>地块>房间

总是只有一个项目,但是我仍然想在树中显示该项目,而不必像建议的那样传递仅包含一个对象的Collection。
由于您只能将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所指出的,为了维护single responsibility principle,我创建了一个新的包装器类:

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()方法纠正了一个错误。

答案 19 :(得分:0)

我更喜欢

public static IEnumerable<T> Collect<T>(this T item, params T[] otherItems)
{
    yield return item;
    foreach (var otherItem in otherItems)
    {
        yield return otherItem;
    }
}

这让您可以在需要单例时调用 item.Collect(),但也可以在需要时调用 item.Collect(item2, item3)