Verifique se uma classe é derivada de uma classe genérica


309

Eu tenho uma classe genérica no meu projeto com classes derivadas.

public class GenericClass<T> : GenericInterface<T>
{
}

public class Test : GenericClass<SomeType>
{
}

Existe alguma maneira de descobrir se um Typeobjeto é derivado GenericClass?

t.IsSubclassOf(typeof(GenericClass<>))

não funciona.

Respostas:


430

Experimente este código

static bool IsSubclassOfRawGeneric(Type generic, Type toCheck) {
    while (toCheck != null && toCheck != typeof(object)) {
        var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
        if (generic == cur) {
            return true;
        }
        toCheck = toCheck.BaseType;
    }
    return false;
}

4
Este é um doce pedaço de código, devo dizer. A implementação do loop while evita também o desempenho desnecessário da recursão. É uma solução elegante e bonita para uma pergunta meta-genérica.
EnocNRoll - AnandaGopal Pardue

2
Eu adicionei esse método à minha classe estática ReflectionUtils na minha estrutura e também o adaptei como um método de extensão para objeto, definindo toCheck dentro do método como Type toCheck = obj.GetType (); dado "este objeto obj" é o primeiro parâmetro.
EnocNRoll - AnandaGopal Pardue

11
O loop while não será interrompido se o tipo toCheck não for uma classe (ou seja, interface). Isso causará uma NullReferenceException.
JD Courtoy

2
Isso também retornará true se toCheck for o tipo genérico que você está procurando.
Oillio 5/05

14
Isso funciona apenas para herança de tipo concreto ... Caso de teste: bool esperado = true; bool real = Program.IsSubclassOfRawGeneric (typeof (IEnumerable <>), typeof (List <string>)); Assert.AreEqual (esperado, real); // falha
Bobby

90

(Republicado devido a uma reescrita massiva)

A resposta de código do JaredPar é fantástica, mas tenho uma dica que tornaria desnecessária se seus tipos genéricos não fossem baseados em parâmetros de tipo de valor. Fiquei indiferente ao motivo pelo qual o operador "is" não funcionaria, por isso também documentei os resultados da minha experimentação para referência futura. Aumente esta resposta para aumentar ainda mais sua clareza.

DICA:

Se você garantir que sua implementação GenericClass herda de uma classe base não genérica abstrata, como GenericClassBase, você pode fazer a mesma pergunta sem nenhum problema como este:

typeof(Test).IsSubclassOf(typeof(GenericClassBase))

IsSubclassOf ()

Meus testes indicam que IsSubclassOf () não funciona em tipos genéricos sem parâmetros, como

typeof(GenericClass<>)

considerando que funcionará com

typeof(GenericClass<SomeType>)

Portanto, o código a seguir funcionará para qualquer derivação de GenericClass <>, supondo que você esteja disposto a testar com base em SomeType:

typeof(Test).IsSubclassOf(typeof(GenericClass<SomeType>))

A única vez em que posso imaginar que você deseja testar com GenericClass <> está em um cenário de estrutura de plug-in.


Pensamentos sobre o operador "is"

No tempo de design, o C # não permite o uso de genéricos sem parâmetros porque eles não são essencialmente um tipo completo de CLR nesse ponto. Portanto, você deve declarar variáveis ​​genéricas com parâmetros, e é por isso que o operador "is" é tão poderoso para trabalhar com objetos. Aliás, o operador "is" também não pode avaliar tipos genéricos sem parâmetros.

O operador "is" testará toda a cadeia de herança, incluindo interfaces.

Portanto, dada uma instância de qualquer objeto, o seguinte método fará o truque:

bool IsTypeof<T>(object t)
{
    return (t is T);
}

Isso é meio redundante, mas imaginei que iria adiante e visualizaria para todos.

Dado

var t = new Test();

As seguintes linhas de código retornariam true:

bool test1 = IsTypeof<GenericInterface<SomeType>>(t);

bool test2 = IsTypeof<GenericClass<SomeType>>(t);

bool test3 = IsTypeof<Test>(t);

Por outro lado, se você quiser algo específico para GenericClass, poderia torná-lo mais específico, suponho, assim:

bool IsTypeofGenericClass<SomeType>(object t)
{
    return (t is GenericClass<SomeType>);
}

Então você testaria assim:

bool test1 = IsTypeofGenericClass<SomeType>(t);

2
+1 para a análise e teste. Além disso, sua resposta foi muito útil no meu caso.
Guillermo Gutiérrez

3
Deve-se notar que o compilador está perfeitamente satisfeito com .IsSubclassOf (typeof (GenericClass <>)), mas não faz o que você deseja.
user2880616

2
Mas e se o SomeType não for conhecido no momento da compilação?
Ryan The Leach

