是否有一种通用的方法可以将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<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;
}
}
答案 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 optimization在yield
中使用时,这比Enumerable.Repeat
或foreach
快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)
IanG有a 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)