Cómo determinar si un tipo implementa un tipo de interfaz genérico específico

226

Suponga las siguientes definiciones de tipo:

public interface IFoo<T> : IBar<T> {}
public class Foo<T> : IFoo<T> {}

¿Cómo puedo saber si el tipo Fooimplementa la interfaz genérica IBar<T>cuando solo está disponible el tipo destrozado?

sduplooy
fuente

Respuestas:

387

Al usar la respuesta de TcKs, también se puede hacer con la siguiente consulta LINQ:

bool isBar = foo.GetType().GetInterfaces().Any(x =>
  x.IsGenericType &&
  x.GetGenericTypeDefinition() == typeof(IBar<>));
sduplooy
fuente
1
¡Esta es una solución muy elegante! Los otros que he visto en SO usan bucles foreach o consultas LINQ más largas. Sin embargo, tenga en cuenta que para usar esto, debe tener .NET Framework 3.5.
Daniel T.
77
Te recomiendo que hagas de este un método de extensión a la bit.ly/ccza8B , ¡lo limpiarás bastante bien!
Brad Heller
1
Dependiendo de sus necesidades, es posible que necesite recurrir en las interfaces devueltas.
Sebastian Good
44
Diría que esto debería haberse implementado dentro de .net mucho mejor ... como núcleo ... como member.Implements (IBar) o CustomType.Implements (IBar), o incluso mejor, usando una palabra clave "is" .. .. Estoy explorando C # y estoy un poco decepcionado con .net en este momento ...
Sofija
2
adición menor: si IBar tiene múltiples tipos genéricos, debe indicar esto como: typeof(IBar<,,,>)con comas que actúan como marcadores de posición
Rob Von Nesselrode
33

Debe subir por el árbol de herencia y encontrar todas las interfaces para cada clase en el árbol, y comparar typeof(IBar<>)con el resultado de llamar Type.GetGenericTypeDefinition si la interfaz es genérica. Todo es un poco doloroso, ciertamente.

Vea esta respuesta y estas para obtener más información y código.

Jon Skeet
fuente
¿por qué no simplemente enviar a IBar <SomeClass> y verificar si es nulo? (Me refiero al casting con 'como', por supuesto)
Pablo Retyk
55
T es desconocido y no se puede lanzar a un tipo específico.
sduplooy 02 de
@ sduplooy: tal vez me estoy perdiendo algo, ¿cómo puedo ser desconocido? compilaría la clase pública Foo: IFoo <T> {}
Pablo Retyk 02 de
25
public interface IFoo<T> : IBar<T> {}
public class Foo : IFoo<Foo> {}

var implementedInterfaces = typeof( Foo ).GetInterfaces();
foreach( var interfaceType in implementedInterfaces ) {
    if ( false == interfaceType.IsGeneric ) { continue; }
    var genericType = interfaceType.GetGenericTypeDefinition();
    if ( genericType == typeof( IFoo<> ) ) {
        // do something !
        break;
    }
}
TcKs
fuente
2
Como typeof (Foo) devuelve un objeto System.Type (que describe Foo), la llamada GetType () siempre devolverá el tipo de System.Type. Debe cambiar a typeof (Foo). GetInterfaces ()
Michael Meadows
9

Como una extensión de método auxiliar

public static bool Implements<I>(this Type type, I @interface) where I : class
{
    if(((@interface as Type)==null) || !(@interface as Type).IsInterface)
        throw new ArgumentException("Only interfaces can be 'implemented'.");

    return (@interface as Type).IsAssignableFrom(type);
}

Ejemplo de uso:

var testObject = new Dictionary<int, object>();
result = testObject.GetType().Implements(typeof(IDictionary<int, object>)); // true!
Programador genérico
fuente
2
"IsAssignableFrom" era exactamente lo que estaba buscando - gracias
Jesper
22
Esto no funciona para el requerimiento del autor de no conocer el parámetro de tipo genérico. De su ejemplo testObject.GetType (). Implements (typeof (IDictionary <,>)); devolverá falso.
ctusch
@ctusch entonces, ¿alguna solución para eso?
Tohid
5

Estoy usando una versión un poco más simple del método de extensión @GenericProgrammers:

public static bool Implements<TInterface>(this Type type) where TInterface : class {
    var interfaceType = typeof(TInterface);

    if (!interfaceType.IsInterface)
        throw new InvalidOperationException("Only interfaces can be implemented.");

    return (interfaceType.IsAssignableFrom(type));
}

Uso:

    if (!featureType.Implements<IFeature>())
        throw new InvalidCastException();
Ben Foster
fuente
55
Todavía no funciona según el requisito de la pregunta original que es para interfaces genéricas.
nathanchere
4

Debe verificar con un tipo construido de la interfaz genérica.

Tendrás que hacer algo como esto:

foo is IBar<String>

porque IBar<String>representa ese tipo construido. La razón por la que tiene que hacer esto es porque si Tno está definido en su verificación, el compilador no sabe si quiere decir IBar<Int32>o no IBar<SomethingElse>.

Andrew Hare
fuente
4

