首页 文章

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

提问于
浏览
323

是否有一种常见的方法将 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> .

[Edit] 作为扩展方法,它可能被命名为:

public static class IEnumerableExt
{
    // usage: someObject.SingleItemAsEnumerable();
    public static IEnumerable<T> SingleItemAsEnumerable<T>(this T item)
    {
        yield return item; 
    }
}

我在这里错过了什么吗?

[Edit2] 我们发现 someObject.Yield() (正如@Peter在下面的评论中所建议的)是这个扩展方法的最佳名称,主要是为了简洁起见,所以如果有人想 grab 它的话,它会与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;
    }
}

15 回答

  • 1

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

  • 7

    我同意@EarthEngine 's comments to the original post, which is that ' AsSingleton'是一个更好的名字 . See this wikipedia entry . 然后从单例的定义得出如果一个空值作为参数传递,'AsSingleton'应该返回一个具有单个空值的IEnumerable而不是一个空的IEnumerable,这将解决 if (item == null) yield break; 争论 . 我认为最好的解决方案是有两种方法:'AsSingleton'和'AsSingletonOrEmpty';其中,如果将null作为参数传递,'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;
    }
    

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

  • 5

    虽然对于一种方法来说太过分了,但我相信有些人可能会发现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 for IEnumerable .

    你可以找到Rx and Ix on CodePlex .

  • 2

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

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

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

  • 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);
    
  • 95

    IanGa good post on the topic,建议 EnumerableFrom() 作为名称,并提到讨论指出Haskell和Rx称之为 Return . IIRC F#也称它为Return . F#的Seq calls the operator singleton<'T> .

    如果你准备成为C#中心的诱惑就是把它叫做 Yield [暗指参与实现它的 yield return ] .

    如果你对它的性能感兴趣,James Michael Hare也有returning zero or one items post,非常值得扫描 .

  • 4

    我参加派对有点晚了但是无论如何我会分享我的方式 . 我的问题是我想将ItemSource或WPF TreeView绑定到单个对象 . 层次结构如下所示:

    项目>剧情>房间

    总是只有一个项目,但我仍然希望在树中显示项目,而不必传递一个只有一个对象的集合,就像一些建议 .
    因为你只能将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;
    }
    

    这可能不是“最干净”的解决方案,但它对我有用 .

    EDIT
    为了支持single responsibility principle,因为@Groo指出我创建了一个新的包装器类:

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

    EDIT 2
    我用 MoveNext() 方法纠正了一个错误 .

  • 82

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

    通过

    new T[] { item }
    

    因为我觉得这个论点应该足够了

  • 9

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

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

    要么(如前所述)

    MyMethodThatExpectsAnIEnumerable(new[] { myObject });
    

    要么

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

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

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

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

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

    由于this C# compiler optimization,在 foreach 中使用时,这比 yieldEnumerable.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);
        }
    }
    

    在这个测试中:

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

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

    // using System.Linq
    
    Enumerable.Repeat(item, 1);
    

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

  • 25

    我说的最简单的方法是 new T[]{item}; ;没有语法可以做到这一点 . 最近的相当于我能想到的是 params 关键字,但当然这需要你有权访问方法定义,并且只能用于数组 .

  • 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
        }
    }
    

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

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

    更新:AsEnumerable函数看起来很有效! (reference

相关问题