Sprawdź, czy klasa pochodzi od klasy ogólnej


309

Mam w swoim projekcie klasę ogólną z klasami pochodnymi.

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

public class Test : GenericClass<SomeType>
{
}

Czy jest jakiś sposób, aby dowiedzieć się, czy Typeobiekt pochodzi GenericClass?

t.IsSubclassOf(typeof(GenericClass<>))

nie działa.

Odpowiedzi:


430

Wypróbuj ten kod

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
To słodki fragment kodu, muszę powiedzieć. Implementacja pętli while pozwala uniknąć niepotrzebnego uderzenia wydajności. To eleganckie i piękne rozwiązanie meta-ogólnego pytania.
EnocNRoll - AnandaGopal Pardue

2
Dodałem tę metodę do mojej klasy statycznej ReflectionUtils w moim frameworku, a także dostosowałem ją jako metodę rozszerzenia dla obiektu, definiując toCheck w metodzie jako Type toCheck = obj.GetType (); biorąc pod uwagę „ten obiekt obj” jest pierwszym parametrem.
EnocNRoll - AnandaGopal Pardue

11
Pętla while nie zostanie przerwana, jeśli typ toCheck nie jest klasą (tzn. Interfejsem). Spowoduje to wyjątek NullReferenceException.
JD Courtoy,

2
Zwróci również wartość true, jeśli toCheck jest typem ogólnym, którego szukasz.
oillio

14
Działa to tylko w przypadku dziedziczenia typu betonu ... Przypadek testowy: oczekiwany bool = prawda; bool actual = Program.IsSubclassOfRawGeneric (typeof (IEnumerable <>), typeof (List <string>)); Assert.AreEqual (oczekiwany, rzeczywisty); // nie udaje się
Bobby

90

(Przesłane z powodu ogromnego przepisywania)

Odpowiedź kodu JaredPara jest fantastyczna, ale mam wskazówkę, która sprawiłaby, że niepotrzebne byłoby, gdyby typy ogólne nie były oparte na parametrach typu wartości. Nie wiedziałem, dlaczego operator „jest” nie działa, więc udokumentowałem również wyniki moich eksperymentów do wykorzystania w przyszłości. Popraw tę odpowiedź, aby jeszcze bardziej zwiększyć jej przejrzystość.

WSKAZÓWKA:

Jeśli upewnisz się, że twoja implementacja GenericClass dziedziczy po abstrakcyjnej nieogólnej klasie podstawowej, takiej jak GenericClassBase, możesz zadać to samo pytanie bez żadnych problemów w następujący sposób:

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

IsSubclassOf ()

Moje testy wskazują, że IsSubclassOf () nie działa na typach ogólnych bez parametrów, takich jak

typeof(GenericClass<>)

podczas gdy będzie działać

typeof(GenericClass<SomeType>)

Dlatego poniższy kod będzie działał dla dowolnej pochodnej GenericClass <>, zakładając, że chcesz przetestować na podstawie SomeType:

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

Jedyny raz, kiedy mogę sobie wyobrazić, że chciałbyś przetestować przez GenericClass <>, to scenariusz wtyczki.


Myśli o operatorze „jest”

W czasie projektowania C # nie zezwala na stosowanie bezparametrowych danych generycznych, ponieważ zasadniczo nie są one w tym momencie kompletnym typem CLR. Dlatego musisz zadeklarować zmienne ogólne za pomocą parametrów i dlatego operator „jest” jest tak potężny do pracy z obiektami. Nawiasem mówiąc, operator „jest” również nie może oceniać ogólnych parametrów bez parametrów.

Operator „jest” przetestuje cały łańcuch dziedziczenia, w tym interfejsy.

Tak więc, biorąc pod uwagę instancję dowolnego obiektu, następująca metoda załatwi sprawę:

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

Jest to trochę zbędne, ale pomyślałem, że pójdę naprzód i zwizualizuję to dla wszystkich.

Dany

