关于c#:将单个项目作为IEnumerable传递

Passing a single item as IEnumerable<T>

有没有一种常见的方法将类型为T的单个项传递给需要IEnumerable参数的方法?语言是C,框架版本2.0。

目前我使用的是一个助手方法(它是.NET 2.0,所以我有很多类似于Linq的强制转换/投影助手方法),但这看起来很愚蠢:

1
2
3
4
5
6
7
8
public static class IEnumerableExt
{
    // usage: IEnumerableExt.FromSingleItem(someObject);
    public static IEnumerable<T> FromSingleItem<T>(T item)
    {
        yield return item;
    }
}

另一种方法当然是创建和填充一个List或一个Array,并传递它而不是IEnumerable

[编辑]作为扩展方法,它可能命名为:

1
2
3
4
5
6
7
8
public static class IEnumerableExt
{
    // usage: someObject.SingleItemAsEnumerable();
    public static IEnumerable<T> SingleItemAsEnumerable<T>(this T item)
    {
        yield return item;
    }
}

我是不是错过了什么?

[edit2]我们发现someObject.Yield()是这个扩展方法的最佳名称(正如@peter在下面的注释中所建议的那样),主要是为了简洁起见,因此如果有人想要获取它,它与XML注释一起出现在这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
    }
}


好吧,如果该方法需要一个IEnumerable,那么您必须传递一个列表,即使它只包含一个元素。

经过

1
new T[] { item }

我认为这场争论应该足够了


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

1
2
3
// using System.Linq

Enumerable.Repeat(item, 1);

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


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


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

1
2
3
4
5
6
7
static class IEnumerableExtensions
{
    public static IEnumerable<T> ToEnumerable<T>(this T item)
    {
        yield return item;
    }
}

客户端代码是item.ToEnumerable()


此助手方法适用于项或多个项。

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


我有点惊讶,没有人建议使用带有t类型参数的方法的新重载来简化客户端API。

1
2
3
4
5
6
7
8
9
public void DoSomething<T>(IEnumerable<T> list)
{
    // Do Something
}

public void DoSomething<T>(T item)
{
    DoSomething(new T[] { item });
}

现在,您的客户机代码可以这样做:

1
2
MyItem item = new MyItem();
Obj.DoSomething(item);

或列表:

1
2
List<MyItem> itemList = new List<MyItem>();
Obj.DoSomething(itemList);


正如我刚刚发现的,并且看到用户lukeh也建议的那样,一个很好的简单方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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
    }
}

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

不过,我不能百分之百地肯定使用无数法的效率,但它确实起到了治疗作用。

更新:AsEnumeratable函数看起来很有效!(参考)


虽然一种方法的杀伤力太大,但我相信有些人可能会发现交互式扩展很有用。

微软的交互式扩展(ix)包括以下方法。

1
2
3
4
public static IEnumerable<TResult> Return<TResult>(TResult value)
{
    yield return value;
}

可以这样使用:

1
var result = EnumerableEx.Return(0);

ix添加了在原始LINQ扩展方法中找不到的新功能,这是创建反应式扩展(RX)的直接结果。

想想,Linq Extension MethodsIxRxIEnumerable

你可以在codeplex上找到rx和ix。


由于此C编译器优化,在其他情况下性能相同,因此在foreach中使用时比yieldEnumerable.Repeat快30%。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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);
    }
}

在本测试中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    // 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}");
    }


或者(如前所述)

1
MyMethodThatExpectsAnIEnumerable(new[] { myObject });

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

作为补充说明,如果您想要一个匿名对象的空列表,最后一个版本也可以很好。

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


我同意@outengine对原始帖子的评论,即"assingleton"是一个更好的名字。看看这个维基百科条目。然后,根据singleton的定义,如果将一个空值作为参数传递,那么"assingleton"应该返回一个具有单个空值的ienumerable,而不是一个空的ienumerable,这将解决if (item == null) yield break;的争论。我认为最好的解决方案是有两个方法:"assingleton"和"assingletorEmpty";其中,如果将空值作为参数传递,"assingleton"将返回单个空值,"assingletorEmpty"将返回空的ienumerable。这样地:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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"扩展方法,这只是感觉正确而已。


这可能不会更好,但有点酷:

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


梁在这个问题上有一个很好的帖子,建议以EnumerableFrom()为名称,并提到讨论指出haskell和rx称之为Returniirc f calls it return too.。f的Seq呼叫操作员singleton<'T>

如果你准备好以"C"为中心,那就把它称为"EDOCX1"(8),[暗示着参与实现它的yield return)。

如果你对它的性能方面感兴趣,詹姆斯迈克尔黑尔也有一个返回零或一个项目张贴太值得扫描。


我想说的最简单的方法是new T[]{item};;没有语法可以做到这一点。我能想到的最接近的等价物是params关键字,但当然这要求您能够访问方法定义,并且只能用于数组。


我参加聚会有点晚了,但无论如何我还是要和你分享我的路。我的问题是我想将itemsource或wpf treeview绑定到单个对象。层次结构如下:

项目>地块>房间

总是只有一个项目,但我仍然希望在树中显示该项目,而不必像某些建议的那样传递一个集合,其中只包含一个对象。因为您只能将IEnumerable对象作为itemsource传递,所以我决定将类设置为IEnumerable:

1
2
3
4
5
6
7
8
9
10
public class ProjectClass : IEnumerable<ProjectClass>
{
    private readonly SingleItemEnumerator<AufmassProjekt> enumerator;

    ...

    public IEnumerator<ProjectClass > GetEnumerator() => this.enumerator;

    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}

并相应地创建我自己的枚举器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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指出的单一责任原则,我创建了一个新的包装类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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();
}

我用这个

1
TreeView.ItemSource = new SingleItemWrapper(itemToWrap);

编辑2我用MoveNext()方法纠正了一个错误。