Передача одного элемента как IEnumerable ‹T›

Есть ли общий способ передать один элемент типа 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;
    }
}

person Groo    schedule 16.10.2009    source источник
comment
Я бы немного изменил тело метода расширения: if (item == null) yield break; Теперь вы не можете передавать значение null, а также использовать (тривиальный) шаблон нулевого объекта для IEnumerable. (foreach (var x in xs) отлично справляется с пустым xs). Между прочим, эта функция является монадической единицей для монады списка, которая равна IEnumerable<T>, и, учитывая монадный праздник любви в Microsoft, я удивлен, что что-то подобное вообще отсутствует во фреймворке.   -  person Matt Enright    schedule 11.11.2009
comment
Спасибо за ваш комментарий. Однако у меня есть статическое свойство IEnumerableExt.Empty, которое содержит только оператор yield break; внутри, просто чтобы избежать передачи null в качестве параметра моему методу одного элемента. Это также оставляет (маловероятный) вариант возврата одного нулевого значения из IEnumerable<T>, если это необходимо.   -  person Groo    schedule 12.11.2009
comment
В почти дублированном (более позднем) сообщении Я добавил ответ, который, как мне кажется, очень полезно использует ключевое слово params таким образом, который затрагивается некоторыми ответами и комментариями здесь и очень похож на ваш второй метод. Это определенно самая короткая функция, которую я когда-либо писал!   -  person Gert Arnold    schedule 08.10.2011
comment
Для метода расширения вы не должны называть его AsEnumerable, потому что встроенное расширение с таким именем уже существует. (Когда T реализует IEnumerable, например, string.)   -  person Jon-Eric    schedule 13.08.2012
comment
@MattEnright, if (item == null) yield break может использоваться только в том случае, если T является типом класса. Вам придется закодировать его другим и менее эффективным способом. Что-то вроде if (T == default (T) && typeof (T) .IsClass) yield break;   -  person Thanasis Ioannidis    schedule 04.11.2012
comment
@Saysmaster Тот факт, что item == null никогда не возвращает false для типа значения, не означает, что вы не можете его использовать - это совершенно законный оператор с предполагаемой семантикой.   -  person Matt Enright    schedule 05.11.2012
comment
@MattEnright Вы правы. По какой-то странной причине у меня создалось впечатление, что компилятор выдаст ошибку, когда вы попытаетесь сравнить объект, не являющийся классом, с null! Просто протестировал его, и, похоже, он успешно компилируется.   -  person Thanasis Ioannidis    schedule 06.11.2012
comment
Как насчет наименования метода Yield? Нет ничего лучше краткости.   -  person Philip Guin    schedule 19.11.2012
comment
@ Филипп: отличная идея, спасибо.   -  person Groo    schedule 19.11.2012
comment
@Saysmaster компилируется успешно, но нарушает идиоматические предположения C #.   -  person Den    schedule 26.09.2013
comment
@MattEnright - это совершенно законное утверждение с предполагаемой семантикой - одиночные запросы также допустимы, но их лучше избегать. Идиоматический C # подразумевает, что левая часть в left.right () не должна быть нулевой.   -  person Den    schedule 26.09.2013
comment
@Den никаких аргументов :) Но я хотел сказать, что, во-первых, left == null, где left - это тип значения (так что условие тривиально ложно), что ничего не вредит и имеет смысл, когда тип неизвестен, а во-вторых, я думаю Хороший принцип - использовать нулевые объекты (например, пустую последовательность) вместо буквального нуля во имя надежности, когда что-то подобное существует.   -  person Matt Enright    schedule 27.09.2013
comment
Предложения по именованию здесь. SingleItemAsEnumerable немного подробен. Yield описывает реализацию, а не интерфейс, что нехорошо. Для лучшего названия я предлагаю AsSingleton, которые соответствуют точному смыслу поведения.   -  person Earth Engine    schedule 24.03.2014
comment
Я ненавижу здесь left==null чек. Это нарушает красоту кода и препятствует тому, чтобы код был более гибким - что, если однажды вам понадобится сгенерировать синглтон с чем-то, что может быть нулевым? Я имею в виду, что new T[] { null } - это не то же самое, что new T[] {}, и когда-нибудь вам может потребоваться различить их.   -  person Earth Engine    schedule 24.03.2014
comment
Я согласен с обоими пунктами, высказанными @EarthEngine.   -  person Jason Boyd    schedule 10.04.2015
comment
Почему мы думаем, что Yield - хорошее имя? Что дает? Назовите это от того, что не так, как это работает. Реализации меняются со временем, давая те же результаты.   -  person Alan Macdonald    schedule 31.07.2015
comment
Как насчет наименования метода Once?   -  person Yurii N.    schedule 08.02.2018
comment
if (item == null) yield break; - это крайне плохая идея. Семантика метода состоит в том, чтобы дать перечисление одного элемента, и этот один элемент вполне может иметь значение NULL. То, что он дает 0 элементов, когда элемент равен нулю, является нарушением семантики и ожидает дюжины ошибок. вам не разрешено передавать null - гм, нет, вы не можете передавать null, а это плохо. , а также использование преимущества (тривиального) шаблона нулевого объекта - это не имеет ничего общего с шаблоном нулевого объекта, который представляет собой использование ненулевого объекта для представления нулевого значения.   -  person Jim Balter    schedule 29.11.2018
comment
Почему мы думаем, что Yield - хорошее имя? - потому что, очевидно, так оно и есть. Какая прибыль? - единственный предоставляемый товар. Назовите это от того, что это не так, как это работает - Yield ничего не говорит о том, как это работает, только о том, что он делает. Вам не нужно реализовывать его с помощью yield return ... вы можете написать его, используя класс, реализующий IEnumerable<T>. Тот факт, что Yield и yield - одно и то же слово, потому что они имеют одинаковую семантику; это не имеет ничего общего с реализацией.   -  person Jim Balter    schedule 29.11.2018
comment
Как насчет наименования метода Once - это плохое название; концепция здесь один, а не один раз.   -  person Jim Balter    schedule 29.11.2018
comment
Yield описывает реализацию, а не интерфейс - нет, это не так. Для лучшего названия я предлагаю AsSingleton - это не синглтон, поскольку этот термин используется в информатике: en.wikipedia.org/wiki/Singleton_pattern   -  person Jim Balter    schedule 29.11.2018
comment
Просто потому, что item == null никогда не возвращает false для типа значения - он возвращает false для типов значений, допускающих значение NULL, например, int?, то есть Nullable<int>. Это одна из многих недоразумений и ошибок в комментариях здесь.   -  person Jim Balter    schedule 29.11.2018
comment
Я пытался сделать то же самое, и new T[] { item } у меня сработало. Я думаю, что case T будет именем моего класса, откуда я его вызываю, а item - экземпляром этого класса.   -  person Atta H.    schedule 29.04.2019


