2011-02-01 29 views
10

Tôi có một giao diện tương tự như hình dưới đây:Kiểm tra nếu một đối tượng đáp ứng một hạn chế Generic Parameter

public interface IInterface<T> 
    where T : IInterface<T> 
{ 
} 

Và bây giờ tôi cần phải tạo ra một loại đại diện cho giao diện này sử dụng phản chiếu, ví dụ

typeof(IInterface<>).MakeGenericType(someType); 

Tuy nhiên, tôi không thực sự biết những gì loại 'someType' sẽ đến khi thời gian chạy, và nó có thể là các loại sẽ không có giá trị như một đối số kiểu cho giao diện chung chung, do MakeGenericType thất bại.

Câu hỏi đặt ra là, làm cách nào để kiểm tra xem 'someType' có hợp lệ cho ràng buộc chung không?

Trả lời

17

Thành thật mà nói, cách tiếp cận đơn giản nhất sẽ được chỉ cần gọi MakeGenericType và bắt ArgumentException sẽ được ném nếu bất kỳ đối số kiểu là sai (hoặc nếu bạn đã có những sai số của các tham số type).

Trong khi bạn có thể sử dụng Type.GetGenericParameterConstraints để tìm các ràng buộc và sau đó tìm hiểu ý nghĩa của từng mã, nó sẽ trở thành mã xấu và dễ bị lỗi.

Tôi không thường là như đề xuất "hãy thử và bắt" nhưng trong trường hợp này tôi nghĩ đó sẽ là phương pháp đáng tin cậy nhất. Nếu không, bạn chỉ cần thực hiện lại các kiểm tra mà CLR sẽ thực hiện anyway - và cơ hội nào bạn sẽ thực hiện lại chúng một cách hoàn hảo? :)

+0

Hội chợ đủ, nhờ Jon. Đã hy vọng để tránh điều đó nhưng như hiệu suất không phải là một vấn đề trong mã này, nếu bạn nói đó là cách dễ nhất là nó đủ tốt cho tôi! – Simon

+0

Đây không phải là cách tiếp cận "người lười biếng"? Tôi đã bị đập vào hộp sọ bởi nhiều người cao tuổi trong những năm đầu của tôi vì đã sử dụng ngoại lệ để kiểm tra mọi thứ thay vì kiểm tra mọi thứ để tránh một ngoại lệ. –

+0

Nếu bạn có thể cải thiện câu trả lời của Jon, tôi sẽ vui hơn Joel :-) – Simon

4

Điều này là có thể. Với một hạn chế, bạn sử dụng Type.GenericParameterAttributes và mặt nạ

GenericParameterAttributes.ReferenceTypeConstraint 
GenericParameterAttributes.NotNullableValueTypeConstraint 
GenericParameterAttributes.DefaultConstructorConstraint 

