Передача одного элемента как 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; 
    }
}

Другой способ, конечно, состоит в том, чтобы создать и заполнить 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 в комментариях ниже) лучшее имя для этого метода расширения, в основном для краткости, поэтому здесь, наряду с комментарием 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;
    }
}
16.10.2009 12:40:17
Я бы внес небольшую модификацию в тело метода расширения: if (item == null) yield break; теперь вы не можете передавать значение null, а также использовать (тривиальный) шаблон нулевого объекта для IEnumerable. ( foreach (var x in xs)справляется с пустым xsпросто отлично). Между прочим, эта функция является монадической единицей для монады списка, которая есть IEnumerable<T>, и, учитывая монаду love-fest в Microsoft, я удивлен, что что-то подобное не входит в рамки в первую очередь.
Matt Enright 11.11.2009 12:49:43
Для метода расширения его не следует называть, AsEnumerableпотому что встроенное расширение с таким именем уже существует . (Когда Tреализует IEnumerable, например,. string)
Jon-Eric 13.08.2012 16:12:27
Как насчет названия метода Yield? Ничто не сравнится с краткостью.
Philip 19.11.2012 08:13:02
Называть предложения здесь. "SingleItemAsEnumerable" немного многословно. «Выход» описывает реализацию, а не интерфейс - что не очень хорошо. Для лучшего названия я предлагаю «AsSingleton», которые соответствуют точному значению поведения.
Earth Engine 24.03.2014 01:40:02
Я ненавижу left==nullчек здесь. Это нарушает красоту кода и делает его более гибким - что если однажды вам понадобится синглтон с чем-то, что может быть нулевым? Я имею в виду, new T[] { null }не то же самое new T[] {}, и когда-нибудь вам, возможно, придется различать их.
Earth Engine 24.03.2014 01:45:02
16 ОТВЕТОВ
РЕШЕНИЕ

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

98
16.10.2009 12:49:05
хорошо, если список / массив построен ad-hoc, его область действия заканчивается после вызова метода, поэтому это не должно вызывать проблем
Mario F 16.10.2009 12:55:12
Если он отправлен в виде массива, как его можно изменить? Я предполагаю, что это может быть приведено к массиву, а затем изменить ссылку на что-то еще, но что хорошего это будет делать? (Я, вероятно, что-то
Svish 16.10.2009 12:55:43
Предположим, вы решили создать один перечислимый объект для передачи двум разным методам ... затем первый преобразовал его в массив и изменил содержимое. Затем вы передаете его в качестве аргумента другому методу, не подозревая об изменениях. Я не говорю, что это вероятно, просто то, что что-то изменчивое, когда это не нужно, менее аккуратно, чем неизменность натурлы.
Jon Skeet 16.10.2009 12:58:13
Это принятый ответ, и, скорее всего, его прочтут, поэтому я добавлю свою озабоченность здесь. Я попробовал этот метод, но это сломало мой ранее скомпилированный вызов, myDict.Keys.AsEnumerable()где myDictбыл тип Dictionary<CheckBox, Tuple<int, int>>. После того как я переименовал метод расширения в SingleItemAsEnumerable, все начало работать. Невозможно преобразовать IEnumerable <Dictionary <CheckBox, System.Tuple <int, int >>. KeyCollection> 'в' IEnumerable <CheckBox> '. Существует явное преобразование (вам не хватает актеров?) Я могу некоторое время жить с хаком, но могут ли другие силовые хакеры найти лучший способ?
Hamish Grubijan 17.10.2011 17:24:52
@HamishGrubijan: Звучит так, будто ты хотел dictionary.Valuesначать с самого начала. По сути, не ясно, что происходит, но я предлагаю вам начать новый вопрос об этом.
Jon Skeet 17.10.2011 17:33:27

Хорошо, если метод ожидает, что IEnumerableвы должны передать что-то, что является списком, даже если он содержит только один элемент.

прохождение

new[] { item }

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

132
2.04.2020 07:52:34
Я думаю, что вы могли бы даже опустить T, так как он будет выведен (если я не очень ошибаюсь здесь: p)
Svish 16.10.2009 12:51:48
Я думаю, что это не выводится в C # 2.0.
Groo 16.10.2009 13:12:33
«Язык - это C #, версия платформы 2» Компилятор C # 3 может вывести T, и код будет полностью совместим с .NET 2.
sisve 16.10.2009 13:25:39
лучший ответ внизу ?!
Falco Alexander 12.08.2019 10:48:32
@Svish Я предложил и отредактировал ответ, чтобы сделать именно это - мы находимся в 2020 году, так что это должен быть «стандартный» imho
OschtärEi 2.04.2020 07:53:29

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

2
16.10.2009 12:48:56

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

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

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

34
19.12.2012 19:58:45
Спасибо, я знаю об этом (работает в .Net 2.0, если я использую C # 3.0), мне просто интересно, есть ли встроенный механизм для этого.
Groo 16.10.2009 13:04:45
Это действительно хорошая альтернатива. Я только хочу предложить переименовать, чтобы ToEnumerableизбежать путаницыSystem.Linq.Enumerable.AsEnumerable
Fede 12.08.2011 17:49:16
Как примечание, уже существует ToEnumerableметод расширения для IObservable<T>, так что это имя метода также мешает существующим соглашениям. Честно говоря, я бы предложил вообще не использовать метод расширения просто потому, что он слишком общий.
cwharris 25.11.2013 23:07:26

Я немного удивлен, что никто не предложил новую перегрузку метода с аргументом типа 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);
10
5.11.2009 16:15:53
Более того, вы могли бы иметь, DoSomething<T>(params T[] items)что означает, что компилятор будет обрабатывать преобразование из одного элемента в массив. (Это также позволит вам передавать несколько отдельных элементов и, опять же, компилятор будет обрабатывать преобразование их в массив для вас.)
LukeH 16.10.2009 13:26:41

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

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

или

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

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

var x = MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(new { a = 0, b = "x" }, 0));
7
16.10.2009 13:22:21
Спасибо, хотя Enumerable.Repeat является новым в .Net 3.5. Похоже, что он ведет себя аналогично вспомогательному методу выше.
Groo 16.10.2009 13:28:57

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