Ответы (20)


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

person Jon Skeet    schedule 16.10.2009
comment
хорошо, если список / массив создается ad-hoc, его область действия заканчивается после вызова метода, поэтому это не должно вызывать проблем - person Mario F; 16.10.2009
comment
Если отправлено в виде массива, как его можно изменить? Я предполагаю, что его можно преобразовать в массив, а затем изменить ссылку на что-то еще, но что хорошего в этом? (Я, наверное, что-то упускаю ...) - person Svish; 16.10.2009
comment
Предположим, вы решили создать один перечислимый объект для передачи двум различным методам ... затем первый преобразовал его в массив и изменил содержимое. Затем вы передаете его в качестве аргумента другому методу, не подозревая об изменении. Я не говорю, что это вероятно, просто иметь что-то изменяемое, когда это не нужно, менее аккуратно, чем неизменность naturla. - person Jon Skeet; 16.10.2009
comment
Это принятый ответ, который, скорее всего, будет прочитан, поэтому я добавлю здесь свое беспокойство. Я попробовал этот метод, но это нарушило мой предыдущий вызов компиляции myDict.Keys.AsEnumerable(), где myDict был типа Dictionary<CheckBox, Tuple<int, int>>. После того, как я переименовал метод расширения в SingleItemAsEnumerable, все заработало. Невозможно преобразовать IEnumerable ‹Dictionary‹ CheckBox, System.Tuple ‹int, int ››. KeyCollection› в IEnumerable ‹CheckBox›. Явное преобразование существует (вам не хватает приведений?) Я могу какое-то время жить с хаком, но могут ли другие сильные хакеры найти лучший способ? - person Hamish Grubijan; 17.10.2011
comment
@HamishGrubijan: Похоже, вы хотели начать с dictionary.Values. В основном непонятно, что происходит, но я предлагаю вам задать новый вопрос по этому поводу. - person Jon Skeet; 17.10.2011
comment
@JonSkeet Я ожидал, что что-то подобное станет частью MoreLINQ. Есть там что-нибудь подобное? Не удалось найти. - person nawfal; 13.06.2020
comment
@nawfal: Боюсь, я давно не работал над MoreLINQ. Я предлагаю вам взглянуть на исходный код и сообщить о проблеме, если вы не можете ее найти. - person Jon Skeet; 13.06.2020

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