var t = new Test();

Poniższe wiersze kodu zwrócą wartość true:

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

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

bool test3 = IsTypeof<Test>(t);

Z drugiej strony, jeśli chcesz coś specyficznego dla GenericClass, możesz uczynić to bardziej szczegółowym, tak myślę, tak:

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

Następnie przetestowałbyś w ten sposób:

bool test1 = IsTypeofGenericClass<SomeType>(t);

2
+1 za analizę i testowanie. Również twoja odpowiedź była bardzo przydatna w moim przypadku.
Guillermo Gutiérrez,

3
Należy zauważyć, że kompilator jest całkowicie zadowolony z .IsSubclassOf (typeof (GenericClass <>)), po prostu nie robi tego, co chcesz.
user2880616,

2
Ale co, jeśli SomeType nie jest znany w czasie kompilacji?
Ryan The Leach

Celem całej dyskusji było, kiedy masz tylko Typeprzedmiot.
Jonathan Wood

@JonathanWood, dlatego moja powyższa odpowiedź dotyczy typeofoperatora. Zgodnie z dokumentacją: „Operator typeof służy do uzyskania obiektu System.Type dla typu”.
EnocNRoll - AnandaGopal Pardue

33

Przebadałem niektóre z tych próbek i okazało się, że w niektórych przypadkach ich brakowało. Ta wersja działa z wszystkimi rodzajami ogólnych: typów, interfejsów i ich definicji typów.

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

Oto również testy jednostkowe:

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
Zastanawiam się nad metodą ResolveGenericTypeDefinition. Zmienna „shouldUseGenericType” ma tak naprawdę przypisaną wartość: !parent.IsGenericType || parent.GetGenericTypeDefinition() == parent; zastępujesz więc tę zmienną rozszerzeniem instrukcji if: if (parent.IsGenericType && shouldUseGenericType) i dostajesz to, if (parent.IsGenericType && (!parent.IsGenericType || parent.GetGenericTypeDefinition() == parent)) co następnie redukuje się do if (parent.IsGenericType && parent.GetGenericTypeDefinition() == parent)) parent = parent.GetGenericTypeDefinition();
Michael Blackburn

2
Co wydaje się nic nie robić. Gdyby były to typy wartości, które byłyby podobne do int j = 0; if (j is an int && j == 0) { j=0; } tego, czy moje referencje są równe, a wartość równa się pomieszaniu? Myślałem, że istnieje tylko jedna instancja każdego typu w pamięci, więc dwie zmienne wskazujące ten sam typ w rzeczywistości wskazują na to samo miejsce w pamięci.
Michael Blackburn,

1
@MichaelBlackburn spot on :) zmieniłem to na: return parent.IsGenericType? parent.GetGenericTypeDefinition (): parent;
AaronHS

3
jeśli jest już taki sam jak rodzic, po prostu go zwróć! i dzwoni do GetGenericTypeDef zbyt wiele razy. Wystarczy raz go nazwać
AaronHS

1
Przepraszam, dodam nowy komentarz poniżej.
Menno Deij - van Rijswijk

26

Wydaje mi się, że ta implementacja działa w większej liczbie przypadków (klasa ogólna i interfejs z zainicjowanymi parametrami lub bez nich, niezależnie od liczby elementów potomnych i parametrów):

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

Oto moje 70 76 przypadków testowych:

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

Klasy i interfejsy do testowania:

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
To jedyne rozwiązanie, które działało dla mnie. Nie mogłem znaleźć żadnych innych rozwiązań, które działałyby z klasami o wielu parametrach typu. Dziękuję Ci.
Connor Clark,

1
Naprawdę doceniam twoje zamieszczenie wszystkich tych przypadków testowych. Myślę, że przypadki 68 i 69 powinny być fałszywe zamiast prawdziwych, ponieważ masz ClassB, ClassA po lewej stronie i ClassA, ClassB po prawej stronie.
Grax32,

