2008-09-16 20 views
22

dụ:Làm thế nào để phát hiện nếu Type là một loại Generic

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 */ } 
} 

này có thể được thực hiện mà không sử dụng phản ánh? Làm thế nào để tôi nói IEnumerable trong C#? Tôi có nên sử dụng IEnumerable từ IEnumerable <> triển khai IEnumerable?

Trả lời

35

The previously accepted answer là đẹp, nhưng nó là sai. Rất may, lỗi là một lỗi nhỏ. Kiểm tra IEnumerable là không đủ nếu bạn thực sự muốn biết về phiên bản chung của giao diện; có rất nhiều lớp chỉ thực hiện giao diện nongeneric. Tôi sẽ trả lời trong một phút. Thứ nhất, tuy nhiên, tôi muốn chỉ ra rằng câu trả lời được chấp nhận là quá phức tạp, vì đoạn mã sau sẽ đạt được cùng một trong các trường hợp đưa ra:

if (items[key] is IEnumerable) 

này không nhiều hơn bởi vì nó hoạt động cho từng hạng mục riêng biệt (và không thuộc phân lớp chung của chúng, V).

Bây giờ, để có giải pháp đúng. Đây là một chút phức tạp hơn bởi vì chúng ta phải lấy chung loại IEnumerable`1 (có nghĩa là, loại IEnumerable<> với tham số một loại) và tiêm lập luận chung phải:

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

Bạn có thể kiểm tra tính chính xác của mã này một cách dễ dàng :

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

sản lượng:

True 
False 

Đừng quá lo lắng bởi thực tế rằng đây sử dụng phản ánh. Trong khi đó là sự thật rằng điều này cho biết thêm chi phí thời gian chạy, do đó, việc sử dụng toán tử is.

Tất nhiên mã trên bị hạn chế hết sức và có thể được mở rộng thành phương thức áp dụng chung hơn, IsAssignableToGenericType. Việc triển khai sau đây hơi không chính xác và tôi sẽ để nó ở đây cho mục đích lịch sử chỉ. Không sử dụng nó.Thay vào đó, 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); 
} 

Nó thất bại khi genericType cũng giống như givenType; vì lý do tương tự, nó không thành công đối với các loại không có giá trị, tức là

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

Tôi đã tạo một gist with a comprehensive suite of test cases.

+0

Đây chính xác là những gì tôi đang tìm kiếm. Cảm ơn! Nó vẫn sử dụng sự phản chiếu, nhưng tôi đang tìm cú pháp "typeof (IEnumerable <>)". Cảm ơn! –

+0

Không có cách nào khác để thực hiện việc kiểm tra loại, ngoài việc sử dụng "là"? –

+0

dòng cuối cùng nên đọc 'return (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == genericType) || IsAssignableToGenericType (baseType, genericType); 'Bạn không thể tránh đệ quy chỉ vì lớp cơ sở là chung chung. –

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

Điều đó sử dụng sự phản chiếu. Nó hoạt động ... nhưng tôi đã tự hỏi nếu bạn có thể làm điều đó mà không có sự phản ánh, đặc biệt là cách bạn nói chỉ định một loại generic không đặc biệt trong C#. Cảm ơn! –

+0

Tôi không chắc lắm. Tôi nghĩ rằng thời gian chạy có đủ thông tin trong hệ thống kiểu để có thể trả lời câu hỏi IsAssignableFrom mà không phản ánh việc lắp ráp. –

0

Tôi không chắc mình hiểu ý của bạn ở đây. Bạn có muốn biết nếu đối tượng là bất kỳ loại chung nào hoặc bạn có muốn kiểm tra xem đây có phải là loại loại chung cụ thể không? Hay bạn chỉ muốn biết liệu có đếm được không?

Tôi không nghĩ điều đầu tiên là có thể. Thứ hai là chắc chắn có thể, chỉ xử lý nó như bất kỳ loại nào khác. Đối với thứ ba, chỉ cần kiểm tra nó chống lại IEnumerable như bạn đề nghị.

Ngoài ra, bạn không thể sử dụng toán tử 'is' trên các loại.

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

Xem this question about types để biết thêm chi tiết. Có lẽ nó sẽ giúp bạn.

+0

Tôi muốn biết nếu V trong ví dụ của tôi thực hiện IEnumerable của bất cứ điều gì, và tôi không quan tâm những gì. Tôi phải nói rằng các mục [key] là IEnumerable trong ví dụ của tôi. Cảm ơn vì đã bắt được điều này. –

1

Tôi muốn sử dụng quá tải:

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

Một lời cảnh báo về các loại generic và sử dụng IsAssignableFrom() ...

Giả sử bạn có những điều sau đây:

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

public class MyItem : ItemBase 
{ 
} 

public class MyDerivedList : MyListBase<MyItem> 
{ 
} 

Calling IsAssignableFrom vào loại danh sách cơ sở hoặc vào loại danh sách có nguồn gốc sẽ trả về false, nhưng rõ ràng MyDerivedList được thừa kế MyListBase<T>. (Một lưu ý nhanh cho Jeff, generics hoàn toàn phải được gói trong một khối mã hoặc dấu ngã để có được các <T>, nếu không nó được bỏ qua. Đây có phải là dự định?) Vấn đề bắt nguồn từ thực tế là MyListBase<MyItem> được coi là một loại hoàn toàn khác MyListBase<T>. Bài viết sau đây có thể giải thích điều này tốt hơn một chút. http://mikehadlow.blogspot.com/2006/08/reflecting-generics.html

Thay vào đó, hãy thử các hàm đệ quy sau:

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

/EDIT: bài mới Konrad của mà mất đệ quy chung vào tài khoản cũng như giao diện là tại chỗ trên. Công việc rất tốt. :)

/EDIT2: Nếu kiểm tra được thực hiện về việc liệu genericType có phải là giao diện hay không, lợi ích hiệu suất có thể được thực hiện. Vui lòng cung có thể là một khối nếu xung quanh mã giao diện hiện tại, nhưng nếu bạn đang quan tâm đến việc sử dụng .NET 3.5, một người bạn của tôi cung cấp như sau:

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

Ghi chú lại cho Jeff: Điều này là do thiết kế vì Markdown cho phép HTML nội tuyến. Cảm ơn thông báo của bạn, tôi sẽ tích hợp nó vào mã của tôi. –

+0

Giàu, tôi thực sự thích mã của bạn nhưng tôi không may, nó không hoạt động nếu 'genericType' là một kiểu giao diện như trong câu hỏi. Điều này có thể dễ dàng sửa chữa, mặc dù. –

+0

Xem câu trả lời của James để có câu trả lời hay hơn: http://stackoverflow.com/a/1075059/320623 – Joel

0

Tôi đã từng nghĩ một tình huống như vậy có thể giải quyết được theo một cách tương tự như @Thomas Danecker của solution, nhưng thêm một mẫu đối số:

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 */ } 

nhưng tôi nhận thấy bây giờ mà nó does't làm việc trừ khi tôi xác định các đối số mẫu của phương pháp đầu tiên rõ ràng. Điều này rõ ràng không được tùy chỉnh cho mỗi mục trong từ điển, nhưng nó có thể là một loại giải pháp của người nghèo.

Tôi sẽ rất biết ơn nếu ai đó có thể chỉ ra bất kỳ điều gì không chính xác mà tôi có thể đã làm ở đây.

82

Cảm ơn rất nhiều vì bài đăng này. Tôi muốn cung cấp một phiên bản của giải pháp của Konrad Rudolph đã làm việc tốt hơn cho tôi. Tôi gặp vấn đề nhỏ với phiên bản đó, đáng chú ý là khi thử nghiệm nếu Loại là loại giá trị không thể sử dụng:

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

Công việc tuyệt vời! Đây là một phiên bản ngắn (er) cho những người thích ngắn gọn: 'bool tĩnh công cộng IsAssignableToGenericType (loại Kiểu này, Loại genericType) { kiểu trả về.GetInterfaces(). Any (it => it.IsGenericType && it.GetGenericTypeDefinition () == genericType) || (type.IsGenericType && type.GetGenericTypeDefinition() == genericType) || (type.BaseType! = null && IsAssignableToGenericType (type.BaseType, genericType)); } ' – Moeri

+3

Điều này là tuyệt vời nhưng có vẻ như điều này sẽ nhanh hơn nhiều nếu bạn di chuyển so sánh trực tiếp lên trên so sánh giao diện nếu so sánh trực tiếp khớp với nhau? – Shazwazza

+1

@Moeri Làm cho nó ngắn hơn bằng cách loại bỏ biến vị ngữ dư thừa và sử dụng '? .':' public static bool IsAssignableToGenericType (kiểu này, Type generic) {return new [] {type} .Concat (type.GetTypeInfo().) .Any (i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == generic) || (type.GetTypeInfo(). BaseType? .IsAssignableToGenericType (generic) ?? false); } ' – HappyNomad

4

Cảm ơn thông tin tuyệt vời. Đối với convienience, tôi đã refactored này vào một phương pháp mở rộng và giảm nó vào một tuyên bố duy nhất.

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

Bây giờ nó có thể dễ dàng gọi với:

sometype.IsAssignableToGenericType (typeof (MyGenericType <>))

+0

+1 cho một lớp lót –

Các vấn đề liên quan