прохождение

new[] { item }

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

person Mario F    schedule 16.10.2009
comment
Я думаю, вы могли бы даже отказаться от T, так как это будет логически выведено (если я не ошибаюсь здесь: p) - person Svish; 16.10.2009
comment
Я думаю, что это не подразумевается в C # 2.0. - person Groo; 16.10.2009
comment
Язык - C #, версия фреймворка 2. Компилятор C # 3 может сделать вывод о T, и код будет полностью совместим с .NET 2. - person sisve; 16.10.2009
comment
лучший ответ внизу ?! - person Falco Alexander; 12.08.2019
comment
@Svish Я предложил и отредактировал ответ, чтобы сделать именно это - сейчас мы находимся в 2020 году, поэтому он должен быть стандартным, imho - person OschtärEi; 02.04.2020

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

// using System.Linq

Enumerable.Repeat(item, 1);

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

person luksan    schedule 22.12.2009
comment
Я думаю, что это решение затруднит чтение кода. :( Повторение одного элемента - это довольно нелогично, не так ли? - person Drahakar; 11.04.2011
comment
«Повторить один раз» для меня означает «хорошо». Намного более простое решение, позволяющее не беспокоиться о добавлении еще одного метода расширения и обеспечивать включение пространства имен везде, где вы хотите его использовать. - person si618; 09.04.2013
comment
Да, я сам просто использую new[]{ item }, я просто подумал, что это интересное решение. - person luksan; 09.04.2013
comment
Это решение - деньги. - person ProVega; 11.10.2013
comment
ИМХО, это должен быть принятый ответ. Его легко читать, он краток и реализован в одной строке BCL без специального метода расширения. - person Ryan; 07.01.2020

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

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

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

person Ðаn    schedule 16.10.2009
comment
Спасибо, я знаю об этом (работает в .Net 2.0, если я использую C # 3.0), мне просто было интересно, есть ли для этого встроенный механизм. - person Groo; 16.10.2009
comment
Это действительно хорошая альтернатива. Я только предлагаю переименовать его в ToEnumerable, чтобы не испортить System.Linq.Enumerable.AsEnumerable - person Fede; 12.08.2011
comment
Кстати, уже существует ToEnumerable метод расширения для IObservable<T>, поэтому это имя метода также противоречит существующим соглашениям. Честно говоря, я бы посоветовал вообще не использовать метод расширения просто потому, что он слишком общий. - person cwharris; 26.11.2013

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

public static IEnumerable<T> ToEnumerable<T>(params T[] items)
{
    return items;
}    
person duraid    schedule 01.05.2014
comment
Полезный вариант, так как поддерживает более одного элемента. Мне нравится, что он использует params для построения массива, поэтому результирующий код выглядит чистым. Еще не решил, нравится мне больше или меньше, чем new T[]{ item1, item2, item3 }; для нескольких предметов. - person ToolmakerSteve; 14.10.2015
comment
Умный ! Любить это - person Mitsuru Furuta; 22.05.2020

Я немного удивлен, что никто не предложил новую перегрузку метода с аргументом типа 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);
person Joshua Starner    schedule 16.10.2009
comment
Еще лучше, у вас может быть DoSomething<T>(params T[] items), что означает, что компилятор будет обрабатывать преобразование из одного элемента в массив. (Это также позволит вам передать несколько отдельных элементов, и, опять же, компилятор обработает их преобразование в массив за вас.) - person LukeH; 16.10.2009

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

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

or

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

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

var x = MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(new { a = 0, b = "x" }, 0));
person erikkallen    schedule 16.10.2009
comment
Спасибо, хотя Enumerable.Repeat является новым в .Net 3.5. Похоже, что он ведет себя аналогично описанному выше вспомогательному методу. - person Groo; 16.10.2009

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

