Передача одного элемента в виде 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<T>
/// 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<T> 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()
.