Перевірте, чи походить клас із загального класу


309

У мене в проекті є загальний клас із похідними класами.

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

public class Test : GenericClass<SomeType>
{
}

Чи є спосіб дізнатись, Typeвід чого похідний об'єкт GenericClass?

t.IsSubclassOf(typeof(GenericClass<>))

не працює.

Відповіді:


430

Спробуйте цей код

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
Це солодкий шматок коду, я мушу сказати. Реалізація циклу у той час уникає і непотрібної рекурсійної продуктивності. Це елегантне і красиве рішення мета-загального питання.
EnocNRoll - AnandaGopal Pardue

2
Я додав цей метод до свого статичного класу ReflectionUtils у своїй рамці, а також адаптував його як метод розширення для об'єкта, визначивши toCheck в методі як Type toCheck = obj.GetType (); заданий "цей об'єкт obj" є першим параметром.
EnocNRoll - AnandaGopal Pardue

11
Цикл while не порушиться, якщо тип toCheck не є класом (тобто інтерфейсом). Це спричинить NullReferenceException.
JD Courtoy

2
Це також поверне істину, якщо toCheck - це загальний тип, який ви шукаєте.
oillio

14
Це працює лише для успадкування конкретного типу ... Тестовий випадок: очікуваний bool = вірно; bool фактично = Program.IsSubclassOfRawGeneric (typeof (IEnumerable <>), typeof (Список <string>)); Assert.AreEqual (очікуваний, фактичний); // не вдається
Боббі

90

(Повідомлення через масове перезапис)

Відповідь на код JaredPar - це фантастично, але у мене є підказка, яка зробить непотрібною, якщо ваші загальні типи не базуються на параметрах типу значень. Мене повісили на те, чому оператор "є" не працюватиме, тому я також задокументував результати мого експерименту для подальшого використання. Будь ласка, удосконаліть цю відповідь, щоб ще більше підвищити її чіткість.

ПОРАДА:

Якщо ви впевнені, що ваша реалізація GenericClass успадковується від абстрактного негенеричного базового класу, такого як GenericClassBase, ви можете задати те саме питання без проблем, як це:

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

IsSubclassOf ()

Моє тестування показує, що IsSubclassOf () не працює на загальних параметрах, таких як, наприклад,

typeof(GenericClass<>)

тоді як це буде працювати

typeof(GenericClass<SomeType>)

Отже, наступний код буде працювати для будь-якого виведення GenericClass <>, припускаючи, що ви готові протестувати на основі SomeType:

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

Єдиний раз, коли я можу собі уявити, що ви хотіли б протестувати за допомогою GenericClass <>, це сценарій підключення модулів.


Думки про оператора "є"

На час проектування C # не дозволяє використовувати безпараметричні дженерики, оскільки вони по суті не є повноцінним типом CLR в той момент. Тому ви повинні оголосити загальні змінні з параметрами, і саме тому оператор "є" настільки потужний для роботи з об'єктами. Між іншим, оператор "є" також не може оцінити параметри загальних типів.

Оператор "є" перевірить усю ланцюжок спадкування, включаючи інтерфейси.

Отже, з огляду на примірник будь-якого об’єкта, фокус виконає наступний метод:

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

Це щось надлишкове, але я подумав, що буду йти вперед і візуалізувати це для всіх.

Дано

var t = new Test();

Наступні рядки коду повернуть істину:

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

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

bool test3 = IsTypeof<Test>(t);

З іншого боку, якщо ви хочете чогось конкретного для GenericClass, ви, мабуть, могли би зробити це більш конкретним:

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

Тоді ви б протестували так:

bool test1 = IsTypeofGenericClass<SomeType>(t);

2
+1 для аналізу та тестування. Також ваша відповідь була дуже корисною в моєму випадку.
Гільєрмо Гутьеррес

3
Слід зазначити, що компілятор абсолютно задоволений .IsSubclassOf (typeof (GenericClass <>)), він просто не робить те, що ви могли б побажати.
користувач2880616

2
Але що робити, якщо SomeType не відомий під час компіляції?
Райан Ліч

Суть усієї дискусії полягала в тому, коли у вас є лише Typeоб’єкт.
Джонатан Вуд

@JonathanWood, тому моя відповідь вище стосується typeofоператора. Згідно з документами: "Оператор typeof використовується для отримання об'єкта System.Type для типу."
EnocNRoll - AnandaGopal Pardue

33

Я розробив деякі з цих зразків і виявив, що їх у деяких випадках бракує. Ця версія працює з усіма видами дженериків: типи, інтерфейси та їх визначення.

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