để kiểm tra sự hiện diện của class, struct hoặc new() ràng buộc. Bạn có thể dễ dàng kiểm tra xem một loại có đáp ứng các ràng buộc này hay không (đầu tiên là dễ thực hiện (sử dụng Type.IsClass), thứ hai hơi phức tạp nhưng bạn có thể làm điều đó bằng cách sử dụng sự phản chiếu. Type.GetConstructor(new Type[0]) không trả lại hàm khởi tạo mặc định cho các loại giá trị nhưng bạn biết rằng các hàm này có một hàm tạo mặc định, dù sao đi nữa, bạn cũng có thể sử dụng Type.GetGenericParameterConstraints để nhận các ràng buộc phân cấp kiểu rằng các loại nhất định đáp ứng chúng

+0

Cảm ơn câu trả lời Jason - Tôi đang thực hiện một cách dễ dàng và chỉ bắt được ngoại lệ ... mã chạy một lần khi khởi động, vì vậy trong trường hợp này tôi đang uốn các quy tắc thông thường. – Simon

+0

Chỉnh sửa nhỏ cần thiết ở đây. Nó không chính xác khi bạn nói "đầu tiên là dễ thực hiện (sử dụng' Type.IsClass') ". Ràng buộc 'class' không ràng buộc kiểu cho một lớp. Nó thực sự ràng buộc nó với bất kỳ loại tham chiếu nào (bao gồm giao diện, vv) Xem https://msdn.microsoft.com/en-us/library/d5x73970.aspx. Tôi không biết tại sao Microsoft quyết định sử dụng từ khóa 'class' cho ràng buộc này vì nó rất khó hiểu. Thay vào đó, bạn cần sử dụng 'sometype.IsValueType == false'. – 0b101010

2

Tìm kiếm một chút trực tuyến cho một cái gì đó như thế này, tôi found this article bởi Scott Hansen.Sau khi đọc nó (nó ngắn), và đã mỏng vua dọc theo dòng của phương pháp mở rộng từ câu trả lời @ Jon Skeet, tôi ném miếng ngon này ít với nhau và đưa nó một cách nhanh chóng:

public static class Extensions 
{ 
    public static bool IsImplementationOf(this System.Type objectType, System.Type interfaceType) 
    { 
     return (objectType.GetInterface(interfaceType.FullName) != null); 
    } 
} 

Nó thực sự làm việc cho vài cuộc thử nghiệm mà tôi đưa nó vào. Nó trở lại đúng khi tôi sử dụng nó trên một kiểu mà DID thực hiện một giao diện mà tôi đã truyền nó, và nó đã thất bại khi tôi chuyển cho nó một kiểu không thực hiện giao diện. Tôi thậm chí đã loại bỏ khai báo giao diện khỏi loại thành công và thử lại lần nữa và nó không thành công.Tôi đã sử dụng nó như thế này:

if (myType.IsImplementationOf(typeof(IFormWithWorker))) 
{ 
    //Do Something 
    MessageBox.Show(myType.GetInterface(typeof(DocumentDistributor.Library.IFormWithWorker).FullName).FullName); 
} 
else 
{ 
    MessageBox.Show("It IS null"); 
} 

tôi có lẽ sẽ chơi đùa với nó nhưng tôi có thể kết thúc gửi nó cho: What are your favorite extension methods for C#? (codeplex.com/extensionoverflow)

+0

+1 Hmm, điều này cũng phù hợp với tôi ... –

0

Dưới đây là thực hiện của tôi về 3 phương pháp khuyến nông:

  • bool CanMakeGenericTypeVia(this Type openConstructedType, Type closedConstructedType)
  • Type MakeGenericTypeVia(this Type openConstructedType, Type closedConstructedType)
  • MethodInfo MakeGenericMethodVia(this MethodInfo openConstructedMethod, params Type[] closedConstructedParameterTypes)

Đầu tiên cho phép bạn kiểm tra xem loại được xây dựng kín có khớp với định nghĩa kiểu được xây dựng không. Nếu vậy, thứ hai có thể phỏng đoán tất cả các đối số kiểu được yêu cầu để trả về một cấu trúc khép kín được xây dựng từ một kiểu được xây dựng khép kín nhất định. Cuối cùng, phương thức thứ ba có thể tự động giải quyết tất cả điều này cho các phương thức. Lưu ý rằng các phương pháp này sẽ không thất bại hoặc trả về false nếu bạn chuyển một loại được xây dựng mở khác làm đối số kiểu "được xây dựng khép kín", miễn là loại thứ hai này tuân thủ tất cả các ràng buộc kiểu của kiểu mở ban đầu được xây dựng. . Thay vào đó, họ sẽ giải quyết càng nhiều thông tin loại càng tốt từ các loại đã cho. Do đó, nếu bạn muốn đảm bảo độ phân giải đã đưa ra một loại được đóng hoàn toàn, bạn nên kiểm tra xem kết quả của ContainsGenericParameters có trả về sai không. Điều này phù hợp với hành vi của .NET MakeGenericType hoặc MakeGenericMethod.

Cũng lưu ý rằng tôi không được thông báo đầy đủ về đồng ý và đối nghịch, vì vậy những triển khai này có thể không chính xác trong vấn đề đó.

sử dụng Ví dụ:

public static void GenericMethod<T0, T1>(T0 direct, IEnumerable<T1> generic) 
    where T0 : struct 
    where T1 : class, new(), IInterface 
{ } 

public interface IInterface { } 
public class CandidateA : IInterface { private CandidateA(); } 
public struct CandidateB : IInterface { } 
public class CandidateC { public CandidateC(); } 
public class CandidateD : IInterface { public CandidateD(); } 

var method = GetMethod("GenericMethod"); 
var type0 = method.GetParameters()[0].ParameterType; 
var type1 = method.GetParameters()[1].ParameterType; 

// Results: 

type0.CanMakeGenericTypeVia(typeof(int)) // true 
type0.CanMakeGenericTypeVia(typeof(IList)) // false, fails struct 

type1.CanMakeGenericTypeVia(typeof(IEnumerable<CandidateA>)) 
// false, fails new() 

type1.CanMakeGenericTypeVia(typeof(IEnumerable<CandidateB>)) 
// false, fails class 

type1.CanMakeGenericTypeVia(typeof(IEnumerable<CandidateC>)) 
// false, fails : IInterface 

type1.CanMakeGenericTypeVia(typeof(IEnumerable<CandidateD>)) 
// true 

type0.MakeGenericTypeVia(typeof(int)) 
// typeof(int) 

type1.MakeGenericTypeVia(typeof(List<CandidateD>)) 
// IEnumerable<CandidateD> 

method.MakeGenericMethodVia(123.GetType(), (new CandidateD[0]).GetType()) 
// GenericMethod(int, IEnumerable<CandidateD>) 

method.MakeGenericMethodVia(123.GetType(), type1) 
// GenericMethod<T1>(int, IEnumerable<T1>) 
// (partial resolution) 

Thực hiện:

public static bool CanMakeGenericTypeVia(this Type openConstructedType, Type closedConstructedType) 
{ 
    if (openConstructedType == null) 
    { 
     throw new ArgumentNullException("openConstructedType"); 
    } 

    if (closedConstructedType == null) 
    { 
     throw new ArgumentNullException("closedConstructedType"); 
    } 

    if (openConstructedType.IsGenericParameter) // e.g.: T 
    { 
     // The open-constructed type is a generic parameter. 

     // First, check if all special attribute constraints are respected. 

     var constraintAttributes = openConstructedType.GenericParameterAttributes; 

     if (constraintAttributes != GenericParameterAttributes.None) 
     { 
      // e.g.: where T : struct 
      if (constraintAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint) && 
       !closedConstructedType.IsValueType) 
      { 
       return false; 
      } 

      // e.g.: where T : class 
      if (constraintAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint) && 
       closedConstructedType.IsValueType) 
      { 
       return false; 
      } 

      // e.g.: where T : new() 
      if (constraintAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint) && 
       closedConstructedType.GetConstructor(Type.EmptyTypes) == null) 
      { 
       return false; 
      } 

      // TODO: Covariance and contravariance? 
     } 

     // Then, check if all type constraints are respected. 

     // e.g.: where T : BaseType, IInterface1, IInterface2 
     foreach (var constraint in openConstructedType.GetGenericParameterConstraints()) 
     { 
      if (!constraint.IsAssignableFrom(closedConstructedType)) 
      { 
       return false; 
      } 
     } 

     return true; 
    } 
    else if (openConstructedType.ContainsGenericParameters) 
    { 
     // The open-constructed type is not a generic parameter but contains generic parameters. 
     // It could be either a generic type or an array. 

     if (openConstructedType.IsGenericType) // e.g. Generic<T1, int, T2> 
     { 
      // The open-constructed type is a generic type. 

      var openConstructedGenericDefinition = openConstructedType.GetGenericTypeDefinition(); // e.g.: Generic<,,> 
      var openConstructedGenericArguments = openConstructedType.GetGenericArguments(); // e.g.: { T1, int, T2 } 

      // Check a list of possible candidate closed-constructed types: 
      // - the closed-constructed type itself 
      // - its base type, if any (i.e.: if the closed-constructed type is not object) 
      // - its implemented interfaces 

      var inheritedClosedConstructedTypes = new List<Type>(); 

      inheritedClosedConstructedTypes.Add(closedConstructedType); 

      if (closedConstructedType.BaseType != null) 
      { 
       inheritedClosedConstructedTypes.Add(closedConstructedType.BaseType); 
      } 

      inheritedClosedConstructedTypes.AddRange(closedConstructedType.GetInterfaces()); 

      foreach (var inheritedClosedConstructedType in inheritedClosedConstructedTypes) 
      { 
       if (inheritedClosedConstructedType.IsGenericType && 
        inheritedClosedConstructedType.GetGenericTypeDefinition() == openConstructedGenericDefinition) 
       { 
        // The inherited closed-constructed type and the open-constructed type share the same generic definition. 

        var inheritedClosedConstructedGenericArguments = inheritedClosedConstructedType.GetGenericArguments(); // e.g.: { float, int, string } 

        // For each open-constructed generic argument, recursively check if it 
        // can be made into a closed-constructed type via the closed-constructed generic argument. 

        for (int i = 0; i < openConstructedGenericArguments.Length; i++) 
        { 
         if (!openConstructedGenericArguments[i].CanMakeGenericTypeVia(inheritedClosedConstructedGenericArguments[i])) // !T1.IsAssignableFromGeneric(float) 
         { 
          return false; 
         } 
        } 

        // The inherited closed-constructed type matches the generic definition of 
        // the open-constructed type and each of its type arguments are assignable to each equivalent type 
        // argument of the constraint. 

        return true; 
       } 
      } 

      // The open-constructed type contains generic parameters, but no 
      // inherited closed-constructed type has a matching generic definition. 

      return false; 
     } 
     else if (openConstructedType.IsArray) // e.g. T[] 
     { 
      // The open-constructed type is an array. 

      if (!closedConstructedType.IsArray || 
       closedConstructedType.GetArrayRank() != openConstructedType.GetArrayRank()) 
      { 
       // Fail if the closed-constructed type isn't an array of the same rank. 
       return false; 
      } 

      var openConstructedElementType = openConstructedType.GetElementType(); 
      var closedConstructedElementType = closedConstructedType.GetElementType(); 

      return openConstructedElementType.CanMakeGenericTypeVia(closedConstructedElementType); 
     } 
     else 
     { 
      // I don't believe this can ever happen. 

      throw new NotImplementedException("Open-constructed type contains generic parameters, but is neither an array nor a generic type."); 
     } 
    } 
    else 
    { 
     // The open-constructed type does not contain generic parameters, 
     // we can proceed to a regular closed-type check. 

     return openConstructedType.IsAssignableFrom(closedConstructedType); 
    } 
} 

