Comment déterminer si un type implémente un type d'interface générique spécifique

226

Supposons les définitions de type suivantes:

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

Comment savoir si le type Fooimplémente l'interface générique IBar<T>lorsque seul le type déformé est disponible?

sduplooy
la source

Réponses:

387

En utilisant la réponse de TcKs, cela peut également être fait avec la requête LINQ suivante:

bool isBar = foo.GetType().GetInterfaces().Any(x =>
  x.IsGenericType &&
  x.GetGenericTypeDefinition() == typeof(IBar<>));
sduplooy
la source
1
C'est une solution très élégante! Les autres que j'ai vus sur SO utilisent des boucles foreach ou des requêtes LINQ plus longues. Gardez à l'esprit que pour l'utiliser, vous devez disposer de .NET Framework 3.5.
Daniel
7
Je vous recommande d'en faire une méthode d'extension à la bit.ly/ccza8B - nettoiera cela très bien!
Brad Heller
1
En fonction de vos besoins, il se peut que vous deviez récidiver sur les interfaces renvoyées.
Sebastian Good
4
Je dirais que cela aurait dû être implémenté dans .net beaucoup mieux ... en tant que noyau ... comme member.Implements (IBar) ou CustomType.Implements (IBar), ou encore mieux, en utilisant un mot clé "is" .. .. J'explore c # et je suis un peu plus déçu par .net en ce moment ...
Sofija
2
ajout mineur: si IBar a plusieurs types génériques, vous devez indiquer ceci comme: typeof(IBar<,,,>)avec des virgules agissant comme des espaces réservés
Rob Von Nesselrode
33

Vous devez remonter dans l'arbre d'héritage et trouver toutes les interfaces pour chaque classe dans l'arbre, et comparer typeof(IBar<>) avec le résultat de l'appel Type.GetGenericTypeDefinition si l'interface est générique. C'est un peu douloureux, certainement.

Voir cette réponse et celles-ci pour plus d'informations et de code.

Jon Skeet
la source
pourquoi ne pas simplement caster sur IBar <SomeClass> et vérifier la valeur null? (Je veux dire casting avec 'as' bien sûr)
Pablo Retyk
5
T est inconnu et ne peut pas être converti en un type spécifique.
sduplooy
@sduplooy: peut-être que je manque quelque chose comment T peut-il être inconnu? il compilerait la classe publique Foo: IFoo <T> {}
Pablo Retyk
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
la source
2
Étant donné que typeof (Foo) renvoie l'objet System.Type (décrivant Foo), l'appel GetType () renvoie toujours le type pour System.Type. Vous devriez changer pour typeof (Foo) .GetInterfaces ()
Michael Meadows
9

En tant qu'extension de méthode d'assistance

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

Exemple d'utilisation:

var testObject = new Dictionary<int, object>();
result = testObject.GetType().Implements(typeof(IDictionary<int, object>)); // true!
GenericProgrammer
la source
2
"IsAssignableFrom" était exactement ce que je cherchais - merci
Jesper
22
Cela ne fonctionne pas pour l'exigence du demandeur de ne pas connaître le paramètre de type générique. À partir de votre exemple testObject.GetType (). Implements (typeof (IDictionary <,>)); retournera faux.
ctusch
@ctusch alors, une solution pour ça?
Tohid
5

J'utilise une version légèrement plus simple de la méthode d'extension @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));
}

Usage:

    if (!featureType.Implements<IFeature>())
        throw new InvalidCastException();
Ben Foster
la source
5
Cela ne fonctionne toujours pas selon l'exigence de la question d'origine qui concerne les interfaces génériques.
nathanchere
4

Vous devez vérifier par rapport à un type construit de l'interface générique.

Vous devrez faire quelque chose comme ceci:

foo is IBar<String>

car IBar<String>représente ce type construit. La raison pour laquelle vous devez le faire est que si Tn'est pas défini dans votre vérification, le compilateur ne sait pas si vous voulez dire IBar<Int32>ou IBar<SomethingElse>.

Andrew Hare
la source
4

Pour faire face au système de type complètement, je pense que vous devez gérer récursion, par exemple IList<T>: ICollection<T>: IEnumerable<T>, sans que vous ne savez pas que , IList<int>finalement , met en œuvre 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 Good
la source
3

Tout d'abord public class Foo : IFoo<T> {}ne compile pas car vous devez spécifier une classe au lieu de T, mais en supposant que vous faites quelque chose commepublic class Foo : IFoo<SomeClass> {}

alors si tu le fais

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

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

Dans le cas où vous vouliez une méthode d'extension qui prendrait en charge les types de base génériques ainsi que les interfaces, j'ai développé la réponse de sduplooy:

    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
la source
1

Méthode pour vérifier si le type hérite ou implémente un type générique:

   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
la source
0

Essayez l'extension suivante.

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

Pour le tester. créer

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 { }

et la méthode d'essai

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
la source
-1

Il ne devrait y avoir aucun problème:

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

Pour obtenir un crédit supplémentaire, vous pouvez intercepter AmbiguousMatchException si vous souhaitez fournir un paramètre de type générique spécifique avec votre requête IBar.

Mindlace
la source
Eh bien, il est généralement préférable d'éviter d'utiliser des littéraux de chaîne lorsque cela est possible. Cette approche rendrait la refactorisation de l'application plus difficile, car renommer l'interface IBar ne changerait pas le littéral de chaîne et l'erreur ne serait détectable qu'au moment de l'exécution.
andyroschy
Bien que je sois généralement d'accord avec le commentaire ci-dessus sur l'utilisation de «cordes magiques», etc., c'est toujours la meilleure approche que j'ai trouvée. Bien assez proche - test du PropertyType.Name égal à "IWwhat`1".
nathanchere
Pourquoi pas ça? bool implementsGeneric = (anObject.Implements(typeof(IBar<>).Name) != null);
Maxime Gélinas