Ось також одиничні тести:

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
Мене спантеличує метод ResolveGenericTypeDefinition. Змінній "shouldUseGenericType" дійсно присвоєно значення: !parent.IsGenericType || parent.GetGenericTypeDefinition() == parent; Отже, ви замінюєте цю змінну розширенням оператора if: if (parent.IsGenericType && shouldUseGenericType) і ви отримуєте, if (parent.IsGenericType && (!parent.IsGenericType || parent.GetGenericTypeDefinition() == parent)) яке потім зводиться до if (parent.IsGenericType && parent.GetGenericTypeDefinition() == parent)) parent = parent.GetGenericTypeDefinition();
Майкл Блекберн

2
Що, здавалося б, нічого не робить. Якби це типи значень, які були б схожими на int j = 0; if (j is an int && j == 0) { j=0; } Am Я отримував моє довідкове рівне і значення дорівнює? Я думав, що у пам'яті є лише один екземпляр кожного типу, тому дві змінні, які вказують на один і той же тип, насправді вказують на одне місце в пам'яті.
Майкл Блекберн

1
@ MichaelBlackburn місце на :) Я відремонтував це, щоб це було просто: повернути батьків.IsGenericType? parent.GetGenericTypeDefinition (): батьківський;
AaronHS

3
якщо його вже те саме, що і батьків, просто поверніть його! і він дзвонить GetGenericTypeDef занадто багато разів. Її просто потрібно викликати один раз
AaronHS

1
Вибачте, я додам новий коментар нижче.
Menno Deij - van Rijswijk

26

Мені здається, що ця реалізація працює в більшості випадків (загальний клас та інтерфейс із ініційованими параметрами або без них, незалежно від кількості дочірніх та параметрів):

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

Ось мої 70 76 тестових прикладів:

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

Класи та інтерфейси для тестування:

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
Це єдине рішення, яке працювало на мене. Я не міг знайти жодного іншого рішення, яке працювало з класами, які мають кілька параметрів типу. Дякую.
Коннор Кларк

1
Я дуже вдячний за те, що ви розмістили всі ці тестові випадки. Я думаю, що випадки 68 і 69 повинні бути помилковими, а не істинними, оскільки у вас є ClassB, ClassA зліва та ClassA, ClassB праворуч.
Grax32

Ти маєш рацію, @Grax. Я не маю часу вносити виправлення зараз, але я оновлю свою посаду, як тільки це буде зроблено. Я думаю, що виправлення має бути в методі "VerifyGenericArguments"
Xav987

1
@Grax: Я знайшов деякий час, щоб зробити виправлення. Я додав клас ClassB2, змінив VerifyGenericArguments і додав елемент керування за викликом VerifyGenericArguments. Я також змінив випадки 68 і 69 і додав 68-2, 68-3, 68-4, 69-2, 69-3 і 69-4.
Xav987

1
Дякую вам сер. +1 для робочого рішення ТА величезна кількість тестових випадків (принаймні для мене надзвичайна).
Eldoïr

10

Код JaredPar працює, але лише для одного рівня успадкування. Для необмеженого рівня спадкування використовуйте наступний код

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
Код whileJaredPar охоплює необмежений рівень.
Джей

@jay ... і уникає рекурсії.
Марк Л.

1
@MarcL. Для цього використовується хвостова рекурсія, тому компілятор повинен бути тривіальним для оптимізації рекурсії.
Дархук

10

Ось невеликий метод, який я створив для перевірки того, що об’єкт походить від певного типу. Для мене чудово працює!

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

Це може бути зайвим, але я використовую такі методи розширення, як наступний. Вони перевіряють інтерфейси, а також підкласи. Він також може повернути тип, який має вказане загальне визначення.

Наприклад, для прикладу у запитанні він може перевірити як загальний інтерфейс, так і загальний клас. Повернутий тип можна використовувати, GetGenericArgumentsщоб визначити, що загальним типом аргументу є "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;
}

Гарний пост! Приємно розділити проблеми двома методами.
Wiebe Tijsma

4

Спираючись на чудову відповідь, описану вище, від fir3rpho3nixx та Девіда Шмітта, я змінив їхній код і додав тест ShouldInheritOrImplementTypedGenericInterface (останній).

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

Це все можна легко зробити за допомогою linq. Тут знайдуться будь-які типи, що є підкласом загального базового класу GenericBaseType.

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

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

Це було єдине рішення, яке працювало на мене. Простий і елегантний. Дякую.
шовковий вогонь

4