public static Type MakeGenericTypeVia(this Type openConstructedType, Type closedConstructedType, Dictionary<Type, Type> resolvedGenericParameters, bool safe = true) 
{ 
    if (openConstructedType == null) 
    { 
     throw new ArgumentNullException("openConstructedType"); 
    } 

    if (closedConstructedType == null) 
    { 
     throw new ArgumentNullException("closedConstructedType"); 
    } 

    if (resolvedGenericParameters == null) 
    { 
     throw new ArgumentNullException("resolvedGenericParameters"); 
    } 

    if (safe && !openConstructedType.CanMakeGenericTypeVia(closedConstructedType)) 
    { 
     throw new InvalidOperationException("Open-constructed type is not assignable from closed-constructed type."); 
    } 

    if (openConstructedType.IsGenericParameter) // e.g.: T 
    { 
     // The open-constructed type is a generic parameter. 
     // We can directly map it to the closed-constructed type. 

     // Because this is the lowest possible level of type resolution, 
     // we will add this entry to our list of resolved generic parameters 
     // in case we need it later (e.g. for resolving generic methods). 

     // Note that we allow an open-constructed type to "make" another 
     // open-constructed type, as long as the former respects all of 
     // the latter's constraints. Therefore, we will only add the resolved 
     // parameter to our dictionary if it actually is resolved. 

     if (!closedConstructedType.ContainsGenericParameters) 
     { 
      if (resolvedGenericParameters.ContainsKey(openConstructedType)) 
      { 
       if (resolvedGenericParameters[openConstructedType] != closedConstructedType) 
       { 
        throw new InvalidOperationException("Nested generic parameters resolve to different values."); 
       } 
      } 
      else 
      { 
       resolvedGenericParameters.Add(openConstructedType, closedConstructedType); 
      } 
     } 

     return closedConstructedType; 
    } 
    else if (openConstructedType.ContainsGenericParameters) // e.g.: Generic<T1, int, T2> 
    { 
     // The open-constructed type is not a generic parameter but contains generic parameters. 
     // It could be either a generic type or an array. 

     if (openConstructedType.IsGenericType) // e.g. Generic<T1, int, T2> 
     { 
      // The open-constructed type is a generic type. 

      var openConstructedGenericDefinition = openConstructedType.GetGenericTypeDefinition(); // e.g.: Generic<,,> 
      var openConstructedGenericArguments = openConstructedType.GetGenericArguments(); // e.g.: { T1, int, T2 } 

      // Check a list of possible candidate closed-constructed types: 
      // - the closed-constructed type itself 
      // - its base type, if any (i.e.: if the closed-constructed type is not object) 
      // - its implemented interfaces 

      var inheritedCloseConstructedTypes = new List<Type>(); 

      inheritedCloseConstructedTypes.Add(closedConstructedType); 

      if (closedConstructedType.BaseType != null) 
      { 
       inheritedCloseConstructedTypes.Add(closedConstructedType.BaseType); 
      } 

      inheritedCloseConstructedTypes.AddRange(closedConstructedType.GetInterfaces()); 

      foreach (var inheritedCloseConstructedType in inheritedCloseConstructedTypes) 
      { 
       if (inheritedCloseConstructedType.IsGenericType && 
        inheritedCloseConstructedType.GetGenericTypeDefinition() == openConstructedGenericDefinition) 
       { 
        // The inherited closed-constructed type and the open-constructed type share the same generic definition. 

        var inheritedClosedConstructedGenericArguments = inheritedCloseConstructedType.GetGenericArguments(); // e.g.: { float, int, string } 

        // For each inherited open-constructed type generic argument, recursively resolve it 
        // via the equivalent closed-constructed type generic argument. 

        var closedConstructedGenericArguments = new Type[openConstructedGenericArguments.Length]; 

        for (int j = 0; j < openConstructedGenericArguments.Length; j++) 
        { 
         closedConstructedGenericArguments[j] = MakeGenericTypeVia 
         (
          openConstructedGenericArguments[j], 
          inheritedClosedConstructedGenericArguments[j], 
          resolvedGenericParameters, 
          safe: false // We recursively checked before, no need to do it again 
         ); 

         // e.g.: Resolve(T1, float) 
        } 

        // Construct the final closed-constructed type from the resolved arguments 

        return openConstructedGenericDefinition.MakeGenericType(closedConstructedGenericArguments); 
       } 
      } 

      // The open-constructed type contains generic parameters, but no 
      // inherited closed-constructed type has a matching generic definition. 
      // This cannot happen in safe mode, but could in unsafe mode. 

      throw new InvalidOperationException("Open-constructed type is not assignable from closed-constructed type."); 
     } 
     else if (openConstructedType.IsArray) // e.g. T[] 
     { 
      var arrayRank = openConstructedType.GetArrayRank(); 

      // The open-constructed type is an array. 

      if (!closedConstructedType.IsArray || 
       closedConstructedType.GetArrayRank() != arrayRank) 
      { 
       // Fail if the closed-constructed type isn't an array of the same rank. 
       // This cannot happen in safe mode, but could in unsafe mode. 
       throw new InvalidOperationException("Open-constructed type is not assignable from closed-constructed type."); 
      } 

      var openConstructedElementType = openConstructedType.GetElementType(); 
      var closedConstructedElementType = closedConstructedType.GetElementType(); 

      return openConstructedElementType.MakeGenericTypeVia 
      (
       closedConstructedElementType, 
       resolvedGenericParameters, 
       safe: false 
      ).MakeArrayType(arrayRank); 
     } 
     else 
     { 
      // I don't believe this can ever happen. 

      throw new NotImplementedException("Open-constructed type contains generic parameters, but is neither an array nor a generic type."); 
     } 
    } 
    else 
    { 
     // The open-constructed type does not contain generic parameters, 
     // it is by definition already resolved. 

     return openConstructedType; 
    } 
} 