person teppicymon    schedule 10.11.2010
comment
На самом деле, .AsEnumerable() вам совсем не нужен. Массив YourType[] уже реализует IEnumerable<YourType>. Но мой вопрос относился к случаю, когда доступен только второй метод (в вашем примере), и вы используете .NET 2.0, и вы хотите передать один элемент. - person Groo; 10.11.2010
comment
Да, есть, но вы обнаружите, что можете получить переполнение стека ;-) - person teppicymon; 10.11.2010
comment
И чтобы на самом деле ответить на ваш реальный вопрос (не тот, на который я на самом деле хотел ответить для себя!), Да, вам в значительной степени нужно преобразовать его в массив в соответствии с ответом Марио - person teppicymon; 10.11.2010
comment
Как насчет того, чтобы просто определить метод IEnumerable<T> MakeEnumerable<T>(params T[] items) {return items;}? Затем можно было бы использовать это со всем, что ожидало IEnumerable<T>, для любого количества дискретных элементов. Код должен быть таким же эффективным, как определение специального класса для возврата одного элемента. - person supercat; 21.09.2012

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

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

public static IEnumerable<TResult> Return<TResult>(TResult value)
{
    yield return value;
}

Что можно использовать так:

var result = EnumerableEx.Return(0);

Ix добавляет новую функциональность, отсутствующую в исходных методах расширения Linq, и является прямым результатом создания реактивных расширений (Rx).

Подумайте, Linq Extension Methods + Ix = Rx для IEnumerable.

Вы можете найти как Rx, так и Ix на CodePlex.

person cwharris    schedule 25.11.2013

Я согласен с комментариями @ EarthEngine к исходному сообщению, которые заключаются в том, что «AsSingleton» - лучшее имя. См. эту статью в википедии. Затем из определения синглтона следует, что если в качестве аргумента передается значение NULL, 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, что кажется правильным.

person Jason Boyd    schedule 10.04.2015

Это на 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}");
    }
