2011-02-01 12 views
10

tengo una interfaz similar a la de abajo:Comprobación de si un objeto se encuentra con una restricción de parámetro genérico

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

Y ahora necesito para crear un tipo que representa esta interfaz utilizando la reflexión, por ejemplo

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

Sin embargo, yo en realidad no sé qué tipo 'SomeType' será hasta el tiempo de ejecución, y es posible que el tipo no será válida como un argumento de tipo para la interfaz genérica, por lo MakeGenericType falla.

La pregunta es, ¿cómo puedo comprobar que 'SomeType' es válido para la restricción genérica?

Respuesta

17

Para ser honesto, el enfoque más simple sería simplemente llamar MakeGenericType y coger el ArgumentException que será generada si cualquier tipo de argumento es erróneo (o si usted tiene un número incorrecto de parámetros de tipo).

Mientras que usted podría utilizar Type.GetGenericParameterConstraints para encontrar las limitaciones y luego trabajar en lo que significa cada uno de ellos, que va a ser feo y código de error propenso.

No me gusta por lo general como sugiriendo "solo pruébalo y atrapa", pero en este caso creo que va a ser el enfoque más confiable. De lo contrario, simplemente está reimplantando los cheques que el CLR va a realizar de todos modos, y ¿cuáles son las posibilidades de que los reemplace perfectamente? :)

+0

Justo lo suficiente, gracias Jon. Estaba esperando evitar eso, pero como el rendimiento no es un problema en este código, si dices que es la manera más fácil, ¡es lo suficientemente bueno para mí! – Simon

+0

¿No es este el enfoque del "hombre perezoso"? Muchos estadistas mayores me golpearon en el cráneo en mis primeros años por usar excepciones para verificar cosas en lugar de verificar cosas para evitar una excepción. –

+0

Si puedes mejorar la respuesta de Jon, estaré más que feliz Joel :-) – Simon

4

Esto es posible. Teniendo en cuenta una limitación, se utiliza Type.GenericParameterAttributes y las máscaras

GenericParameterAttributes.ReferenceTypeConstraint 
GenericParameterAttributes.NotNullableValueTypeConstraint 
GenericParameterAttributes.DefaultConstructorConstraint 

para comprobar la presencia de class, struct o new() limitaciones. Puede verificar fácilmente si un tipo determinado cumple estas restricciones (el primero es fácil de implementar (use Type.IsClass), el segundo es un poco complicado pero puede hacerlo utilizando la reflexión, y el tercero tiene un poco de error que las pruebas de su unidad detectarán (Type.GetConstructor(new Type[0]) no devuelve el constructor por defecto para los tipos de valor pero ya se sabe los que tienen un constructor por defecto de todos modos).

Después de esto, se utiliza Type.GetGenericParameterConstraints para obtener las restricciones de tipo de jerarquía (el where T : Base, IInterface como limitaciones) y correr a través de ellos para comprobar que el tipo dado los satisface

+0

Gracias por la respuesta Jason - Estoy tomando el camino más fácil y estoy atrapando la excepción ... el código se ejecuta una vez en el arranque, así que en este caso estoy doblando las reglas habituales. – Simon

+0

Se necesita una pequeña corrección aquí. Es incorrecto cuando dices "el primero es fácil de implementar (usa' Type.IsClass') ". La restricción 'class' no restringe el tipo a una clase. En realidad, lo limita a cualquier tipo de referencia (incluidas las interfaces, etc.) Consulte https://msdn.microsoft.com/en-us/library/d5x73970.aspx. No tengo idea de por qué Microsoft decidió usar la palabra clave 'class' para esta restricción ya que es muy confusa. En su lugar, debe usar 'tipo.IsValueType == false'. – 0b101010

2

Mirando un poco en línea para algo como esto, I found this article por Scott Hansen. Después de leerlo (es corto), y ya delgado rey lo largo de las líneas del método de extensión de la respuesta de @ Jon Skeet, que lanzó esta golosina poco juntos y le dio un pequeño resumen:

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

efectivamente trabajadas por las pocas pruebas que lo ponen a. Se volvió verdadero cuando lo usé en un tipo que DID implementó una interfaz que lo pasé, y falló cuando lo pasé un tipo que no implementó la interfaz. Incluso eliminé la declaración de interfaz del tipo exitoso y lo intenté nuevamente y falló.Lo utilicé como esto:

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

probablemente voy a jugar un rato con él, pero yo podría terminar publicarla en: What are your favorite extension methods for C#? (codeplex.com/extensionoverflow)

+0

+1 Hmm, esto también funciona para mí ... –

0

Aquí está mi aplicación de 3 métodos de extensión:

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

El primero le permite comprobar si un tipo de construcción cerrada coincide con una definición de tipo de construcción abierta. Si es así, el segundo puede deducir todos los argumentos de tipo requeridos para devolver un construido-cerrado a partir de un tipo dado construido-cerrado. Finalmente, el tercer método puede resolver todo esto automáticamente para los métodos.

Tenga en cuenta que estos métodos no fallarán o devolverán falso si pasa otro tipo de construcción abierta como argumento de tipo "construcción cerrada", siempre que este segundo tipo respete todas las restricciones de tipo del tipo inicial de construcción abierta . En su lugar, resolverán la mayor cantidad posible de tipos de información de los tipos especificados. Por lo tanto, si desea asegurarse de que la resolución proporcionó un tipo construido completamente cerrado, debe verificar que el resultado ContainsGenericParameters devuelva falso. Esto coincide con el comportamiento de .NET MakeGenericType o MakeGenericMethod.

También tenga en cuenta que no estoy muy bien informado sobre co y contravariancia, por lo que estas implementaciones pueden no ser correctas en ese sentido.

Ejemplo de uso:

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) 

Implementació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); 
} 
Cuestiones relacionadas