Създаване на метод за разширение за обвиване на типове като IEnumerables

Исках да създам метод за разширение, който ефективно да обвива единични обекти като IEnumerables. Това беше, за да се избегнат случаите, в които в крайна сметка поставяте new [] {} в средата на израз. Това е достатъчно лесно да се направи, като се използва следният метод:

public static IEnumerable<TSource> WrapAsEnumerable<TSource>(this TSource source)
{
    return new[] { source };
}

Проблемът е, че това ще бъде приложено към всички и всички типове (което е очакваното поведение), но това също ще има страничен ефект от предоставянето на метода на IEnumerable <T> екземпляри. В случай, че разрешеният разширен тип е IEnumerable<T>, бих искал просто да върна този IEnumerable, тъй като алтернативата се намира с IEnumerable<IEnumerable<T>>, което всъщност не е това, което бихте очаквали при извикване на метода.

Инстинктивно (и може би сънливо) първо създадох претоварване, което изглеждаше така

public static IEnumerable<TSource> WrapAsEnumerable<TSource>(this IEnumerable<TSource> source)
{
    return source;
}

за да се справи със случая, когато типът за обвиване е IEnumerable<T>, но разбира се, контролният поток винаги се разрешава на първия метод.

И така, въпросът е: как мога да създам такъв метод за обвиване, който обработва както случая, когато екземплярът на разширения параметър е IEnumerable<T>, така и когато не е?


person Phil Gref    schedule 03.06.2015    source източник
comment
Извикването на метода не е разрешено до първия метод. Той използва резолюция при претоварване при компилиране време.   -  person Willem Van Onsem    schedule 03.06.2015
comment
Какво се случва с колекциите, които не са общи, т.е. IEnumerable?   -  person Dustin Kingen    schedule 03.06.2015
comment
Докато те все още могат да бъдат итерирани, бих ги разглеждал като единични екземпляри, които могат и ще бъдат обвити в IEnumerable.   -  person Phil Gref    schedule 03.06.2015
comment
Бих казал, че очакваното поведение, извикващо обвиване в последователност върху последователност, ще създаде последователност от последователности. Има много ситуации, в които бихте искали да направите това. Бих казал, че просто оставете това, което имате; не е счупено.   -  person Servy    schedule 03.06.2015
comment
Напълно съм съгласен, че има някои случаи, в които бихте искали това конкретно поведение, но винаги мога да използвам булев параметър (със стойност по подразбиране), за да реша каква стратегия да възприема. Това, което питам е: усмихнете ме за секунда. Да приемем, че методът е наречен GetEnumerableIfSingleElementOrIdentityIfElementIsEnumerable. Искам да видя дали някой се сеща за начин, който съм пропуснал, за да получа поведението, което описах.   -  person Phil Gref    schedule 03.06.2015


Отговори (2)


Ето още един опит, вдъхновен от отличната публикация на Ерик Липерт на: https://stackoverflow.com/a/1451184/4955425 .

Можете да контролирате разделителната способност на претоварване, като поставите вашите 2 метода за разширение на различни нива в йерархията на пространството от имена.

namespace MyExtensions
{
    public static class HighPrecendenceExtensions
    {
        public static IEnumerable<TSource> WrapAsEnumerable<TSource>(this IEnumerable<TSource> source)
        {
            return source;
        }
    }

    namespace LowerPrecedenceNamespace
    {
        public static class LowPrecedenceExtensions
        {
            public static IEnumerable<TSource> WrapAsEnumerable<TSource>(this TSource source)
            {
                return new[] { source };
            }
        }
    }
}

Недостатъкът обаче е, че винаги ще трябва да препращате към двете пространства от имена, за да получите правилното поведение при извикване на метод.

using MyExtensions;
using MyExtensions.LowerPrecedenceNamespace;
person sstan    schedule 03.06.2015
comment
Защо трябва да работи? Ако мястото, където искате да извикате разширението, е в несвързано пространство от имена, кажете namespace MyApp.SomeTopic { ... } и ако и двете using директиви са поставени на едно и също място (едно и също ниво на декларация на пространство от имена), и двата метода на разширение ще бъдат намерени на една и съща дълбочина на търсене и първоначалният проблем от въпроса е нерешен. Осъзнавам, че нещо подобно може да проработи, ако моите две помещения if по-горе не издържат, но изглежда като изключително опасен подход! Поведението зависи от текущото пространство от имена и от разположението на using директиви. - person Jeppe Stig Nielsen; 04.06.2015

Обмисляли ли сте да промените малко подписа си, това прави кода малко по-сложен, но прави употребата изключително проста.

public static class Extensions
{
    public static IEnumerable<TSource> WrapAsEnumerable<TSource>(this object source)
    {
        var allInterfaces = source.GetType().GetInterfaces();

        IEnumerable<Type> allEnumerableInterfaces = allInterfaces.Where(t => t.Name.StartsWith("IEnumerable"));
        if (!allEnumerableInterfaces.Any())
            return new[] { (TSource)source };

        IEnumerable<Type> genericEnumerableOfTSource = allEnumerableInterfaces.Where(t => t.GenericTypeArguments.Contains(typeof(TSource)));

        // we have a generic implementation
        if (genericEnumerableOfTSource.Any())
        {
            return (IEnumerable<TSource>) source;
        }

        return new[] { (TSource)source }; ;
    }
}

[TestFixture]
public class Tests
{
    [Test]
    public void should_return_string_an_enumerable()
    {
        const string aString = "Hello World";
        var wrapped = aString.WrapAsEnumerable<string>();
        wrapped.ShouldAllBeEquivalentTo(new[] {"Hello World"});
    }

    [Test]
    public void should_return_generic_enumerable_unwrapped()
    {
        var aStringList = new List<string> { "Hello", "World" };
        var wrapped = aStringList.WrapAsEnumerable<string>();
        wrapped.ShouldAllBeEquivalentTo(new[] { "Hello", "World" });
    }

    [Test]
    public void should_return_non_generic_enumerable_unwrapped()
    {
        var aStringArray = new[] {"Hello", "World"};
        var wrapped = aStringArray.WrapAsEnumerable<string>();
        wrapped.ShouldAllBeEquivalentTo(new[] { "Hello", "World" });
    }
}
person Ruskin    schedule 03.06.2015