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

Gro*_*roo 357 .net c# 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; 
    }
}
Run Code Online (Sandbox Code Playgroud)

其他方式当然是创建和填充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; 
    }
}
Run Code Online (Sandbox Code Playgroud)

我在这里错过了什么吗?

[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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*o F 123

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

通过

new T[] { item }
Run Code Online (Sandbox Code Playgroud)

因为论证应该足够我想

  • 我想你甚至可能放弃T,因为它会被推断(除非我在这里非常错:p) (30认同)
  • "语言是C#,框架版本2"C#3编译器可以推断T,并且代码将与.NET 2完全兼容. (4认同)
  • 我认为它不是在C#2.0中推断出来的. (2认同)
  • @Svish 我建议并编辑答案来做到这一点 - 我们现在已经到了 2020 年,所以它应该是“标准”恕我直言 (2认同)

luk*_*san 111

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

// using System.Linq

Enumerable.Repeat(item, 1);
Run Code Online (Sandbox Code Playgroud)

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

  • 我认为这个解决方案会让代码更难阅读.:(重复单个元素是非常反直觉你不觉得? (25认同)
  • 是的,我实际上只是自己使用`new [] {item},我只是认为这是一个有趣的解决方案. (13认同)
  • 这个解决方案就是钱. (6认同)
  • 重复一次对我说好.更简单的解决方案,无需担心添加另一个扩展方法,并确保命名空间包含在您想要使用它的任何位置. (4认同)
  • 恕我直言,这应该是公认的答案。它易于阅读、简洁,并且在 BCL 上用一行代码实现,无需自定义扩展方法。 (2认同)

Jon*_*eet 95

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

  • 这是一个公认的答案,最有可能被阅读,所以我将在此处加以关注.我尝试了这个方法,但这破坏了我以前编译的调用`myDict.Keys.AsEnumerable()`,其中`myDict`类型为`Dictionary <CheckBox,Tuple <int,int >>`.在我将扩展方法重命名为`SingleItemAsEnumerable`之后,一切都开始起作用了.无法转换IEnumerable <Dictionary <CheckBox,System.Tuple <int,int >> .KeyCollection>'to'IEnumerable <CheckBox>'.存在显式转换(你是否错过了演员?)我可以忍受一段时间的黑客攻击,但是其他黑客能否找到更好的方法? (3认同)
  • @HamishGrubijan:听起来你只想要`dictionary.Values`.基本上不清楚发生了什么,但我建议你开始一个关于它的新问题. (3认同)
  • 假设您决定创建一个枚举以传递给两个不同的方法...然后第一个将其转换为数组并更改内容.然后将其作为参数传递给另一个方法,而不知道更改.我不是说它很可能,只是当它不需要时有一些可变的东西比naturla不变性更不整齐. (2认同)

Ðаn*_*Ðаn 31

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

static class IEnumerableExtensions
{
    public static IEnumerable<T> ToEnumerable<T>(this T item)
    {
        yield return item;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后是客户端代码item.ToEnumerable().

  • 这是一个非常好的选择.我只想建议重命名为`ToEnumerable`以避免弄乱`System.Linq.Enumerable.AsEnumerable` (12认同)
  • 作为旁注,已存在`IObservable <T>`的`ToEnumerable`扩展方法,因此该方法名称也会干扰现有约定.老实说,我建议根本不使用扩展方法,因为它太*通用了. (3认同)

dur*_*aid 11

这个辅助方法适用于项目或许多项目.

public static IEnumerable<T> ToEnumerable<T>(params T[] items)
{
    return items;
}    
Run Code Online (Sandbox Code Playgroud)

  • 这实际上在内存中创建了一个数组:https://sharplab.io/#gist:cf122bcb10a521c314b865dc5c7a6581。**如果你不想分配内存就不要使用它。** (3认同)

Jos*_*ner 9

我很惊讶没有人建议使用类型为T的参数重新设置方法,以简化客户端API.

public void DoSomething<T>(IEnumerable<T> list)
{
    // Do Something
}

public void DoSomething<T>(T item)
{
    DoSomething(new T[] { item });
}
Run Code Online (Sandbox Code Playgroud)

现在您的客户端代码可以执行此操作:

MyItem item = new MyItem();
Obj.DoSomething(item);
Run Code Online (Sandbox Code Playgroud)

或列表:

List<MyItem> itemList = new List<MyItem>();
Obj.DoSomething(itemList);
Run Code Online (Sandbox Code Playgroud)

  • 更好的是,你可以使用`DoSomething <T>(params T [] items)`这意味着编译器将处理从单个项到数组的转换.(这也允许您传递多个单独的项目,同样,编译器会处理它们为您转换为数组.) (19认同)

Str*_*iax 8

对于那些想知道性能的人,虽然@mattica在上面提到的类似问题中提供了一些基准测试信息,但是我的基准测试提供了不同的结果。

在 .NET 7 中,yield return value速度比 9% 快new T[] { value },并分配 75% 的内存量。在大多数情况下,这已经是超高性能的并且可以满足您的需要。

我很好奇自定义单个集合实现是否会更快或更轻量。事实证明,因为yield return被实现为IEnumerator<T>IEnumerable<T>,所以在分配方面击败它的唯一方法就是在我的实现中也这样做。

如果您要转到IEnumerable<>外部图书馆,我强烈建议您要这样做,除非您非常熟悉您正在构建的内容。话虽这么说,我做了一个非常简单(不重用安全)的实现,它能够比yield方法快5ns,并且只分配了数组的一半。

因为所有测试都通过了IEnumerable<T>,所以值类型的性能通常比引用类型差。我的最佳实现实际上是最简单的 - 您可以查看我链接到的要点SingleCollection中的类。(这比 快 2 纳秒,但分配了数组的 88%,而为 分配了 75% 。)yield returnyield return

TL:博士;如果您关心速度,请使用yield return item. 如果您确实关心速度,请使用SingleCollection.


tep*_*mon 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
    }
}
Run Code Online (Sandbox Code Playgroud)

此模式允许您以多种方式调用相同的功能:单个项目; 多个项目(以逗号分隔); 数组; 一个列表; 枚举等

我不是百分之百确定使用AsEnumerable方法的效率,但它确实有效.

更新:AsEnumerable函数看起来非常有效!(参考)

  • 实际上,您根本不需要`.AsEnumerable()`。数组`YourType[]` 已经实现了`IEnumerable&lt;YourType&gt;`。但我的问题是指只有第二种方法(在您的示例中)可用的情况,并且您使用的是 .NET 2.0,并且您想传递单个项目。 (2认同)
  • 是的,确实如此,但你会发现你可能会出现堆栈溢出;-) (2认同)
  • 如何定义一个`IEnumerable <T> MakeEnumerable <T>(params T [] items){return items;}`方法?然后可以对任何期望"IEnumerable <T>"的任何东西使用它,用于任意数量的离散项.代码应该基本上与定义返回单个项的特殊类一样有效. (2认同)

Jas*_*oyd 7

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

然后,这些或多或少类似于 IEnumerable 上的“First”和“FirstOrDefault”扩展方法,感觉正确。


cwh*_*ris 6

虽然对于一种方法来说太过分了,但我相信有些人可能会发现Interactive Extensions非常有用.

Microsoft的Interactive Extensions(Ix)包括以下方法.

public static IEnumerable<TResult> Return<TResult>(TResult value)
{
    yield return value;
}
Run Code Online (Sandbox Code Playgroud)

哪个可以这样使用:

var result = EnumerableEx.Return(0);
Run Code Online (Sandbox Code Playgroud)

Ix添加了原始Linq扩展方法中没有的新功能,并且是创建Reactive Extensions(Rx)的直接结果.

想想,Linq Extension Methods+ Ix= Rxfor IEnumerable.

您可以在CodePlex上找到Rx和Ix.


eri*_*len 5

要么(如前所述)

MyMethodThatExpectsAnIEnumerable(new[] { myObject });
Run Code Online (Sandbox Code Playgroud)

要么

MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(myObject, 1));
Run Code Online (Sandbox Code Playgroud)

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

var x = MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(new { a = 0, b = "x" }, 0));
Run Code Online (Sandbox Code Playgroud)


Rub*_*ink 5

IanG一篇关于该主题的好帖子,建议EnumerableFrom()使用该名称(并提到 Haskell 和 Rx 称其为Return)。IIRC F# 也将其称为 Return。F#Seq调用 operatorsingleton<'T>

如果您准备好以 C# 为中心,那么可以称之为Yield[暗指yield return实现它的过程]。

如果您对它的性能方面感兴趣,James Michael Hare 也有返回零或一个项目的帖子,非常值得一看。


V.B*_*.B. 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在这个测试中:

    // 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}");
    }
Run Code Online (Sandbox Code Playgroud)

  • 刚刚测量了它,`new [] { i }` 选项大约比你的选项快 3.2 倍。此外,您的选择比使用“收益回报”进行扩展快约 1.2 倍。 (4认同)
  • 返回时,枚举数和可枚举数都会被装箱...... (3认同)
  • 不要使用秒表进行比较。使用Benchmark.NET https://github.com/dotnet/BenchmarkDotNet (2认同)

mat*_*ica 5

我最近在另一篇文章中问了同样的事情

有没有办法调用需要 IEnumerable<T> 且具有单个值的 C# 方法?...通过基准测试

我希望人们在这里停下来看看最新帖子中显示的针对这些答案中提出的 4 种方法的简短基准比较。

看来,简单地写入new[] { x }方法的参数是最短、最快的解决方案。