person V.B.    schedule 02.12.2015
comment
Спасибо, имеет смысл создать структуру для этого случая, чтобы уменьшить нагрузку на сборщик мусора в узких циклах. - person Groo; 02.12.2015
comment
И перечислитель, и перечислимый будут упакованы при возврате .... - person Stephen Drew; 03.04.2017
comment
Не сравнивайте, используя секундомер. Используйте Benchmark.NET github.com/dotnet/BenchmarkDotNet - person NN_; 05.11.2018
comment
Просто измерил и new [] { i } вариант примерно в 3,2 раза быстрее, чем ваш вариант. Также ваш вариант примерно в 1,2 раза быстрее, чем расширение с yield return. - person lorond; 20.05.2019
comment
Вы можете ускорить это, используя другую оптимизацию компилятора C #: добавьте метод SingleEnumerator GetEnumerator(), который возвращает вашу структуру. Компилятор C # будет использовать этот метод (он ищет его с помощью утиной печати) вместо интерфейсных и, таким образом, избегает боксов. Многие встроенные коллекции используют этот прием, например List. . Примечание: это будет работать, только если у вас есть прямая ссылка на SingleSequence, как в вашем примере. Если вы сохраните его в переменной IEnumerable<>, трюк не сработает (будет использован метод интерфейса) - person enzi; 24.03.2020
comment
Ваши тайминги несправедливо используют оптимизацию C # для foreach: никто не собирается реально вызывать foreach для одноэлементной коллекции - попробуйте вместо этого пройти через интерфейс IEnumerable<T> в foreach. - person NetMage; 04.08.2020

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

Если вы готовы сосредоточиться на C #, то соблазнительно назвать это Yield [имея в виду yield return, участвовавший в его реализации].

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

person Ruben Bartelink    schedule 30.05.2011

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

Enumerable.Range(0, 1).Select(i => item);
person Ken Lange    schedule 24.01.2011
comment
Это не. Это другое. - person nawfal; 10.06.2014
comment
Фууу. Это много деталей, просто чтобы превратить элемент в перечислимое. - person ToolmakerSteve; 14.10.2015
comment
Это эквивалент использования машины Руба Голдберга для приготовления завтрака. Да, работает, но для этого нужно преодолеть 10 обручей. Такая простая вещь может стать узким местом производительности, если выполняется в жестком цикле, который выполняется миллионы раз. На практике аспект производительности не имеет значения в 99% случаев, но лично я все еще считаю, что это излишнее излишнее количество. - person enzi; 24.03.2020

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

person FacticiusVir    schedule 16.10.2009

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

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

person frhack    schedule 04.07.2019
comment
Этот ответ был автоматически отмечен как низкое качество. Как объясняется в справке (краткость приемлема, но более полные объяснения лучше.), Отредактируйте ее, чтобы указать OP, что он делает не так, о чем ваш код. - person Sandra Rossi; 05.07.2019
comment
Я вижу, что большая часть этого ответа, включая ту часть, на которую я отвечаю, была позже отредактирована кем-то другим. но если целью второго фрагмента является предоставление значения по умолчанию, если MyData.Where(myCondition) пусто, это уже возможно (и проще) с помощью _ 2_: var existingOrNewObject = MyData.Where(myCondition).DefaultIfEmpty(defaultValue).First();. Это можно упростить до var existingOrNewObject = MyData.FirstOrDefault(myCondition);, если вы хотите default(T), а не настраиваемое значение. - person Lance U. Matthews; 20.05.2020

