15 ответов:
ваш вспомогательный метод-это самый чистый способ сделать это, ИМО. Если вы передадите список или массив, то недобросовестный кусок кода может привести его к изменению содержимого, что приведет к странному поведению в некоторых ситуациях. Вы можете использовать коллекцию только для чтения, но это, вероятно, потребует еще большей упаковки. Я думаю, что ваше решение так же аккуратно, как и получается.
Ну, если метод ожидает
IEnumerableвы должны передать что-то, что является списком, даже если он содержит только один элемент.передает
new T[] { item }в качестве аргумента должно быть достаточно, я думаю
В C# 3.0, вы можете использовать систему.В LINQ.Перечисляемый класс:
// using System.Linq Enumerable.Repeat(item, 1);это создаст новый IEnumerable, который содержит только ваш элемент.
В C# 3 (я знаю, что вы сказали 2), Вы можете написать общий метод расширения, который может сделать синтаксис немного более приемлемым:
static class IEnumerableExtensions { public static IEnumerable<T> ToEnumerable<T>(this T item) { yield return item; } }клиентский код после
item.ToEnumerable().
Я немного удивлен, что никто не предложил новую перегрузку метода с аргументом типа 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);
это вспомогательный метод работает для элемента или многих.
public static IEnumerable<T> ToEnumerable<T>(params T[] items) { return items; }
Как я только что нашел, и видел, что пользователь 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 выглядит довольно эффективно! (ссылка)
либо (как было сказано ранее)
MyMethodThatExpectsAnIEnumerable(new[] { myObject });или
MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(myObject, 1));в качестве примечания, последняя версия также может быть хорошей, если вы хотите пустой список анонимного объекта, например
var x = MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(new { a = 0, b = "x" }, 0));
Это на 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}"); }
хотя это излишне для одного метода, я считаю, что некоторые люди могут найти интерактивные расширения полезными.
интерактивные расширения (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.
Я согласен с комментариями @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, который просто чувствует себя правильно.
IanG и хороший пост по теме, предполагая, что
EnumerableFrom()как имя и упоминает, что обсуждение указывает на то, что Haskell и Rx называют егоReturn.IIRC F# называет это возвращение тоже. F# ' sSeqвызов оператораsingleton<'T>.заманчиво, если вы готовы быть c#-centric, чтобы назвать его
Yield[намекая наyield returnучаствует в его реализации].если вы заинтересованы в аспектах perf это, Джеймс Майкл Заяц имеет возврат нулевого или одного элемента post тоже, что стоит сканирования.
самый простой способ, я бы сказал, что бы
new T[]{item};; для этого нет синтаксиса. Самый близкий эквивалент, который я могу придумать, этоparamsключевое слово, но, конечно, это требует от вас доступа к определению метода и может использоваться только с массивами.
Я немного опоздал на вечеринку, но я все равно поделюсь своим путем. Моя проблема заключалась в том, что я хотел привязать 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
Чтобы поддержать принцип единой ответственности как указал @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()метод.
Comments