Masz rację, @Grax. Nie mam teraz czasu na dokonanie korekty, ale zaktualizuję swój post, gdy tylko będzie to możliwe. Myślę, że poprawka musi być w metodzie „VerifyGenericArguments”
Xav987

1
@Grax: Znalazłem trochę czasu na korektę. Dodałem klasę ClassB2, zmieniłem VerifyGenericArguments i dodałem kontrolkę na wywołanie VerifyGenericArguments. Zmodyfikowałem również przypadki 68 i 69 i dodałem 68-2, 68-3, 68-4, 69-2, 69-3 i 69-4.
Xav987,

1
Dziękuję Panu. +1 za działające rozwiązanie ORAZ ogromną liczbę przypadków testowych (przynajmniej dla mnie ogromną).
Eldoïr

10

Kod JaredPara działa, ale tylko dla jednego poziomu dziedziczenia. Aby uzyskać nieograniczone poziomy dziedziczenia, użyj następującego kodu

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
Kod whilew JaredPar obejmuje nieograniczoną liczbę poziomów.
Jay

@jay ... i unika rekurencji.
Marc L.

1
@MarcL. Wykorzystuje to rekurencję ogona, więc kompilator powinien zoptymalizować rekursję.
Darhuuk

10

Oto mała metoda, którą stworzyłem, aby sprawdzić, czy obiekt pochodzi od określonego typu. Działa świetnie dla mnie!

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

Może to być przesada, ale używam metod rozszerzenia takich jak poniżej. Sprawdzają interfejsy oraz podklasy. Może również zwrócić typ, który ma określoną definicję ogólną.

Na przykład na przykład w pytaniu może testować zarówno ogólny interfejs, jak i ogólną klasę. Zwracanego typu można użyć GetGenericArgumentsdo określenia, czy ogólny typ argumentu to „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;
}

Dobry post! Miło jest podzielić obawy na dwie metody.
Wiebe Tijsma

4

Opierając się na doskonałej odpowiedzi powyżej autorstwa fir3rpho3nixx i Davida Schmitta, zmodyfikowałem ich kod i dodałem test ShouldInheritOrImplementTypedGenericInterface (ostatni).

    /// <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

Wszystko to można łatwo zrobić za pomocą linq. Znajdzie to wszystkie typy, które są podklasą ogólnej klasy bazowej GenericBaseType.

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

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

To było jedyne rozwiązanie, które działało dla mnie. Prosty i elegancki. Dziękuję Ci.
Silkfire,

4

Proste rozwiązanie: po prostu utwórz i dodaj drugi, nietypowy interfejs do klasy ogólnej:

public interface IGenericClass
{
}

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

Następnie wystarczy sprawdzić, że w żaden sposób, jak za pomocą is, as, IsAssignableFrom, itd.

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

Oczywiście możliwe tylko wtedy, gdy masz możliwość edycji klasy ogólnej (którą wydaje się mieć OP), ale jest to nieco bardziej eleganckie i czytelne niż przy użyciu kryptycznej metody rozszerzenia.


1
jednak samo sprawdzenie, czy coś jest w rodzaju IGenericClass, nie zagwarantuje ci tego, GenericClassani GenericInterfaceteż nie zostanie rozszerzone lub zaimplementowane. Oznacza to, że twój kompilator również nie pozwoli ci uzyskać dostępu do żadnych członków klasy ogólnej.
B12Toaster

4

Dodano do odpowiedzi @ jaredpar, oto czego używam do sprawdzania interfejsów:

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

Dawny:

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

Dobry dodatek Dałem tobie i jaredparowi głos. Moim jedynym komentarzem jest to, że odwróciłeś pozycję typów (z odpowiedzi jaredpar) z powodu metody rozszerzenia. Usunąłem go jako metodę rozszerzenia i trochę mnie to wyrzuciło. Nie twój problem, ale mój. Chciałem tylko przekazać następną osobę. Dzięki jeszcze raz.
Tony

