Передача одного элемента в качестве IEnumerable



есть ли простой способ передать один элемент типа T к методу, который ожидает

611   15  

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

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

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

Это на 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# ' s Seq вызов оператора 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

    Ничего не найдено.