传递一个单独的项目作为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;
}
}
120554 次浏览

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

通过

new[] { item }

我认为这个论点应该足够了

我认为最简单的方法是new T[]{item};;没有语法来做这个。我能想到的最接近的等效是params关键字,但当然,这需要你有权访问方法定义,并且只能用于数组。

在我看来,你的帮手方法是最干净的方法。如果传入一个列表或数组,那么不道德的代码段可能会强制转换它并更改内容,从而在某些情况下导致奇怪的行为。您可以使用只读集合,但这可能涉及更多的包装。我认为你的解决办法非常巧妙。

在c# 3(我知道你说的是2)中,你可以编写一个泛型扩展方法,这可能会使语法更容易接受:

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

客户端代码为item.ToEnumerable()

让我有点惊讶的是,没有人建议使用类型为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);

或者(如前所述)

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

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

顺便说一句,如果你想要一个匿名对象的空列表,最后一个版本也可以很好。

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

在c# 3.0中,你可以使用System.Linq.Enumerable类:

// using System.Linq


Enumerable.Repeat(item, 1);

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

正如我刚刚发现的,也看到了用户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函数看起来非常高效!(参考)

这可能没有更好,但它有点酷:

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

尽管对于一种方法来说,这有点过头了,但我相信有些人可能会发现交互式扩展很有用。

微软的交互式扩展(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

此helper方法适用于item或many。

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

我同意@EarthEngine对原文的评论,“AsSingleton”是一个更好的名字。请看这个维基百科词条。然后,根据singleton的定义,如果一个空值作为参数传递,'AsSingleton'应该返回一个具有单个空值的IEnumerable,而不是一个空IEnumerable,这将解决if (item == null) yield break;争论。我认为最好的解决方案是有两个方法:'AsSingleton'和'AsSingletonOrEmpty';其中,在事件中,null被作为参数传递,'AsSingleton'将返回一个空值,'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'扩展方法,这感觉是对的。

由于这个c#编译器优化,这比在foreach中使用的yieldEnumerable.Repeat快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}");
}
我来派对有点晚了,但无论如何我要分享我的方式。 我的问题是,我想将ItemSource或WPF TreeView绑定到单个对象。层次结构如下所示:

>项目地块>房间

总是只有一个项目,但我仍然想在树中显示项目,而不必像一些人建议的那样传递一个只有一个对象的集合 因为你只能传递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;
}

这可能不是“最干净”的解决方案,但对我来说很有效。

< p > 编辑 < br > 为了维护@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);
< p > 编辑2 < br > 我用MoveNext()方法纠正了一个错误
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();

在上面的代码片段中,没有空/空检查,并且保证只返回一个对象,而不担心异常。此外,由于闭包是惰性的,因此直到证明没有现有数据符合条件时才会执行闭包。

在“不一定是解决方案,但仍然…”一个解决方案”或“愚蠢的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<>(),但很难说这会使情况变得更好。

我最近在另一个帖子上问了同样的问题

是否有一种方法调用c#方法需要IEnumerable<T>只有一个值?...与基准测试

我想让人们在这里停下来,看看这些答案中给出的4种方法的简短基准比较。

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

有时候,当我觉得很顽皮的时候,我会这样做:

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

这与减少转变按键是一样的,嘿:

from _ in "_" select 3.14

对于一个实用函数,我发现这是最不冗长的,或者至少比数组更具自文档性,尽管它会让多个值滑动;作为加号,它可以被定义为一个局部函数:

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

以下是我能想到的所有其他方法(可运行在这里):

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


}

我更喜欢

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)