Para abordar el sistema de tipo completo, creo que es necesario para la recursividad mango, por ejemplo IList<T>: ICollection<T>: IEnumerable<T>, sin la cual no sabría que IList<int>en última instancia los implementos IEnumerable<>.

    /// <summary>Determines whether a type, like IList&lt;int&gt;, implements an open generic interface, like
    /// IEnumerable&lt;&gt;. Note that this only checks against *interfaces*.</summary>
    /// <param name="candidateType">The type to check.</param>
    /// <param name="openGenericInterfaceType">The open generic type which it may impelement</param>
    /// <returns>Whether the candidate type implements the open interface.</returns>
    public static bool ImplementsOpenGenericInterface(this Type candidateType, Type openGenericInterfaceType)
    {
        Contract.Requires(candidateType != null);
        Contract.Requires(openGenericInterfaceType != null);

        return
            candidateType.Equals(openGenericInterfaceType) ||
            (candidateType.IsGenericType && candidateType.GetGenericTypeDefinition().Equals(openGenericInterfaceType)) ||
            candidateType.GetInterfaces().Any(i => i.IsGenericType && i.ImplementsOpenGenericInterface(openGenericInterfaceType));

    }
Sebastian bueno
fuente
3

En primer lugar, public class Foo : IFoo<T> {}no se compila porque necesita especificar una clase en lugar de T, pero suponiendo que haga algo comopublic class Foo : IFoo<SomeClass> {}

entonces si lo haces

Foo f = new Foo();
IBar<SomeClass> b = f as IBar<SomeClass>;

if(b != null)  //derives from IBar<>
    Blabla();
Pablo Retyk
fuente
2

En caso de que quisieras un método de extensión que admitiera tipos básicos genéricos, así como interfaces, he ampliado la respuesta de sduploy:

    public static bool InheritsFrom(this Type t1, Type t2)
    {
        if (null == t1 || null == t2)
            return false;

        if (null != t1.BaseType &&
            t1.BaseType.IsGenericType &&
            t1.BaseType.GetGenericTypeDefinition() == t2)
        {
            return true;
        }

        if (InheritsFrom(t1.BaseType, t2))
            return true;

        return
            (t2.IsAssignableFrom(t1) && t1 != t2)
            ||
            t1.GetInterfaces().Any(x =>
              x.IsGenericType &&
              x.GetGenericTypeDefinition() == t2);
    }
Philip Pittle
fuente
1

Método para verificar si el tipo hereda o implementa un tipo genérico:

   public static bool IsTheGenericType(this Type candidateType, Type genericType)
    {
        return
            candidateType != null && genericType != null &&
            (candidateType.IsGenericType && candidateType.GetGenericTypeDefinition() == genericType ||
             candidateType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericType) ||
             candidateType.BaseType != null && candidateType.BaseType.IsTheGenericType(genericType));
    }
Derek Greer
fuente
0

Prueba la siguiente extensión.

public static bool Implements(this Type @this, Type @interface)
{
    if (@this == null || @interface == null) return false;
    return @interface.GenericTypeArguments.Length>0
        ? @interface.IsAssignableFrom(@this)
        : @this.GetInterfaces().Any(c => c.Name == @interface.Name);
}

Para probarlo. crear

public interface IFoo { }
public interface IFoo<T> : IFoo { }
public interface IFoo<T, M> : IFoo<T> { }
public class Foo : IFoo { }
public class Foo<T> : IFoo { }
public class Foo<T, M> : IFoo<T> { }
public class FooInt : IFoo<int> { }
public class FooStringInt : IFoo<string, int> { }
public class Foo2 : Foo { }

y el método de prueba

public void Test()
{
    Console.WriteLine(typeof(Foo).Implements(typeof(IFoo)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<>)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<int>)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<string>)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<,>)));
    Console.WriteLine(typeof(FooStringInt).Implements(typeof(IFoo<,>)));
    Console.WriteLine(typeof(FooStringInt).Implements(typeof(IFoo<string,int>)));
    Console.WriteLine(typeof(Foo<int,string>).Implements(typeof(IFoo<string>)));
 }
Waleed AK
fuente
-1

No debería haber nada malo en lo siguiente:

bool implementsGeneric = (anObject.Implements("IBar`1") != null);

Para obtener crédito adicional, puede atrapar AmbiguousMatchException si desea proporcionar un parámetro de tipo genérico específico con su consulta IBar.

mentalidad
fuente
Bueno, generalmente es mejor evitar el uso de literales de cadena cuando sea posible. Este enfoque dificultaría la refactorización de la aplicación, ya que cambiar el nombre de la interfaz IBar no cambiaría el literal de la cadena, y el error solo sería detectable en tiempo de ejecución.
andyroschy
Por mucho que estoy de acuerdo con el comentario anterior sobre el uso de 'cadenas mágicas', etc., este sigue siendo el mejor enfoque que he encontrado. Bien cerca: prueba para PropertyType.Name que equivale a "IWhatever`1"
nathanchere
¿Por qué no esto? bool implementsGeneric = (anObject.Implements(typeof(IBar<>).Name) != null);
Maxime Gélinas