将单个项目作为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; 
    }
}

当然,其他方法是创建并填充a List<T>或an 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;
    }
}

6
我将对扩展方法的主体进行一些修改:if (item == null) yield break; 现在,您不再传递null,也没有利用(的)null对象模式作为IEnumerable。(foreach (var x in xs)处理一个空xs就好了)。顺便说一句,此功能是monad列表的monadic单位IEnumerable<T>,并且鉴于Microsoft的monad love-fest,我很惊讶这样的事情最初不在框架中。
Matt Enright

2
对于扩展方法,您不应为其命名,AsEnumerable因为已经存在具有该名称的内置扩展。(例如,使用T农具时)IEnumerablestring
乔恩·埃里克

22
命名方法Yield怎么样?简洁无比。
菲利普·金

4
在这里命名建议。“ SingleItemAsEnumerable”有点冗长。“ Yield”描述了实现而不是接口-这不好。为了更好地命名,我建议使用“ AsSingleton”,它对应于行为的确切含义。
Earth Engine'3

4
我讨厌left==null这里的支票。它破坏了代码的美感并停止了代码的灵活性-如果某天您需要生成一个可以为null的单例呢?我的意思new T[] { null }是和并不相同new T[] {},有时候您可能需要区分它们。
Earth Engine'3

Answers:


100

IMO是您的助手方法,是最干净的方法。如果传递列表或数组,那么不道德的代码段可能会强制转换它并更改内容,从而在某些情况下导致异常行为。您可以使用只读集合,但这可能涉及更多包装。我认为您的解决方案非常简洁。


好吧,如果列表/数组是临时构建的,则其范围在方法调用之后结束,因此不会引起问题
Mario F

如果以数组形式发送,如何更改?我猜可以将其转换为数组,然后将引用更改为其他内容,但是这样做有什么好处呢?(虽然我可能会丢失一些东西……)
Svish

2
假设您决定创建一个可枚举的方法,以传递给两种不同的方法...然后,第一个将其强制转换为数组并更改其内容。然后,您将其作为参数传递给另一个方法,而不会意识到更改。我并不是说这很可能,只是在不需要时具有可变性比naturla不变性更简洁。
乔恩·斯基特

3
这是一个可以接受的答案,并且很可能会被阅读,因此我将在此添加我的关注。我尝试了这种方法,但是这中断了我先前对myDict.Keys.AsEnumerable()where myDict类型的编译调用Dictionary<CheckBox, Tuple<int, int>>。在将扩展方法重命名为后SingleItemAsEnumerable,一切开始起作用。无法将IEnumerable <Dictionary <CheckBox,System.Tuple <int,int >>。KeyCollection>'转换为'IEnumerable <CheckBox>'。存在显式转换(您是否缺少演员表?)我可以忍受一段时间的黑客攻击,但是其他强力黑客可以找到更好的方法吗?
Hamish Grubijan

3
@HamishGrubijan:听起来您只是dictionary.Values想开始。基本上不清楚发生了什么,但是我建议您开始一个新的问题。
乔恩·斯基特

133

好吧,如果该方法期望IEnumerable,即使它仅包含一个元素,您也必须传递一个列表形式的东西。

通过

new[] { item }

因为我认为论点应该足够


32
我认为您甚至可以删除T,因为可以推断出T(除非我在这里很错:p)
Svish

2
我认为这不是C#2.0中的推断。
Groo

4
“语言是C#,框架版本2” C#3编译器可以推断T,并且该代码将与.NET 2完全兼容
。– sisve

最好的答案是在底部?
Falco Alexander

2
@Svish我建议和编辑的答案做到这一点-我们是在2020年了,所以它应该是“标准”恕我直言
OschtärEi

120

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

// using System.Linq

Enumerable.Repeat(item, 1);

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


