2015-06-03 2 views
3

Я хотел создать метод расширения, который бы эффективно обертывал отдельные объекты как IEnumerables. Это было во избежание случаев, когда вы положили new [] {} в середине выражения. Это достаточно легко сделать, используя следующий метод:Создание метода расширения для обертывания типов как IEnumerables

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>, а когда нет?

+0

Вызов метода не разрешен для первого метода. Он использует [перегрузочное разрешение] (https://msdn.microsoft.com/en-us/library/aa691336%28v=vs.71%29.aspx) во время компиляции. –

+0

Что происходит с коллекциями, не относящимися к генерации, то есть 'IEnumerable'? – Romoku

+0

Хотя они все еще могут быть повторены, я бы рассматривал их как отдельные экземпляры, которые могут и будут завернуты в IEnumerable. –

ответ

3

Вот еще одна попытка, вдохновленная отличным сообщением Эрика Липперта: 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; 
+0

Зачем это работать? *** Если *** место, где вы хотите вызвать расширение, находится внутри несвязанного пространства имен, скажем, 'namespace MyApp.SomeTopic {...}' и ***, если *** обе директивы 'using' размещенные в одном и том же месте (одинаковый декларации пространства имен «уровень»), оба метода расширения будут найдены с одинаковой «глубиной поиска», и исходная проблема из вопроса не решена.Я понимаю, что что-то вроде этого может работать, если мои два «если» помещения выше не выполняются, но это кажется чрезвычайно опасным подходом! Поведение зависит от текущего пространства имен и от размещения директив 'use'. –

+0

Сообщение, которое вы, как правило, хорошо, я вижу сейчас. –

1

Вы считаете, что меняете свою подпись немного, делает код немного более сложным, но делает его чрезвычайно простым.

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" }); 
    } 
} 
Смежные вопросы