// using System.Linq

Enumerable.Repeat(item, 1);

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

116
22.12.2009 19:36:24
Я думаю, что это решение затруднит чтение кода. :( Повторять один элемент довольно
Drahakar 10.04.2011 22:14:11
Повторите, как только читает хорошо для меня. Гораздо более простое решение без необходимости беспокоиться о добавлении еще одного метода расширения и о том, что пространство имен включено везде, где вы хотите его использовать.
si618 9.04.2013 02:20:27
Да, я просто использую new[]{ item }себя, я просто подумал, что это интересное решение.
luksan 9.04.2013 03:01:53
Это решение - деньги.
ProVega 11.10.2013 04:28:41
ИМХО это должен быть принятый ответ. Это легко читать, кратко, и это реализовано в одной строке на BCL, без специального метода расширения.
Ryan 7.01.2020 06:25:59

Как я только что обнаружил и увидел, что пользователь 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 выглядит довольно эффективно! ( ссылка )

7
23.05.2017 12:18:01
На самом деле, вам не нужно .AsEnumerable()вообще. Массив YourType[]уже реализует IEnumerable<YourType>. Но мой вопрос касался случая, когда доступен только второй метод (в вашем примере), и вы используете .NET 2.0, и вы хотите передать один элемент.
Groo 10.11.2010 12:08:48
Да, это так, но вы обнаружите, что вы можете получить переполнение стека ;-)
teppicymon 10.11.2010 15:52:15
И чтобы на самом деле ответить на ваш реальный вопрос (а не тот, на который я на самом деле рассчитывал ответить сам за себя!), Да, вы в значительной степени должны преобразовать его в массив в соответствии с ответом Марио
teppicymon 10.11.2010 15:53:24
Как насчет определения IEnumerable<T> MakeEnumerable<T>(params T[] items) {return items;}метода? Затем можно использовать это с любым, что ожидалось IEnumerable<T>, для любого количества отдельных предметов. Код должен быть по сути таким же эффективным, как и определение специального класса для возврата одного элемента.
supercat 21.09.2012 18:40:18

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