26
我认为这种解决方案会使代码更难阅读。:(在单个元素上重复是很违反直觉的吗?
Drahakar 2011年

6
重复一次就可以了。简单得多的解决方案,而不必担心添加另一种扩展方法,并确保在您要使用的任何地方都包含名称空间。
si618

13
是的,我实际上只是在使用new[]{ item }自己,我只是认为这是一个有趣的解决方案。
luksan

7
这个解决方案就是金钱。
ProVega

恕我直言,这应该是公认的答案。它易于阅读,简洁,并且在BCL上只需一行即可实现,而无需使用自定义扩展方法。
瑞安

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

14

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

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

1
有用的变化形式,因为它支持多个项目。我喜欢它依赖于params构建数组,因此生成的代码看起来很干净。还没有决定我喜欢还是不喜欢new T[]{ item1, item2, item3 };多个项目。
制造商史蒂夫(Steve)2015年

聪明 !喜欢它
古田光

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,2009年

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 2010年

2
是的,的确如此,但是您会发现堆栈可能会溢出;-)
teppicymon 2010年

并且要真正回答您的真实问题(不是我真正想为自己回答的问题!),是的,您几乎必须按照Mario的回答将其转换为数组
teppicymon 2010年

2
仅定义IEnumerable<T> MakeEnumerable<T>(params T[] items) {return items;}方法怎么样?然后IEnumerable<T>,对于任意数量的离散项,可以将其与任何预期为的东西一起使用。该代码本质上应与定义一个特殊类以返回单个项目一样高效。
supercat 2012年

6

尽管这对于一种方法来说过于夸张,但我相信有些人可能会发现Interactive Extensions有用。

Microsoft的交互式扩展(Ix)包括以下方法。

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

可以这样利用:

var result = EnumerableEx.Return(0);

Ix添加了原始Linq扩展方法中找不到的新功能,这是创建反应性扩展(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
枚举器和枚举器都将在返回时装箱....
Stephen Drew

2
不要使用秒表进行比较。使用Benchmark.NET github.com/dotnet/BenchmarkDotNet
NN_18年

1
只需对其进行测量,其new [] { i }选择权大约是您选择权的3.2倍。另外,您的选择要比使用扩展大约快1.2倍yield return
朗朗

您可以通过使用另一种C#编译器优化来加速此过程:添加一个SingleEnumerator GetEnumerator()返回结构的方法。C#编译器将使用此方法(它通过鸭子输入查找它)而不是接口方法,从而避免装箱。许多内置集合都利用此技巧,例如List。注意:仅当您直接引用时SingleSequence(例如您的示例中),这才起作用。如果将其存储在IEnumerable<>变量中,则该技巧将不起作用(将使用接口方法)
enzi


4

这可能没有什么好处,但是有点酷:

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

不是。这是不同的。
nawfal 2014年

Ewww。这是很多细节,只是将一个项目变成一个可枚举的项目。
制造商史蒂夫(Steve),2015年

1
这相当于使用Rube Goldberg机做早餐。是的,它可以工作,但是它会跳10个圈才能到达那儿。像这样简单的事情可能是在执行了数百万次的紧密循环中完成时的性能瓶颈。实际上,在99%的情况下,性能方面并不重要,但就我个人而言,我仍然认为这完全是不必要的过大杀伤力。
enzi

4

我同意@EarthEngine对原始帖子的评论,即“ AsSingleton”是一个更好的名称。请参阅此维基百科条目。然后,根据单例的定义得出以下结论:如果将空值作为参数传递,则“ AsSingleton”应返回具有单个空值的IEnumerable而不是将解决if (item == null) yield break;争论的空IEnumerable 。我认为最好的解决方案是使用两种方法:“ 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;
});

上面的代码在使用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)的值,而不是自定义值。
培根

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指出的单一职责原则,我创建了一个新的包装器类:

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

从.NET Framework 4.7.1和.NET Core 1.0开始,后两种方法可用。

这是一个是否是一个可行的解决方案确实在使用现有的方法,而不是写自己,虽然我犹豫不决,如果这是不是更多或更少的明确意图Enumerable.Repeat<>()解决方案。这肯定是更长的代码(部分是由于无法进行类型参数推断Empty<>()),并且创建了两倍的枚举器对象。

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.