O ponto de toda a discussão foi sobre quando você tem apenas um Typeobjeto.
Jonathan Wood

@ JonathanWood, é por isso que minha resposta acima está relacionada ao typeofoperador. De acordo com a documentação: "O operador typeof é usado para obter o objeto System.Type para um tipo".
EnocNRoll - AnandaGopal Pardue

33

Eu trabalhei com algumas dessas amostras e descobri que elas estavam ausentes em alguns casos. Esta versão funciona com todos os tipos de genéricos: tipos, interfaces e definições de tipos.

public static bool InheritsOrImplements(this Type child, Type parent)
{
    parent = ResolveGenericTypeDefinition(parent);

    var currentChild = child.IsGenericType
                           ? child.GetGenericTypeDefinition()
                           : child;

    while (currentChild != typeof (object))
    {
        if (parent == currentChild || HasAnyInterfaces(parent, currentChild))
            return true;

        currentChild = currentChild.BaseType != null
                       && currentChild.BaseType.IsGenericType
                           ? currentChild.BaseType.GetGenericTypeDefinition()
                           : currentChild.BaseType;

        if (currentChild == null)
            return false;
    }
    return false;
}

private static bool HasAnyInterfaces(Type parent, Type child)
{
    return child.GetInterfaces()
        .Any(childInterface =>
        {
            var currentInterface = childInterface.IsGenericType
                ? childInterface.GetGenericTypeDefinition()
                : childInterface;

            return currentInterface == parent;
        });
}

private static Type ResolveGenericTypeDefinition(Type parent)
{
    var shouldUseGenericType = true;
    if (parent.IsGenericType && parent.GetGenericTypeDefinition() != parent)
        shouldUseGenericType = false;

    if (parent.IsGenericType && shouldUseGenericType)
        parent = parent.GetGenericTypeDefinition();
    return parent;
}

Aqui estão os testes de unidade também:

protected interface IFooInterface
{
}

protected interface IGenericFooInterface<T>
{
}

protected class FooBase
{
}

protected class FooImplementor
    : FooBase, IFooInterface
{
}

protected class GenericFooBase
    : FooImplementor, IGenericFooInterface<object>
{

}

protected class GenericFooImplementor<T>
    : FooImplementor, IGenericFooInterface<T>
{
}


[Test]
public void Should_inherit_or_implement_non_generic_interface()
{
    Assert.That(typeof(FooImplementor)
        .InheritsOrImplements(typeof(IFooInterface)), Is.True);
}