Enumerable.Range(0, 1).Select(i => item);
5
23.09.2013 13:49:42
Это не. Это другое.
nawfal 10.06.2014 10:29:19
Ewww. Это много деталей, просто чтобы превратить предмет в перечисляемый.
ToolmakerSteve 14.10.2015 19:17:00
Это эквивалентно использованию машины Rube Goldberg для приготовления завтрака. Да, это работает, но он прыгает через 10 обручей, чтобы добраться туда. Простая вещь, подобная этой, может стать узким местом в производительности, когда выполняется в узком цикле, который выполняется миллионы раз. На практике аспект производительности не имеет значения в 99% случаев, но лично я все же считаю, что это излишнее излишнее излишество.
enzi 24.03.2020 16:42:58

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

Заманчиво, если вы готовы стать C # -центричным, это назвать это Yield[намекая на yield returnвовлеченность в реализацию этого].

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

5
24.03.2020 17:37:38

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

Интерактивные расширения (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 .

6
25.11.2013 23:00:19

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

public static IEnumerable<T> ToEnumerable<T>(params T[] items)
{
    return items;
}    
12
26.11.2019 02:19:05
Полезный вариант, поскольку он поддерживает более одного элемента. Мне нравится, что он опирается на paramsсоздание массива, поэтому полученный код выглядит чисто. Не решил, нравится ли мне это больше или меньше, чем new T[]{ item1, item2, item3 };для нескольких предметов.
ToolmakerSteve 14.10.2015 19:12:59

Я согласен с комментариями @ 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;
}

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

4
10.04.2015 16:35:15

Это 30% быстрее , чем yieldили Enumerable.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);
    }
}

в этом тесте:

    // 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}");
    }
5
23.05.2017 11:47:23
Спасибо, имеет смысл создать структуру для этого случая, чтобы уменьшить нагрузку на GC в тесных циклах.
Groo 2.12.2015 10:02:19
И перечислитель, и перечислимый будут упакованы, когда вернутся ...
Stephen Drew 3.04.2017 06:45:16
Не сравнивайте, используя секундомер. Используйте Benchmark.NET github.com/dotnet/BenchmarkDotNet
NN_ 5.11.2018 10:54:21
Только что измерил и new [] { i }вариант примерно в 3,2 раза быстрее, чем ваш вариант. Также ваш вариант примерно в 1,2 раза быстрее, чем расширение с помощью yield return.
lorond 20.05.2019 15:02:40
Вы можете ускорить это, используя другую оптимизацию компилятора C #: добавьте метод, SingleEnumerator GetEnumerator()который возвращает вашу структуру. Компилятор C # будет использовать этот метод (он ищет его с помощью утки) вместо интерфейсных и, таким образом, избегает упаковок. Многие встроенные коллекции используют этот прием, как List . Примечание: это будет работать только тогда, когда у вас есть прямая ссылка SingleSequence, как в вашем примере. Если вы сохраните его в IEnumerable<>переменной, то хитрость не сработает (будет использован метод интерфейса)
enzi 24.03.2020 16:26:08

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

РЕДАКТИРОВАТЬ 2
Я исправил ошибку с MoveNext()методом.

-1
23.08.2018 07:30:57
SingleItemEnumerator<T>Класс имеет смысл, но делает класс «единый элемент IEnumerableсам по себе» , кажется , как нарушение одного принципа ответственности. Возможно, это делает его более практичным, но я все равно предпочитаю оборачивать его по мере необходимости.
Groo 22.08.2018 11:26:31
Enumerable.Range(1,1).Select(_ => {
    //Do some stuff... side effects...
    return item;
});

Приведенный выше код полезен при использовании как

var existingOrNewObject = MyData.Where(myCondition)
       .Concat(Enumerable.Range(1,1).Select(_ => {
           //Create my object...
           return item;
       })).Take(1).First();

В приведенном выше фрагменте кода проверка пустых / пустых значений отсутствует, и гарантируется, что будет возвращен только один объект, не опасаясь исключений. Кроме того, поскольку оно ленивое, закрытие не будет выполняться, пока не будет доказано, что существующие данные не соответствуют критериям.

2
13.09.2019 01:25:37
Этот ответ был автоматически помечен как «низкое качество». Как объясняется в справке («Краткость приемлема, но более полные объяснения лучше.»), Пожалуйста, отредактируйте ее, чтобы сообщить оператору, что он делает неправильно, о чем ваш код.
Sandra Rossi 5.07.2019 06:02:09