Я недавно спросил то же самое в другом сообщении (Есть ли способ вызвать метод C #, требующий IEnumerable ‹T› с одним значением? ... с тестированием).

Я хотел, чтобы люди заходили сюда, чтобы увидеть краткое сравнение тестов, показанное в этом новом посте, для 4 подходов, представленных в этих ответах.

Кажется, что простое написание new[] { x } в аргументах метода - самое короткое и быстрое решение.

person mattica    schedule 12.06.2020

Чтобы попасть в раздел «Не обязательно хорошее решение, но все же ... решение» или «Глупые трюки с LINQ», вы можете объединить _ 1_ с _ 2_ ...

IEnumerable<string> singleElementEnumerable = Enumerable.Empty<string>().Append("Hello, World!");

... или Enumerable.Prepend<>() ...

IEnumerable<string> singleElementEnumerable = Enumerable.Empty<string>().Prepend("Hello, World!");

Последние два метода доступны, начиная с .NET Framework 4.7.1 и .NET Core 1.0.

Это работоспособное решение, если бы кто-то действительно намеревался использовать существующие методы вместо написания собственных, хотя я не уверен, более или менее понятно, чем Enumerable.Repeat<>() решение. Это определенно более длинный код (отчасти из-за невозможности вывода параметров типа для Empty<>()), однако он создает вдвое больше объектов перечислителя.

Завершая это "Вы знали, что эти методы существуют?" ответ, Array.Empty<>() можно заменить на Enumerable.Empty<>(), но с этим трудно спорить это делает ситуацию ... лучше.

person Lance U. Matthews    schedule 20.05.2020

Иногда я поступаю так, когда чувствую себя озлобленным:

"_".Select(_ => 3.14)  // or whatever; any type is fine

То же самое и с меньшим количеством нажатий клавиш shift, хех:

from _ in "_" select 3.14

Для служебной функции я считаю, что это наименее подробный или, по крайней мере, более самодокументированный, чем массив, хотя он позволяет скользить нескольким значениям; в качестве плюса ее можно определить как локальную функцию:

static IEnumerable<T> Enumerate (params T[] v) => v;
// usage:
IEnumerable<double> example = Enumerate(1.234);

Вот все другие способы, которые мне удалось придумать (здесь можно запустить):

using System;
using System.Collections.Generic;
using System.Linq;

public class Program {
    
    public static IEnumerable<T> ToEnumerable1 <T> (T v) {
        yield return v;
    }
    
    public static T[] ToEnumerable2 <T> (params T[] vs) => vs;
    
    public static void Main () {
        static IEnumerable<T> ToEnumerable3 <T> (params T[] v) => v;
        p( new string[] { "three" } );
        p( new List<string> { "three" } );
        p( ToEnumerable1("three") ); // our utility function (yield return)
        p( ToEnumerable2("three") ); // our utility function (params)
        p( ToEnumerable3("three") ); // our local utility function (params)
        p( Enumerable.Empty<string>().Append("three") );
        p( Enumerable.Empty<string>().DefaultIfEmpty("three") );
        p( Enumerable.Empty<string>().Prepend("three") );
        p( Enumerable.Range(3, 1) ); // only for int
        p( Enumerable.Range(0, 1).Select(_ => "three") );
        p( Enumerable.Repeat("three", 1) );
        p( "_".Select(_ => "three") ); // doesn't have to be "_"; just any one character
        p( "_".Select(_ => 3.3333) );
        p( from _ in "_" select 3.0f );
        p( "a" ); // only for char
        // these weren't available for me to test (might not even be valid):
        //   new Microsoft.Extensions.Primitives.StringValues("three")
        
    }

    static void p <T> (IEnumerable<T> e) =>
        Console.WriteLine(string.Join(' ', e.Select((v, k) => $"[{k}]={v,-8}:{v.GetType()}").DefaultIfEmpty("<empty>")));

}
person Jason C    schedule 11.06.2021
comment
Злой хак :) Но я не думаю, что вам нужны скобки вокруг параметра _, просто .Select(_ => 3.333). - person Groo; 11.06.2021
comment
@Groo Спасибо! Я регулярно об этом забываю. Проверено и исправлено. - person Jason C; 11.06.2021

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

И соответственно создайте свой собственный Enumerator:

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

person IDarkCoder    schedule 22.08.2018
comment
Класс SingleItemEnumerator<T> имеет смысл, но превращение класса в отдельный элемент IEnumerable кажется нарушением принципа единой ответственности. Возможно, это делает его более практичным, но я бы все же предпочел обернуть его по мере необходимости. - person Groo; 22.08.2018

я предпочитаю

public static IEnumerable<T> Collect<T>(this T item, params T[] otherItems)
{
    yield return item;
    foreach (var otherItem in otherItems)
    {
        yield return otherItem;
    }
}

Это позволяет вам вызывать item.Collect(), если вам нужен синглтон, но также позволяет вызывать item.Collect(item2, item3), если вы хотите

person Luke Kubat    schedule 14.07.2021