Просте рішення: просто створіть і додайте другий, не загальний інтерфейс до загального класу:

public interface IGenericClass
{
}

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

Тоді просто перевірити , що в будь-якому випадку ви хочете , використовуючи is, as, IsAssignableFromі т.д.

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

Очевидно, це можливо лише в тому випадку, якщо у вас є можливість редагувати загальний клас (який, здається, має ОП), але це трохи більш елегантно і читабельно, ніж використання методу криптичного розширення.


1
однак, лише перевірка того, чи є щось типу IGenericClass, не гарантує вам того, GenericClassчи GenericInterfaceдійсно розширено чи впроваджено. Це означає, що ваш компілятор також не дозволить вам отримати доступ до будь-яких членів загального класу.
B12Toaster

4

Додано до відповіді @ jaredpar, ось що я використовую для перевірки інтерфейсів:

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

Наприклад:

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

Гарне доповнення. Дав і тобі, і джаредпару нагороду. Мій єдиний коментар - ви змінили положення типів (з відповіді jaredpar) через метод розширення. Я видалив його як метод розширення, і це мене трохи відкинуло. Не твоя проблема, а моя. Просто хотів дати голову наступній людині. Знову дякую.
Тоні

@Tony, дякую за пораду, але наступного разу оновіть відповідь.
johnny 5

@vexe, тестування важливо, ваша оригінальна відповідь зламана, вона працює лише тому, що ви її тестували на інтерфейсі. По-друге, ви витрачаєте процесорну потужність, виконуючи цю функцію незалежно від типу.
johnny 5

2

JaredPar,

Це не спрацювало для мене, якщо я передаю typeof (type <>) як toCheck. Ось що я змінив.

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

Рішення JaredPar справді працює , typeof(type<>)як toCheck. Також вам дійсно потрібна перевірка нуля, як у рішенні JaredPar. Крім того, я не знаю, чого ще ви досягаєте, замінивши cur == genericйого рішення cur.IsGenericType && generic.GetGenericTypeDefinition() == cur.GetGenericTypeDefinition()іншим, ніж обмежуючи свій метод працювати лише для загальних типів. Іншими словами, нічого подібного не вдається за винятком:IsSubclassOfRawGeneric(typeof(MyClass), typeof(MyClass<>))
nawfal

2

@EnocNRoll - відповідь Ананда Гопала цікава, але у випадку, якщо екземпляр заздалегідь не буде ініційований або ви хочете перевірити визначення загального типу, я б запропонував цей метод:

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

і використовувати його так:

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

Існують чотири умовні випадки, коли обидва t(підлягають випробуванню) і dє родовими типами, і два випадки охоплені, t==dякі не є (1), а також tне dє загальним визначенням, або (2) обидва є загальними визначеннями . Решта випадків - один із них - це загальне визначення, лише тоді, коли dце вже є загальним визначенням, ми маємо шанс сказати, що tце а,d а не навпаки.

Він повинен працювати з довільними класами або інтерфейсами, які ви хочете перевірити, і повертає те, що, як якщо б ви тестуєте екземпляр цього типу з isоператором.


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

0

Ви можете спробувати це розширення

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

0

пізно до гри на цьому ... У мене теж є ще одна перестановка відповіді JarodPar.

ось Type.IsSubClassOf (Type) люб’язність відбивача:

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

з цього ми бачимо, що це не робить щось надто кремезний cray і схожий на ітеративний підхід JaredPar. все йде нормально. ось моя версія (відмова від відповідальності: не ретельно перевірена, тому лемме знаю, якщо ви знайдете проблеми)

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

в основному це лише метод розширення для System.Type - я зробив це, щоб навмисно обмежити тип "thisType" конкретними типами, так як моє негайне використання - це запит LINQ "де" призначає проти об'єктів Type. Я впевнений, що всі ви розумні люди там можете вдарити його до ефективного, універсального статичного методу, якщо вам потрібно: :) Код робить кілька речей, код яких не відповідає

  1. відкрити це до загального "розширення" - я розглядаю спадкування (мислительні класи), а також імплементацію (інтерфейси); імена методів і параметрів змінені, щоб краще відобразити це
  2. введення нульової перевірки (meah)
  3. введення одного типу (клас не може поширювати себе)
  4. виконання короткого замикання, якщо відповідний Тип інтерфейсу; оскільки GetInterfaces () повертає всі реалізовані інтерфейси (навіть ті, що реалізовані в суперкласах), ви можете просто провести цикл через цю колекцію, не потребуючи підніматися до дерева спадкування

решта в основному те саме, що і код JaredPar

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.