2008-09-16 127 views
22

例如:如何檢測如果類型是另一個泛型類型

public static void DoSomething<K,V>(IDictionary<K,V> items) { 
    items.Keys.Each(key => { 
     if (items[key] **is IEnumerable<?>**) { /* do something */ } 
     else { /* do something else */ } 
} 

可以這樣不使用反射來完成?我如何說C#中的IEnumerable?我應該只使用IEnumerable,因爲IEnumerable <>實現IEnumerable?

回答

35

The previously accepted answer是好的,但它是錯誤的。謝天謝地,這個錯誤是一個小問題。如果您真的想知道接口的通用版本,檢查IEnumerable是不夠的;有很多類僅實現非通用接口。我會在一分鐘內給出答案。不過,首先我想指出的是,公認的答案是過於複雜,因爲下面的代碼將實現在特定情況下是相同的:

if (items[key] is IEnumerable) 

這確實更因爲它分別適用於每個項目(而不是它們的共同子類,V)。

現在,爲正確的解決方案。這是一個比較複雜一點,因爲我們必須採取泛型類型IEnumerable`1(即用一個類型參數的類型IEnumerable<>),並注入正確的通用說法:

static bool IsGenericEnumerable(Type t) { 
    var genArgs = t.GetGenericArguments(); 
    if (genArgs.Length == 1 && 
      typeof(IEnumerable<>).MakeGenericType(genArgs).IsAssignableFrom(t)) 
     return true; 
    else 
     return t.BaseType != null && IsGenericEnumerable(t.BaseType); 
} 

您可以輕鬆地測試此代碼的正確性:

var xs = new List<string>(); 
var ys = new System.Collections.ArrayList(); 
Console.WriteLine(IsGenericEnumerable(xs.GetType())); 
Console.WriteLine(IsGenericEnumerable(ys.GetType())); 

產量:

True 
False 

不要過分受這個使用反射的事實有關。雖然確實會增加運行時間開銷,但使用is運算符也是如此。

當然,上面的代碼是非常有限的,可以擴展到一個更普遍適用的方法,IsAssignableToGenericType。下面的實現是有點不正確的,我會在這裏留下它僅用於歷史目的請勿使用。相反,James has provided an excellent, correct implementation in his answer.

public static bool IsAssignableToGenericType(Type givenType, Type genericType) { 
    var interfaceTypes = givenType.GetInterfaces(); 

    foreach (var it in interfaceTypes) 
     if (it.IsGenericType) 
      if (it.GetGenericTypeDefinition() == genericType) return true; 

    Type baseType = givenType.BaseType; 
    if (baseType == null) return false; 

    return baseType.IsGenericType && 
     baseType.GetGenericTypeDefinition() == genericType || 
     IsAssignableToGenericType(baseType, genericType); 
} 

genericType相同givenType它將失敗;出於同樣的原因,它不能爲空類型,即

IsAssignableToGenericType(typeof(List<int>), typeof(List<>)) == false 
IsAssignableToGenericType(typeof(int?), typeof(Nullable<>)) == false 

我創建了一個gist with a comprehensive suite of test cases

+0

這正是我所期待的。謝謝! 它仍然使用反射,但我正在尋找「typeof(IEnumerable <>)」語法。謝謝! – 2008-09-16 18:47:03

+0

除了使用「is」之外,沒有其他方法可以對類型進行檢查嗎? – 2008-09-16 19:22:21

+0

最後一行應該是`return(baseType.IsGenericType && baseType.GetGenericTypeDefinition()== genericType)|| IsAssignableToGenericType(baseType,genericType);`僅僅因爲基類是泛型的,你無法避免遞歸。 – 2010-09-22 03:53:53

4
if (typeof(IEnumerable).IsAssignableFrom(typeof(V))) { 
+0

使用反射。它的工作原理......但我想知道你是否可以在沒有反射的情況下做到這一點,特別是你如何在c#中指定一個非專門的泛型類型。謝謝! – 2008-09-16 17:17:19

+0

我不太確定。我認爲運行時在類型系統中有足夠的信息能夠在不反映程序集的情況下回答IsAssignableFrom問題。 – 2008-09-16 17:27:53

0

我不確定我明白你的意思。你想知道對象是任何通用類型還是你想測試它是否是特定通用類型?或者你只是想知道是否可枚舉?

我不認爲第一個可能。第二個絕對有可能,只是把它當作其他類型。對於第三種,只需按照您的建議對IEnumerable進行測試即可。

此外,您不能在類型上使用'is'運算符。

// Not allowed 
if (string is Object) 
    Foo(); 
// You have to use 
if (typeof(object).IsAssignableFrom(typeof(string)) 
    Foo(); 

查看this question about types瞭解更多詳情。也許它會幫助你。

+0

我想知道在我的例子中V是否實現了IEnumerable的任何東西,我不在乎什麼。 我應該說的項目[關鍵]是IEnumerable 在我的例子。感謝您接受這一點。 – 2008-09-16 17:32:57

1

我會使用重載:

public static void DoSomething<K,V>(IDictionary<K,V> items) 
    where V : IEnumerable 
{ 
    items.Keys.Each(key => { /* do something */ }); 
} 

public static void DoSomething<K,V>(IDictionary<K,V> items) 
{ 
    items.Keys.Each(key => { /* do something else */ }); 
} 
5

警告有關泛型類型和使用IsAssignableFrom()的一個字......

假設你有以下幾點:

public class MyListBase<T> : IEnumerable<T> where T : ItemBase 
{ 
} 

public class MyItem : ItemBase 
{ 
} 

public class MyDerivedList : MyListBase<MyItem> 
{ 
} 

呼籲基本列表類型或派生列表類型IsAssignableFrom會返回假,但明確MyDerivedList繼承MyListBase<T>。 (Jeff的一個快速註釋,絕對的必須被包裝在一個代碼塊或tildes得到<T>,否則它被忽略。這是故意的嗎?)這個問題源於這樣一個事實,即MyListBase<MyItem>被視爲一個完全不同的類型MyListBase<T>。下面的文章可以更好地解釋這一點。 http://mikehadlow.blogspot.com/2006/08/reflecting-generics.html

相反,試試下面的遞歸函數:

public static bool IsDerivedFromGenericType(Type givenType, Type genericType) 
    { 
     Type baseType = givenType.BaseType; 
     if (baseType == null) return false; 
     if (baseType.IsGenericType) 
     { 
      if (baseType.GetGenericTypeDefinition() == genericType) return true; 
     } 
     return IsDerivedFromGenericType(baseType, genericType); 
    } 

/編輯:這需要通用遞歸考慮以及接口是當場就康拉德的新職位。非常好的工作。 :)

/EDIT2:如果檢查genericType是否爲接口,可以實現性能優勢。該檢查可以是一個,如果圍繞當前界面的代碼塊,但如果你有興趣使用.NET 3.5,我的一個朋友提供以下功能:

public static bool IsAssignableToGenericType(Type givenType, Type genericType) 
    { 
     var interfaces = givenType.GetInterfaces().Where(it => it.IsGenericType).Select(it => it.GetGenericTypeDefinition()); 
     var foundInterface = interfaces.FirstOrDefault(it => it == genericType); 
     if (foundInterface != null) return true; 

     Type baseType = givenType.BaseType; 
     if (baseType == null) return false; 

     return baseType.IsGenericType ? 
      baseType.GetGenericTypeDefinition() == genericType : 
      IsAssignableToGenericType(baseType, genericType); 
    } 
0

我曾經認爲這種情況可能是可解類似@Thomas Danecker的solution一種方式,但增加一個模板參數:

public static void DoSomething<K, V, U>(IDictionary<K,V> items) 
    where V : IEnumerable<U> { /* do something */ } 
public static void DoSomething<K, V>(IDictionary<K,V> items) 
          { /* do something else */ } 

但我現在發現,但這並沒有工作,除非我明確地指定了第一種方法的模板參數。這顯然不是根據字典中的每個項目定製的,但它可能是一種窮人的解決方案。

如果有人能指出我在這裏所做的任何不正確的事情,我將非常感激。

82

非常感謝這篇文章。我想提供康拉德魯道夫解決方案的一個版本,這對我來說效果更好。

public static bool IsAssignableToGenericType(Type givenType, Type genericType) 
{ 
    var interfaceTypes = givenType.GetInterfaces(); 

    foreach (var it in interfaceTypes) 
    { 
     if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType) 
      return true; 
    } 

    if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) 
     return true; 

    Type baseType = givenType.BaseType; 
    if (baseType == null) return false; 

    return IsAssignableToGenericType(baseType, genericType); 
} 
4

感謝偉大的信息:測試時,如果A型爲空值類型我有小問題與該版本,尤其是。爲了方便起見,我將它重構爲擴展方法,並將其簡化爲單個語句。

sometype。這時候:

public static bool IsAssignableToGenericType(this Type givenType, Type genericType) 
{ 
    return givenType.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == genericType) || 
     givenType.BaseType != null && (givenType.BaseType.IsGenericType && givenType.BaseType.GetGenericTypeDefinition() == genericType || 
             givenType.BaseType.IsAssignableToGenericType(genericType)); 
} 

現在,它可以很容易地與調用。IsAssignableToGenericType(typeof運算(MyGenericType <>))