@ Tony, dzięki za podpowiedź, ale następnym razem Zaktualizuj odpowiedź.
Johnny 5

@vexe, testowanie jest ważne, twoja oryginalna odpowiedź jest zepsuta, działa tylko dlatego, że przetestowałeś ją na interfejsie. Po drugie, marnujemy moc obliczeniową, uruchamiając tę ​​funkcję bez względu na typ.
Johnny 5

2

JaredPar,

Nie działało to dla mnie, jeśli przekażę typeof (type <>) jako toCheck. Oto co zmieniłem.

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

Rozwiązanie JaredPar rzeczywiście działa typeof(type<>)jako as toCheck. Ponadto naprawdę potrzebujesz zerowego czeku, jak w rozwiązaniu JaredPar. Co więcej, nie wiem, co jeszcze osiągniesz, zastępując cur == genericw jego rozwiązaniu cur.IsGenericType && generic.GetGenericTypeDefinition() == cur.GetGenericTypeDefinition()inne niż ograniczenie metody do pracy tylko dla typów ogólnych. Innymi słowy, coś takiego kończy się niepowodzeniem z wyjątkiem:IsSubclassOfRawGeneric(typeof(MyClass), typeof(MyClass<>))
nawfal

2

@EnocNRoll - odpowiedź Anandy Gopal jest interesująca, ale w przypadku, gdy instancja nie zostanie wcześniej utworzona lub chcesz sprawdzić za pomocą definicji typu ogólnego, sugeruję następującą metodę:

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

i używaj go w następujący sposób:

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

Istnieją cztery przypadki, gdy zarówno warunkowe t(do testowania) i dsą typy generyczne i dwa przypadki są objęte t==dktóre są (1) ani tteż nie djest ogólną definicję lub (2), obie z nich są definicje rodzajowe . Pozostałe przypadki są jedną z nich, jest to ogólna definicja, tylko gdy djest to już ogólna definicja, mamy szansę powiedzieć, że a tjest,d ale nie odwrotnie.

Powinien współpracować z dowolnymi klasami lub interfejsami, które chcesz przetestować, i zwraca to tak, jakbyś testował instancję tego typu z isoperatorem.


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

0

Możesz wypróbować to rozszerzenie

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

0

spóźniony do gry w tej sprawie ... ja też mam inną permutację odpowiedzi JarodPara.

oto Type.IsSubClassOf (Type) dzięki uprzejmości reflektora:

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

po tym widzimy, że nie robi nic zbyt cray cray i jest podobny do iteracyjnego podejścia JaredPara. na razie w porządku. oto moja wersja (zastrzeżenie: nie zostało dokładnie przetestowane, więc daj znać, jeśli znajdziesz problemy)

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

w zasadzie jest to tylko metoda rozszerzenia System.Type - zrobiłem to, aby celowo ograniczyć typ „thisType” do konkretnych typów, ponieważ moje bezpośrednie użycie to zapytanie LINQ „gdzie” predykuje względem obiektów Type. jestem pewien, że wszyscy sprytni ludzie mogliby go sprowadzić do wydajnej, uniwersalnej metody statycznej, jeśli zajdzie taka potrzeba :) kod robi kilka rzeczy, kod nie odpowiada

  1. otwórzmy to na ogólne „rozszerzenie” - rozważam dziedziczenie (klasy think) oraz implementację (interfejsy); nazwy metod i parametrów zostały zmienione, aby lepiej to odzwierciedlić
  2. wprowadź zerową walidację (meah)
  3. wejście tego samego typu (klasa nie może się rozszerzyć)
  4. wykonanie zwarcia, jeżeli dany typ jest interfejsem; ponieważ GetInterfaces () zwraca wszystkie zaimplementowane interfejsy (nawet te zaimplementowane w superklasach), możesz po prostu przeglądać tę kolekcję bez konieczności wspinania się na drzewo dziedziczenia

reszta jest w zasadzie taka sama jak kod JaredPara

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.