将单个项目传递为IEnumerable <T>
有没有一种常见的方式来传递一个typesT
的单个项目的方法,期望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; } }
其他方式当然是创build和填充一个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()
(作为@Peterbuild议在下面的评论)是这个扩展方法的最好的名字,主要是为了简洁,所以在这里,如果有人想要抓住它,
public static class IEnumerableExt { /// <summary> /// Wraps this object instance into an IEnumerable<T> /// 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<T> consisting of a single item. </returns> public static IEnumerable<T> Yield<T>(this T item) { yield return item; } }
你的帮手方法是最干净的方法,国际海事组织。 如果你传递一个列表或一个数组,那么不择手段的代码就可以投射它并改变内容,导致在某些情况下奇怪的行为。 你可以使用一个只读的集合,但这可能涉及更多的包装。 我认为你的解决scheme是一样整齐。
那么,如果方法期望一个IEnumerable
你必须传递一个列表,即使它只包含一个元素。
通过
new T[] { item }
作为论据应该是足够的,我想
在C#3.0中,您可以使用System.Linq.Enumerable类:
// using System.Linq Enumerable.Repeat(item, 1);
这将创build一个只包含您的项目的新IEnumerable。
在C#3(我知道你说了2),你可以写一个通用的扩展方法,可能会使语法更容易被接受:
static class IEnumerableExtensions { public static IEnumerable<T> ToEnumerable<T>(this T item) { yield return item; } }
客户端代码然后是item.ToEnumerable()
。
我有点惊讶,没有人提出一个Ttypes的参数来简化客户端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);
正如我刚刚find的,并看到用户LukeH也build议,一个很好的简单的方法做到这一点如下:
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 } }
这种模式将允许您以多种方式调用相同的function:单个项目; 多个项目(逗号分隔); 数组; 一个列表; 枚举等
尽pipe使用AsEnumerable方法的效率并不是100%确定的,但它确实有效。
更新:AsEnumerable函数看起来非常高效! ( 参考 )
要么(如前所述)
MyMethodThatExpectsAnIEnumerable(new[] { myObject });
要么
MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(myObject, 1));
作为一个侧面说明,最后一个版本也可以很好,如果你想要一个匿名对象的空列表,例如
var x = MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(new { a = 0, b = "x" }, 0));
这可能不会更好,但它有点酷:
Enumerable.Range(0, 1).Select(i => item);
这个辅助方法适用于物品或许多物品。
public static IEnumerable<T> ToEnumerable<T>(params T[] items) { return items; }
我同意@EarthEngine对原帖的评论,这就是“AsSingleton”是个更好的名字。 看到这个维基百科条目 。 然后从单例的定义来看,如果一个空值作为一个parameter passing,那么'AsSingleton'应该返回一个具有单一空值的IEnumerable,而不是一个可以解决if (item == null) yield break;
的空IEnumerable if (item == null) yield break;
辩论。 我认为最好的解决scheme是有两种方法:'AsSingleton'和'AsSingletonOrEmpty'; 其中,如果将null作为parameter passing,则“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; }
然后,这些或多或less会类似于IEnumerable上的“First”和“FirstOrDefault”扩展方法,这种方法感觉不错。
由于这个C#编译器优化 ,在foreach
使用时, 这比yield
或Enumerable.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); } }
在这个testing中:
// 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}"); }
IanG 在这个话题上有一个很好的post ,build议使用EnumerableFrom()
作为名字,并提到讨论指出Haskell和Rx把它称为Return
。 IIRC F#调用它也返回 。 F#的Seq
调用操作符singleton<'T>
。
如果你准备成为以C#为中心的诱惑,就叫它Yield
(这意味着实现它的yield return
)。
如果你对它的performance感兴趣,詹姆斯·迈克尔·黑尔(James Michael Hare)也有一个归零或者一个物品的post ,这是非常值得一看的。
虽然对于一种方法来说是过度的,但我相信有些人可能会发现互动式扩展有用。
来自Microsoft的交互式扩展(Ix)包含以下方法。
public static IEnumerable<TResult> Return<TResult>(TResult value) { yield return value; }
可以这样使用:
var result = EnumerableEx.Return(0);
Ix增加了在原始Linq扩展方法中找不到的新function,并且是创build反应式扩展(Rx)的直接结果。
对于IEnumerable
,认为Linq Extension Methods
+ Ix
= Rx
。
您可以在CodePlex上findRx和Ix 。
我想说的最简单的方法是new T[]{item};
; 没有语法来做到这一点。 我能想到的最接近的是params
关键字,但是这当然需要你有权访问方法定义,并且只能用于数组。