Передача одного элемента в виде IEnumerable <T>

Есть ли общий способ передать один элемент типа T методу, который ожидает параметр IEnumerable<T>? Язык - это С#, версия 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;
    }
}

Ответ 1

Ваш вспомогательный метод - самый чистый способ сделать это, ИМО. Если вы передадите список или массив, то недобросовестный фрагмент кода может использовать его и изменять содержимое, что приводит к нечетному поведению в некоторых ситуациях. Вы можете использовать коллекцию только для чтения, но это может привести к еще большему обертыванию. Я думаю, что ваше решение так же аккуратно, как и получается.

Ответ 2

Ну, если метод ожидает IEnumerable, вам нужно передать что-то, что есть список, даже если он содержит только один элемент.

передача

new T[] { item }

поскольку аргумент должен быть достаточным, я думаю,

Ответ 3

В С# 3.0 вы можете использовать класс System.Linq.Enumerable:

// using System.Linq

Enumerable.Repeat(item, 1);

Это создаст новый IEnumerable, содержащий только ваш элемент.

Ответ 4

В С# 3 (я знаю, что вы сказали 2) вы можете написать общий метод расширения, который может сделать синтаксис более приемлемым:

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

клиентский код тогда item.ToEnumerable().

Ответ 5

Я удивлен, что никто не предложил новую перегрузку метода с аргументом типа 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

Этот вспомогательный метод работает для элемента или многих.

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

Ответ 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
    }
}

Этот шаблон позволит вам вызывать одну и ту же функциональность множеством способов: один элемент; несколько элементов (разделенных запятыми); массив; список; перечисление и т.д.

Я не уверен на 100% эффективности использования метода AsEnumerable, но он действительно работает.

Обновление: функция AsEnumerable выглядит довольно эффективно! (ссылка)

Ответ 8

Либо (как ранее было сказано)

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

или

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

В качестве дополнительной заметки последняя версия также может быть приятной, если вам нужен пустой список анонимного объекта, например

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

Ответ 9

Это может быть не лучше, но это классно:

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

Ответ 10

Это на 30% быстрее, чем yield или Enumerable.Repeat при использовании в 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}");
    }

Ответ 11

Хотя это слишком сложно для одного метода, я считаю, что некоторые люди могут найти интерактивные расширения полезными.

Интерактивные расширения (Ix) из Microsoft включают следующий метод.

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 и Ix в CodePlex.

Ответ 12

Я согласен с комментариями @EarthEngine к исходному сообщению, что "AsSingleton" - лучшее имя. См. эту запись в википедии. Тогда из определения singleton следует, что если пустое значение передается как аргумент, что "AsSingleton" должен возвращать IEnumerable с единственным значением null вместо пустого 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;
}

Тогда они будут более или менее похожими на методы расширения "First" и "FirstOrDefault" на IEnumerable, которые просто чувствуют себя правильно.

Ответ 13

IanG имеет хороший пост в теме, предлагая EnumerableFrom() как имя и упоминает, что в обсуждении указывается, что Haskell и Rx называют его Return. IIRC F # вызывает его Возврат тоже. F # Seq вызывает оператор singleton<'T>.

Искушение, если вы готовы быть С# -центриком, это называть его Yield [ссылаясь на yield return, участвующих в его реализации].

Если вы интересуетесь первичными аспектами этого, у Джеймса Майкла Харе есть возврат нулевого или одного сообщения, который тоже стоит сканирование.

Ответ 14

Самый простой способ: new T[]{item};; для этого нет синтаксиса. Ближайшим эквивалентом, о котором я могу думать, является ключевое слово params, но, конечно, это требует, чтобы у вас был доступ к определению метода и он может использоваться только с массивами.

Ответ 15

Я немного опаздываю на вечеринку, но я все равно поделюсь. Моя проблема заключалась в том, что я хотел привязать 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;
}

Это, вероятно, не "самое чистое" решение, но это сработало для меня.

РЕДАКТИРОВАТЬ
Чтобы поддержать принцип единой ответственности, как отметил @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().