[Test]
public void Should_inherit_or_implement_generic_interface()
{
    Assert.That(typeof(GenericFooBase)
        .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
}

[Test]
public void Should_inherit_or_implement_generic_interface_by_generic_subclass()
{
    Assert.That(typeof(GenericFooImplementor<>)
        .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
}

[Test]
public void Should_inherit_or_implement_generic_interface_by_generic_subclass_not_caring_about_generic_type_parameter()
{
    Assert.That(new GenericFooImplementor<string>().GetType()
        .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
}

[Test]
public void Should_not_inherit_or_implement_generic_interface_by_generic_subclass_not_caring_about_generic_type_parameter()
{
    Assert.That(new GenericFooImplementor<string>().GetType()
        .InheritsOrImplements(typeof(IGenericFooInterface<int>)), Is.False);
}

[Test]
public void Should_inherit_or_implement_non_generic_class()
{
    Assert.That(typeof(FooImplementor)
        .InheritsOrImplements(typeof(FooBase)), Is.True);
}

[Test]
public void Should_inherit_or_implement_any_base_type()
{
    Assert.That(typeof(GenericFooImplementor<>)
        .InheritsOrImplements(typeof(FooBase)), Is.True);
}

2
Estou intrigado com o método ResolveGenericTypeDefinition. A variável "shouldUseGenericType" é realmente atribuída ao valor: !parent.IsGenericType || parent.GetGenericTypeDefinition() == parent; Portanto, você substitui essa variável pela expansão da instrução if: if (parent.IsGenericType && shouldUseGenericType) e obtém o if (parent.IsGenericType && (!parent.IsGenericType || parent.GetGenericTypeDefinition() == parent)) que é reduzido a if (parent.IsGenericType && parent.GetGenericTypeDefinition() == parent)) parent = parent.GetGenericTypeDefinition();
Michael Blackburn

2
O que parece não fazer nada. Se esses tipos de valor fossem semelhantes a int j = 0; if (j is an int && j == 0) { j=0; } Estou obtendo minha referência igual e valor igual misturado? Eu pensei que havia apenas uma instância de cada tipo na memória, então duas variáveis ​​que apontam para o mesmo tipo estão de fato apontando para o mesmo local na memória.
Michael Blackburn

1
@MichaelBlackburn spot on :) Eu refatorado isso para ser apenas: return parent.IsGenericType? parent.GetGenericTypeDefinition (): pai;
AaronHS

3
se já é o mesmo que o pai, basta devolvê-lo! e ele está chamando GetGenericTypeDef muitas vezes. Ele só precisa ser chamado uma vez
AaronHS

1
Desculpe, vou adicionar um novo comentário abaixo.
Menno Deij - van Rijswijk 12/03

26

Parece-me que essa implementação funciona em mais casos (classe genérica e interface com ou sem parâmetros iniciados, independentemente do número de filhos e parâmetros):

public static class ReflexionExtension
{
    public static bool IsSubClassOfGeneric(this Type child, Type parent)
    {
        if (child == parent)
            return false;

        if (child.IsSubclassOf(parent))
            return true;

        var parameters = parent.GetGenericArguments();
        var isParameterLessGeneric = !(parameters != null && parameters.Length > 0 &&
            ((parameters[0].Attributes & TypeAttributes.BeforeFieldInit) == TypeAttributes.BeforeFieldInit));

        while (child != null && child != typeof(object))
        {
            var cur = GetFullTypeDefinition(child);
            if (parent == cur || (isParameterLessGeneric && cur.GetInterfaces().Select(i => GetFullTypeDefinition(i)).Contains(GetFullTypeDefinition(parent))))
                return true;
            else if (!isParameterLessGeneric)
                if (GetFullTypeDefinition(parent) == cur && !cur.IsInterface)
                {
                    if (VerifyGenericArguments(GetFullTypeDefinition(parent), cur))
                        if (VerifyGenericArguments(parent, child))
                            return true;
                }
                else
                    foreach (var item in child.GetInterfaces().Where(i => GetFullTypeDefinition(parent) == GetFullTypeDefinition(i)))
                        if (VerifyGenericArguments(parent, item))
                            return true;

            child = child.BaseType;
        }

        return false;
    }

    private static Type GetFullTypeDefinition(Type type)
    {
        return type.IsGenericType ? type.GetGenericTypeDefinition() : type;
    }

    private static bool VerifyGenericArguments(Type parent, Type child)
    {
        Type[] childArguments = child.GetGenericArguments();
        Type[] parentArguments = parent.GetGenericArguments();
        if (childArguments.Length == parentArguments.Length)
            for (int i = 0; i < childArguments.Length; i++)
                if (childArguments[i].Assembly != parentArguments[i].Assembly || childArguments[i].Name != parentArguments[i].Name || childArguments[i].Namespace != parentArguments[i].Namespace)
                    if (!childArguments[i].IsSubclassOf(parentArguments[i]))
                        return false;

        return true;
    }
}

Aqui estão os meus 70 casos de teste:

[TestMethod]
public void IsSubClassOfGenericTest()
{
    Assert.IsTrue(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(BaseGeneric<>)), " 1");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(WrongBaseGeneric<>)), " 2");
    Assert.IsTrue(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), " 3");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(IWrongBaseGeneric<>)), " 4");
    Assert.IsTrue(typeof(IChildGeneric).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), " 5");
    Assert.IsFalse(typeof(IWrongBaseGeneric<>).IsSubClassOfGeneric(typeof(ChildGeneric2<>)), " 6");
    Assert.IsTrue(typeof(ChildGeneric2<>).IsSubClassOfGeneric(typeof(BaseGeneric<>)), " 7");
    Assert.IsTrue(typeof(ChildGeneric2<Class1>).IsSubClassOfGeneric(typeof(BaseGeneric<>)), " 8");
    Assert.IsTrue(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(BaseGeneric<Class1>)), " 9");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(WrongBaseGeneric<Class1>)), "10");
    Assert.IsTrue(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "11");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(IWrongBaseGeneric<Class1>)), "12");
    Assert.IsTrue(typeof(IChildGeneric).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "13");
    Assert.IsFalse(typeof(BaseGeneric<Class1>).IsSubClassOfGeneric(typeof(ChildGeneric2<Class1>)), "14");
    Assert.IsTrue(typeof(ChildGeneric2<Class1>).IsSubClassOfGeneric(typeof(BaseGeneric<Class1>)), "15");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(ChildGeneric)), "16");
    Assert.IsFalse(typeof(IChildGeneric).IsSubClassOfGeneric(typeof(IChildGeneric)), "17");
    Assert.IsFalse(typeof(IBaseGeneric<>).IsSubClassOfGeneric(typeof(IChildGeneric2<>)), "18");
    Assert.IsTrue(typeof(IChildGeneric2<>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "19");
    Assert.IsTrue(typeof(IChildGeneric2<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "20");
    Assert.IsFalse(typeof(IBaseGeneric<Class1>).IsSubClassOfGeneric(typeof(IChildGeneric2<Class1>)), "21");
    Assert.IsTrue(typeof(IChildGeneric2<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "22");
    Assert.IsFalse(typeof(IBaseGeneric<Class1>).IsSubClassOfGeneric(typeof(BaseGeneric<Class1>)), "23");
    Assert.IsTrue(typeof(BaseGeneric<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "24");
    Assert.IsFalse(typeof(IBaseGeneric<>).IsSubClassOfGeneric(typeof(BaseGeneric<>)), "25");
    Assert.IsTrue(typeof(BaseGeneric<>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "26");
    Assert.IsTrue(typeof(BaseGeneric<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "27");
    Assert.IsFalse(typeof(IBaseGeneric<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "28");
    Assert.IsTrue(typeof(BaseGeneric2<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "29");
    Assert.IsFalse(typeof(IBaseGeneric<>).IsSubClassOfGeneric(typeof(BaseGeneric2<>)), "30");
    Assert.IsTrue(typeof(BaseGeneric2<>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "31");
    Assert.IsTrue(typeof(BaseGeneric2<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "32");
    Assert.IsTrue(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(BaseGenericA<,>)), "33");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(WrongBaseGenericA<,>)), "34");
    Assert.IsTrue(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "35");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(IWrongBaseGenericA<,>)), "36");
    Assert.IsTrue(typeof(IChildGenericA).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "37");
    Assert.IsFalse(typeof(IWrongBaseGenericA<,>).IsSubClassOfGeneric(typeof(ChildGenericA2<,>)), "38");
    Assert.IsTrue(typeof(ChildGenericA2<,>).IsSubClassOfGeneric(typeof(BaseGenericA<,>)), "39");
    Assert.IsTrue(typeof(ChildGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(BaseGenericA<,>)), "40");
    Assert.IsTrue(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(BaseGenericA<ClassA, ClassB>)), "41");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(WrongBaseGenericA<ClassA, ClassB>)), "42");
    Assert.IsTrue(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "43");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(IWrongBaseGenericA<ClassA, ClassB>)), "44");
    Assert.IsTrue(typeof(IChildGenericA).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "45");
    Assert.IsFalse(typeof(BaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(ChildGenericA2<ClassA, ClassB>)), "46");
    Assert.IsTrue(typeof(ChildGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(BaseGenericA<ClassA, ClassB>)), "47");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(ChildGenericA)), "48");
    Assert.IsFalse(typeof(IChildGenericA).IsSubClassOfGeneric(typeof(IChildGenericA)), "49");
    Assert.IsFalse(typeof(IBaseGenericA<,>).IsSubClassOfGeneric(typeof(IChildGenericA2<,>)), "50");
    Assert.IsTrue(typeof(IChildGenericA2<,>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "51");
    Assert.IsTrue(typeof(IChildGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "52");
    Assert.IsFalse(typeof(IBaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IChildGenericA2<ClassA, ClassB>)), "53");
    Assert.IsTrue(typeof(IChildGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "54");
    Assert.IsFalse(typeof(IBaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(BaseGenericA<ClassA, ClassB>)), "55");
    Assert.IsTrue(typeof(BaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "56");
    Assert.IsFalse(typeof(IBaseGenericA<,>).IsSubClassOfGeneric(typeof(BaseGenericA<,>)), "57");
    Assert.IsTrue(typeof(BaseGenericA<,>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "58");
    Assert.IsTrue(typeof(BaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "59");
    Assert.IsFalse(typeof(IBaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "60");
    Assert.IsTrue(typeof(BaseGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "61");
    Assert.IsFalse(typeof(IBaseGenericA<,>).IsSubClassOfGeneric(typeof(BaseGenericA2<,>)), "62");
    Assert.IsTrue(typeof(BaseGenericA2<,>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "63");
    Assert.IsTrue(typeof(BaseGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "64");
    Assert.IsFalse(typeof(BaseGenericA2<ClassB, ClassA>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "65");
    Assert.IsFalse(typeof(BaseGenericA<ClassB, ClassA>).IsSubClassOfGeneric(typeof(ChildGenericA2<ClassA, ClassB>)), "66");
    Assert.IsFalse(typeof(BaseGenericA2<ClassB, ClassA>).IsSubClassOfGeneric(typeof(BaseGenericA<ClassA, ClassB>)), "67");
    Assert.IsTrue(typeof(ChildGenericA3<ClassA, ClassB>).IsSubClassOfGeneric(typeof(BaseGenericB<ClassA, ClassB, ClassC>)), "68");
    Assert.IsTrue(typeof(ChildGenericA4<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "69");
    Assert.IsFalse(typeof(ChildGenericA3<ClassB, ClassA>).IsSubClassOfGeneric(typeof(BaseGenericB<ClassA, ClassB, ClassC>)), "68-2");
    Assert.IsTrue(typeof(ChildGenericA3<ClassA, ClassB2>).IsSubClassOfGeneric(typeof(BaseGenericB<ClassA, ClassB, ClassC>)), "68-3");
    Assert.IsFalse(typeof(ChildGenericA3<ClassB2, ClassA>).IsSubClassOfGeneric(typeof(BaseGenericB<ClassA, ClassB, ClassC>)), "68-4");
    Assert.IsFalse(typeof(ChildGenericA4<ClassB, ClassA>).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "69-2");
    Assert.IsTrue(typeof(ChildGenericA4<ClassA, ClassB2>).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "69-3");
    Assert.IsFalse(typeof(ChildGenericA4<ClassB2, ClassA>).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "69-4");
    Assert.IsFalse(typeof(bool).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "70");
}

Classes e interfaces para teste:

public class Class1 { }
public class BaseGeneric<T> : IBaseGeneric<T> { }
public class BaseGeneric2<T> : IBaseGeneric<T>, IInterfaceBidon { }
public interface IBaseGeneric<T> { }
public class ChildGeneric : BaseGeneric<Class1> { }
public interface IChildGeneric : IBaseGeneric<Class1> { }
public class ChildGeneric2<Class1> : BaseGeneric<Class1> { }
public interface IChildGeneric2<Class1> : IBaseGeneric<Class1> { }

public class WrongBaseGeneric<T> { }
public interface IWrongBaseGeneric<T> { }

public interface IInterfaceBidon { }

public class ClassA { }
public class ClassB { }
public class ClassC { }
public class ClassB2 : ClassB { }
public class BaseGenericA<T, U> : IBaseGenericA<T, U> { }
public class BaseGenericB<T, U, V> { }
public interface IBaseGenericB<ClassA, ClassB, ClassC> { }
public class BaseGenericA2<T, U> : IBaseGenericA<T, U>, IInterfaceBidonA { }
public interface IBaseGenericA<T, U> { }
public class ChildGenericA : BaseGenericA<ClassA, ClassB> { }
public interface IChildGenericA : IBaseGenericA<ClassA, ClassB> { }
public class ChildGenericA2<ClassA, ClassB> : BaseGenericA<ClassA, ClassB> { }
public class ChildGenericA3<ClassA, ClassB> : BaseGenericB<ClassA, ClassB, ClassC> { }
public class ChildGenericA4<ClassA, ClassB> : IBaseGenericB<ClassA, ClassB, ClassC> { }
public interface IChildGenericA2<ClassA, ClassB> : IBaseGenericA<ClassA, ClassB> { }

public class WrongBaseGenericA<T, U> { }
public interface IWrongBaseGenericA<T, U> { }

public interface IInterfaceBidonA { }

4
Esta é a única solução que funcionou para mim. Não consegui encontrar outras soluções que funcionassem com classes com vários parâmetros de tipo. Obrigado.
Connor Clark

1
Eu realmente apreciei sua postagem de todos esses casos de teste. Eu acho que os casos 68 e 69 devem ser falsos em vez de verdadeiros, pois você tem ClassB, ClassA à esquerda e ClassA, ClassB à direita.
precisa saber é o seguinte

Você está certo, @Grax. Não tenho tempo para fazer a correção agora, mas atualizarei minha postagem assim que for feita. Eu acho que a correção deve estar em "VerifyGenericArguments" método
Xav987

1
@ Grax: Encontrei algum tempo para fazer a correção. Adicionei a classe ClassB2, alterei VerifyGenericArguments e adicionei um controle na chamada de VerifyGenericArguments. Também modifiquei os casos 68 e 69 e adicionei 68-2, 68-3, 68-4, 69-2, 69-3 e 69-4.
Xav987

1
Obrigado senhor. +1 na solução de trabalho E na enorme quantidade de casos de teste (tremenda para mim, pelo menos).
Eldoïr

10

O código do JaredPar funciona, mas apenas para um nível de herança. Para níveis ilimitados de herança, use o seguinte código

public bool IsTypeDerivedFromGenericType(Type typeToCheck, Type genericType)
{
    if (typeToCheck == typeof(object))
    {
        return false;
    }
    else if (typeToCheck == null)
    {
        return false;
    }
    else if (typeToCheck.IsGenericType && typeToCheck.GetGenericTypeDefinition() == genericType)
    {
        return true;
    }
    else
    {
        return IsTypeDerivedFromGenericType(typeToCheck.BaseType, genericType);
    }
}

4
O whilecódigo do JaredPar cobre níveis ilimitados.
Jay

@jay ... e evita recursão.
Marc L.

1
@MarcL. Isso usa recursão de cauda, ​​portanto, deve ser trivial para o compilador otimizar a recursão.
Darhuuk 27/09

10

Aqui está um pequeno método que eu criei para verificar se um objeto é derivado de um tipo específico. Funciona muito bem para mim!

internal static bool IsDerivativeOf(this Type t, Type typeToCompare)
{
    if (t == null) throw new NullReferenceException();
    if (t.BaseType == null) return false;

    if (t.BaseType == typeToCompare) return true;
    else return t.BaseType.IsDerivativeOf(typeToCompare);
}

7

Pode ser um exagero, mas eu uso métodos de extensão como os seguintes. Eles verificam interfaces e subclasses. Também pode retornar o tipo que possui a definição genérica especificada.

Por exemplo, no exemplo da pergunta, ele pode testar contra interface genérica e classe genérica. O tipo retornado pode ser usado com GetGenericArgumentspara determinar que o tipo de argumento genérico é "SomeType".

/// <summary>
/// Checks whether this type has the specified definition in its ancestry.
/// </summary>   
public static bool HasGenericDefinition(this Type type, Type definition)
{
    return GetTypeWithGenericDefinition(type, definition) != null;
}

/// <summary>
/// Returns the actual type implementing the specified definition from the
/// ancestry of the type, if available. Else, null.
/// </summary>
public static Type GetTypeWithGenericDefinition(this Type type, Type definition)
{
    if (type == null)
        throw new ArgumentNullException("type");
    if (definition == null)
        throw new ArgumentNullException("definition");
    if (!definition.IsGenericTypeDefinition)
        throw new ArgumentException(
            "The definition needs to be a GenericTypeDefinition", "definition");

    if (definition.IsInterface)
        foreach (var interfaceType in type.GetInterfaces())
            if (interfaceType.IsGenericType
                && interfaceType.GetGenericTypeDefinition() == definition)
                return interfaceType;

    for (Type t = type; t != null; t = t.BaseType)
        if (t.IsGenericType && t.GetGenericTypeDefinition() == definition)
            return t;

    return null;
}

Bom post! É bom dividir as preocupações em dois métodos.
Wiebe Tijsma

4

Com base na excelente resposta acima de fir3rpho3nixx e David Schmitt, modifiquei o código e adicionei o teste ShouldInheritOrImplementTypedGenericInterface (último).

    /// <summary>
    /// Find out if a child type implements or inherits from the parent type.
    /// The parent type can be an interface or a concrete class, generic or non-generic.
    /// </summary>
    /// <param name="child"></param>
    /// <param name="parent"></param>
    /// <returns></returns>
    public static bool InheritsOrImplements(this Type child, Type parent)
    {
        var currentChild = parent.IsGenericTypeDefinition && child.IsGenericType ? child.GetGenericTypeDefinition() : child;

        while (currentChild != typeof(object))
        {
            if (parent == currentChild || HasAnyInterfaces(parent, currentChild))
                return true;

            currentChild = currentChild.BaseType != null && parent.IsGenericTypeDefinition && currentChild.BaseType.IsGenericType
                                ? currentChild.BaseType.GetGenericTypeDefinition()
                                : currentChild.BaseType;

            if (currentChild == null)
                return false;
        }
        return false;
    }

    private static bool HasAnyInterfaces(Type parent, Type child)
    {
        return child.GetInterfaces().Any(childInterface =>
            {
                var currentInterface = parent.IsGenericTypeDefinition && childInterface.IsGenericType
                    ? childInterface.GetGenericTypeDefinition()
                    : childInterface;

                return currentInterface == parent;
            });

    }

    [Test]
    public void ShouldInheritOrImplementNonGenericInterface()
    {
        Assert.That(typeof(FooImplementor)
            .InheritsOrImplements(typeof(IFooInterface)), Is.True);
    }

    [Test]
    public void ShouldInheritOrImplementGenericInterface()
    {
        Assert.That(typeof(GenericFooBase)
            .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
    }

    [Test]
    public void ShouldInheritOrImplementGenericInterfaceByGenericSubclass()
    {
        Assert.That(typeof(GenericFooImplementor<>)
            .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
    }

    [Test]
    public void ShouldInheritOrImplementGenericInterfaceByGenericSubclassNotCaringAboutGenericTypeParameter()
    {
        Assert.That(new GenericFooImplementor<string>().GetType()
            .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
    }

    [Test]
    public void ShouldNotInheritOrImplementGenericInterfaceByGenericSubclassNotCaringAboutGenericTypeParameter()
    {
        Assert.That(new GenericFooImplementor<string>().GetType()
            .InheritsOrImplements(typeof(IGenericFooInterface<int>)), Is.False);
    }

    [Test]
    public void ShouldInheritOrImplementNonGenericClass()
    {
        Assert.That(typeof(FooImplementor)
            .InheritsOrImplements(typeof(FooBase)), Is.True);
    }

    [Test]
    public void ShouldInheritOrImplementAnyBaseType()
    {
        Assert.That(typeof(GenericFooImplementor<>)
            .InheritsOrImplements(typeof(FooBase)), Is.True);
    }

    [Test]
    public void ShouldInheritOrImplementTypedGenericInterface()
    {
        GenericFooImplementor<int> obj = new GenericFooImplementor<int>();
        Type t = obj.GetType();

        Assert.IsTrue(t.InheritsOrImplements(typeof(IGenericFooInterface<int>)));
        Assert.IsFalse(t.InheritsOrImplements(typeof(IGenericFooInterface<String>)));
    } 

4

Tudo isso pode ser feito facilmente com o linq. Isso encontrará todos os tipos que são uma subclasse da classe base genérica GenericBaseType.

    IEnumerable<Type> allTypes = Assembly.GetExecutingAssembly().GetTypes();

    IEnumerable<Type> mySubclasses = allTypes.Where(t => t.BaseType != null 
                                                            && t.BaseType.IsGenericType
                                                            && t.BaseType.GetGenericTypeDefinition() == typeof(GenericBaseType<,>));

Esta foi a única solução que funcionou para mim. Simples e elegante. Obrigado.
Silkfire 19/04

4

Solução simples: basta criar e adicionar uma segunda interface não genérica à classe genérica:

public interface IGenericClass
{
}

public class GenericClass<T> : GenericInterface<T>, IGenericClass
{
}

Em seguida, basta verificar que, em qualquer forma que gosto de usar is, as, IsAssignableFrom, etc.

if (thing is IGenericClass)
{
    // Do work
{

Obviamente, só é possível se você tiver a capacidade de editar a classe genérica (que o OP parece ter), mas é um pouco mais elegante e legível do que usar um método de extensão criptográfica.


1
no entanto, apenas verificar se algo é do tipo IGenericClassnão garante isso GenericClassou GenericInterfaceé realmente estendido ou implementado. Isso significa que seu compilador também não permitirá que você acesse nenhum membro da classe genérica.
B12Toaster

4

Adicionado à resposta do @ jaredpar, eis o que eu uso para verificar as interfaces:

public static bool IsImplementerOfRawGeneric(this Type type, Type toCheck)
{
    if (toCheck.GetTypeInfo().IsClass)
    {
        return false;
    }

    return type.GetInterfaces().Any(interfaceType =>
    {
        var current = interfaceType.GetTypeInfo().IsGenericType ?
                    interfaceType.GetGenericTypeDefinition() : interfaceType;
        return current == toCheck;
    });
}

public static bool IsSubTypeOfRawGeneric(this Type type, Type toCheck)
{
    return type.IsInterface ?
          IsImplementerOfRawGeneric(type, toCheck)
        : IsSubclassOfRawGeneric(type, toCheck);
}

Ex:

Console.WriteLine(typeof(IList<>).IsSubTypeOfRawGeneric(typeof(IList<int>))); // true

Boa adição. Deu a você e a Jaredpar um voto positivo. Meu único comentário é que você inverteu a posição dos tipos (da resposta de jaredpar) por causa do método de extensão. Eu o removi como um método de extensão e isso me assustou um pouco. Não é problema seu, mas meu. Só queria dar a próxima pessoa a atenção. Obrigado novamente.
Tony

@ Tony, obrigado pela dica, mas da próxima vez atualize a resposta.
johnny 5

@ vexe, testar é importante, sua resposta original está quebrada, só funciona porque você a testou em uma interface. Em segundo lugar, você está desperdiçando poder de processamento executando esta função independentemente do tipo.
johnny 5

2

JaredPar,

Isso não funcionou para mim se eu passar typeof (type <>) como toCheck. Aqui está o que eu mudei.

static bool IsSubclassOfRawGeneric(Type generic, Type toCheck) {
    while (toCheck != typeof(object)) {
        var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
          if (cur.IsGenericType && generic.GetGenericTypeDefinition() == cur.GetGenericTypeDefinition()) {
            return true;
        }
        toCheck = toCheck.BaseType;
    }
    return false;
}

A solução da JaredPar de fato funciona para typeof(type<>)como toCheck. Além disso, você realmente precisa da verificação nula, como na solução do JaredPar. Além disso, não sei o que mais você está conseguindo substituindo cur == genericna solução dele por cur.IsGenericType && generic.GetGenericTypeDefinition() == cur.GetGenericTypeDefinition()não restringir seu método para funcionar apenas para tipos genéricos. Em outras palavras, qualquer coisa como isto falhar com uma exceção:IsSubclassOfRawGeneric(typeof(MyClass), typeof(MyClass<>))
Nawfal

2

@EnocNRoll - A resposta de Ananda Gopal é interessante, mas caso uma instância não seja instanciada anteriormente ou você queira verificar com uma definição de tipo genérica, sugiro este método:

public static bool TypeIs(this Type x, Type d) {
    if(null==d) {
        return false;
    }

    for(var c = x; null!=c; c=c.BaseType) {
        var a = c.GetInterfaces();

        for(var i = a.Length; i-->=0;) {
            var t = i<0 ? c : a[i];

            if(t==d||t.IsGenericType&&t.GetGenericTypeDefinition()==d) {
                return true;
            }
        }
    }

    return false;
}

e use-o como:

var b = typeof(char[]).TypeIs(typeof(IList<>)); // true

Existem quatro casos condicionais em que ambos t(a serem testados) e dsão tipos genéricos e dois casos são abrangidos pelos t==dquais (1) não é tnem dé uma definição genérica ou (2) ambos são definições genéricas . Nos demais casos, um deles é uma definição genérica; somente quando djá é uma definição genérica, temos a chance de dizer que a té uma,d mas não vice-versa.

Ele deve funcionar com classes ou interfaces arbitrárias que você deseja testar e retorna como se você testasse uma instância desse tipo com o isoperador.


0
Type _type = myclass.GetType();
PropertyInfo[] _propertyInfos = _type.GetProperties();
Boolean _test = _propertyInfos[0].PropertyType.GetGenericTypeDefinition() 
== typeof(List<>);

0

Você pode tentar esta extensão

    public static bool IsSubClassOfGenericClass(this Type type, Type genericClass,Type t)
    {
        return type.IsSubclassOf(genericClass.MakeGenericType(new[] { t }));
    }

0

atrasado para o jogo sobre isso ... eu também tenho mais uma permutação da resposta de JarodPar.

aqui está Type.IsSubClassOf (Type), cortesia do refletor:

    public virtual bool IsSubclassOf(Type c)
    {
        Type baseType = this;
        if (!(baseType == c))
        {
            while (baseType != null)
            {
                if (baseType == c)
                {
                    return true;
                }
                baseType = baseType.BaseType;
            }
            return false;
        }
        return false;
    }

a partir disso, vemos que não está fazendo nada demais e é semelhante à abordagem iterativa de JaredPar. Por enquanto, tudo bem. aqui está minha versão (isenção de responsabilidade: não foi completamente testado, então deixe-me saber se você encontrar problemas)

    public static bool IsExtension(this Type thisType, Type potentialSuperType)
    {
        //
        // protect ya neck
        //
        if (thisType == null || potentialSuperType == null || thisType == potentialSuperType) return false;

        //
        // don't need to traverse inheritance for interface extension, so check/do these first
        //
        if (potentialSuperType.IsInterface)
        {
            foreach (var interfaceType in thisType.GetInterfaces())
            {
                var tempType = interfaceType.IsGenericType ? interfaceType.GetGenericTypeDefinition() : interfaceType;

                if (tempType == potentialSuperType)
                {
                    return true;
                }
            }
        }

        //
        // do the concrete type checks, iterating up the inheritance chain, as in orignal
        //
        while (thisType != null && thisType != typeof(object))
        {
            var cur = thisType.IsGenericType ? thisType.GetGenericTypeDefinition() : thisType;

            if (potentialSuperType == cur)
            {
                return true;
            }

            thisType = thisType.BaseType;
        }
        return false;
    }

basicamente, este é apenas um método de extensão para System.Type - fiz isso para limitar intencionalmente o tipo "thisType" a tipos concretos, pois meu uso imediato é a consulta LINQ "where" predicada em objetos Type. Tenho certeza de que todos vocês, pessoas inteligentes por aí, podem transformá-lo em um método estático eficiente e para todos os fins, se precisar :) o código faz algumas coisas que o código da resposta não

  1. open it up to general "extension" - estou considerando herança (think classes) e implementação (interfaces); nomes de métodos e parâmetros são alterados para refletir melhor isso
  2. validação nula de entrada (meah)
  3. entrada do mesmo tipo (uma classe não pode se estender)
  4. execução em curto-circuito se Type in question for uma interface; Como GetInterfaces () retorna todas as interfaces implementadas (mesmo as implementadas em superclasses), você pode simplesmente percorrer essa coleção sem precisar subir na árvore de herança

o resto é basicamente o mesmo que o código de JaredPar

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.