2015-06-03 60 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

該方法調用未解析爲第一種方法。它在編譯時使用[overloading resolution](https://msdn.microsoft.com/en-us/library/aa691336%28v=vs.71%29.aspx)。 –

+0

非泛型集合會發生什麼,即IEnumerable? – Romoku

+0

雖然它們仍然可以迭代,但我會將它們視爲可以包裝在IEnumerable中的單個實例。 –

回答

3

這是另一個嘗試,從Eric Lippert的優秀博文的啓發: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'指令都是放置在相同的位置(相同的命名空間聲明「級別」),兩個擴展方法將在相同的「搜索深度」找到,並且問題的原始問題未解決。我意識到,如果我的兩個「如果」前提不能成立,那麼這樣的事情就可以發揮作用,但這似乎是一種非常危險的做法!行爲取決於當前命名空間和「using」指令的位置。 –

+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" }); 
    } 
}