public static MethodInfo MakeGenericMethodVia(this MethodInfo openConstructedMethod, params Type[] closedConstructedParameterTypes) 
{ 
    if (openConstructedMethod == null) 
    { 
     throw new ArgumentNullException("openConstructedMethod"); 
    } 

    if (closedConstructedParameterTypes == null) 
    { 
     throw new ArgumentNullException("closedConstructedParameterTypes"); 
    } 

    if (!openConstructedMethod.ContainsGenericParameters) 
    { 
     // The method contains no generic parameters, 
     // it is by definition already resolved. 
     return openConstructedMethod; 
    } 

    var openConstructedParameterTypes = openConstructedMethod.GetParameters().Select(p => p.ParameterType).ToArray(); 

    if (openConstructedParameterTypes.Length != closedConstructedParameterTypes.Length) 
    { 
     throw new ArgumentOutOfRangeException("closedConstructedParameterTypes"); 
    } 

    var resolvedGenericParameters = new Dictionary<Type, Type>(); 

    for (int i = 0; i < openConstructedParameterTypes.Length; i++) 
    { 
     // Resolve each open-constructed parameter type via the equivalent 
     // closed-constructed parameter type. 

     var openConstructedParameterType = openConstructedParameterTypes[i]; 
     var closedConstructedParameterType = closedConstructedParameterTypes[i]; 

     openConstructedParameterType.MakeGenericTypeVia(closedConstructedParameterType, resolvedGenericParameters); 
    } 

    // Construct the final closed-constructed method from the resolved arguments 

    var openConstructedGenericArguments = openConstructedMethod.GetGenericArguments(); 
    var closedConstructedGenericArguments = openConstructedGenericArguments.Select(openConstructedGenericArgument => 
    { 
     // If the generic argument has been successfully resolved, use it; 
     // otherwise, leave the open-constructe argument in place. 

     if (resolvedGenericParameters.ContainsKey(openConstructedGenericArgument)) 
     { 
      return resolvedGenericParameters[openConstructedGenericArgument]; 
     } 
     else 
     { 
      return openConstructedGenericArgument; 
     } 
    }).ToArray(); 

    return openConstructedMethod.MakeGenericMethod(closedConstructedGenericArguments); 
} 
Các vấn đề liên quan