是否有一种通用的方法将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;
    }
}

当前回答

这比yield或Enumerable快30%。由于这种c#编译器优化,在foreach中重复使用,在其他情况下也具有相同的性能。

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

其他回答

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

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

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

客户端代码是item.ToEnumerable()。

或者(如前所述)

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

or

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

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

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

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

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

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