Periksa apakah suatu kelas berasal dari kelas generik

309

Saya memiliki kelas generik dalam proyek saya dengan kelas turunan.

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

public class Test : GenericClass<SomeType>
{
}

Apakah ada cara untuk mengetahui apakah suatu Typeobjek berasal GenericClass?

t.IsSubclassOf(typeof(GenericClass<>))

tidak bekerja.

bernhardrusch
sumber

Jawaban:

430

Coba kode ini

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;
}
JaredPar
sumber
4
Ini kode yang manis, harus saya katakan. Implementasi while loop menghindari hit kinerja rekursi yang tidak perlu juga. Ini adalah solusi yang elegan dan indah untuk pertanyaan meta-generik.
EnocNRoll - AnandaGopal Pardue
2
Saya telah menambahkan metode ini ke kelas statis ReflectionUtils saya dalam kerangka kerja saya, dan saya juga telah mengadaptasinya sebagai metode ekstensi untuk objek dengan mendefinisikan toCheck dalam metode sebagai Type toCheck = obj.GetType (); diberikan "objek objek ini" adalah parameter pertama.
EnocNRoll - AnandaGopal Pardue
11
Loop sementara tidak akan pecah jika tipe toCheck bukan kelas (yaitu, antarmuka). Ini akan menyebabkan NullReferenceException.
JD Courtoy
2
Ini juga akan mengembalikan true jika toCheck adalah tipe generik yang Anda cari.
oillio
14
Ini hanya berfungsi untuk pewarisan jenis konkret ... Test case: bool expected = true; bool aktual = Program.IsSubclassOfRawGeneric (typeof (IEnumerable <>), typeof (List <string>)); Assert.AreEqual (diharapkan, aktual); // gagal
Bobby
90

(Diposting ulang karena penulisan ulang besar-besaran)

Jawaban kode JaredPar luar biasa, tapi saya punya tip yang akan membuatnya tidak perlu jika tipe generik Anda tidak didasarkan pada parameter tipe nilai. Saya terpaku pada alasan operator "is" tidak bekerja, jadi saya juga mendokumentasikan hasil eksperimen saya untuk referensi di masa mendatang. Harap tingkatkan jawaban ini untuk lebih meningkatkan kejelasannya.

TIP:

Jika Anda memastikan bahwa implementasi GenericClass Anda mewarisi dari kelas dasar non-generik abstrak seperti GenericClassBase, Anda dapat mengajukan pertanyaan yang sama tanpa masalah sama sekali seperti ini:

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

IsSubclassOf ()

Pengujian saya menunjukkan bahwa IsSubclassOf () tidak berfungsi pada tipe generik tanpa parameter seperti

typeof(GenericClass<>)

sedangkan itu akan bekerja dengan

typeof(GenericClass<SomeType>)

Karenanya kode berikut ini akan berfungsi untuk turunan apa pun dari GenericClass <>, dengan asumsi Anda bersedia menguji berdasarkan SomeType:

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

Satu-satunya waktu saya dapat membayangkan bahwa Anda ingin menguji oleh GenericClass <> adalah dalam skenario plug-in framework.


Pikiran tentang operator "adalah"

Pada waktu-desain, C # tidak mengizinkan penggunaan generik tanpa parameter karena pada dasarnya mereka bukan tipe CLR yang lengkap. Oleh karena itu, Anda harus mendeklarasikan variabel generik dengan parameter, dan itulah sebabnya operator "is" begitu kuat untuk bekerja dengan objek. Kebetulan, operator "adalah" juga tidak dapat mengevaluasi tipe generik tanpa parameter.

Operator "is" akan menguji seluruh rantai pewarisan, termasuk antarmuka.

Jadi, dengan memberi instance objek apa pun, metode berikut akan melakukan trik:

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

Ini agak berlebihan, tetapi saya pikir saya akan melanjutkan dan memvisualisasikannya untuk semua orang.

Diberikan

var t = new Test();

Baris kode berikut akan mengembalikan true:

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

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

bool test3 = IsTypeof<Test>(t);

Di sisi lain, jika Anda menginginkan sesuatu yang spesifik untuk GenericClass, Anda dapat membuatnya lebih spesifik, saya kira, seperti ini:

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

Maka Anda akan menguji seperti ini:

bool test1 = IsTypeofGenericClass<SomeType>(t);
EnocNRoll - AnandaGopal Pardue
sumber
2
+1 untuk analisis dan pengujian. Juga, jawaban Anda sangat berguna dalam kasus saya.
Guillermo Gutiérrez
3
Perlu dicatat bahwa kompiler sangat senang dengan .IsSubclassOf (typeof (GenericClass <>)), ia tidak melakukan apa yang Anda inginkan.
user2880616
2
Tetapi bagaimana jika SomeType tidak dikenal pada waktu kompilasi?
Ryan The Leach
Inti dari seluruh diskusi adalah ketika Anda hanya memiliki Typeobjek.
Jonathan Wood
@ JonathanWood itu sebabnya jawaban saya di atas adalah berurusan dengan typeofoperator. Menurut dokumen: "Operator typeof digunakan untuk mendapatkan objek System.Type untuk jenis."
EnocNRoll - AnandaGopal Pardue
33

Saya bekerja melalui beberapa sampel ini dan menemukan mereka kurang dalam beberapa kasus. Versi ini berfungsi dengan semua jenis obat generik: tipe, antarmuka, dan definisi tipe.

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

Berikut adalah unit test juga:

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);
}
fir3rpho3nixx
sumber
2
Saya bingung tentang metode ResolveGenericTypeDefinition. Variabel "shouldUseGenericType" benar-benar diberi nilai: !parent.IsGenericType || parent.GetGenericTypeDefinition() == parent; Jadi, Anda mengganti variabel itu dengan perluasan pernyataan if: if (parent.IsGenericType && shouldUseGenericType) dan Anda dapatkan if (parent.IsGenericType && (!parent.IsGenericType || parent.GetGenericTypeDefinition() == parent)) yang kemudian mengurangi menjadi if (parent.IsGenericType && parent.GetGenericTypeDefinition() == parent)) parent = parent.GetGenericTypeDefinition();
Michael Blackburn
2
Yang sepertinya tidak melakukan apa-apa. Jika ini adalah tipe nilai yang akan mirip dengan int j = 0; if (j is an int && j == 0) { j=0; } Apakah saya mendapatkan referensi saya sama dan nilai sama dengan tercampur? Saya pikir hanya ada satu contoh dari masing-masing jenis dalam memori, jadi dua variabel yang menunjuk ke jenis yang sama sebenarnya menunjuk ke lokasi yang sama di memori.
Michael Blackburn
1
@MichaelBlackburn spot :) saya refactored ini hanya menjadi: return parent.IsGenericType? parent.GetGenericTypeDefinition (): parent;
AaronHS
3
jika sudah sama dengan induknya, kembalikan saja! dan dia terlalu sering memanggil GetGenericTypeDef. Itu hanya perlu dipanggil sekali
AaronHS
1
Maaf, saya akan menambahkan komentar baru di bawah ini.
Menno Deij - van Rijswijk
26

Tampaknya bagi saya bahwa implementasi ini berfungsi dalam lebih banyak kasus (kelas umum dan antarmuka dengan atau tanpa parameter yang diinisiasi, terlepas dari jumlah anak dan parameter):

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

Berikut adalah 70 76 kasus pengujian saya:

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

Kelas dan antarmuka untuk pengujian:

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 { }
Xav987
sumber
4
Ini adalah satu-satunya solusi yang berhasil untuk saya. Saya tidak dapat menemukan solusi lain yang berfungsi dengan kelas yang memiliki beberapa tipe parameter. Terima kasih.
Connor Clark
1
Saya sangat menghargai Anda memposting semua kasus uji ini. Saya memang berpikir bahwa case 68 dan 69 seharusnya salah bukan benar, karena Anda memiliki ClassB, ClassA di sebelah kiri dan ClassA, ClassB di sebelah kanan.
Grax32
Anda benar, @Grax. Saya tidak punya waktu untuk melakukan koreksi sekarang, tetapi saya akan memperbarui posting saya segera setelah selesai. Saya pikir koreksi harus dalam metode "VerifyGenericArguments"
Xav987
1
@ Galx: Saya menemukan waktu untuk melakukan koreksi. Saya menambahkan kelas ClassB2, saya mengubah VerifyGenericArguments, dan saya menambahkan kontrol atas panggilan VerifyGenericArguments. Saya juga memodifikasi kasus 68 dan 69, dan menambahkan 68-2, 68-3, 68-4, 69-2, 69-3 dan 69-4.
Xav987
1
Terima kasih Pak. +1 untuk solusi yang berhasil DAN jumlah uji yang luar biasa (setidaknya bagi saya, luar biasa).
Eldoïr
10

Kode JaredPar berfungsi tetapi hanya untuk satu tingkat warisan. Untuk tingkat warisan yang tidak terbatas, gunakan kode berikut

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);
    }
}
pengguna53564
sumber
4
Kode whiledalam JaredPar mencakup level yang tidak terbatas.
Jay
@jay ... dan menghindari rekursi.
Marc L.
1
@MarcL. Ini menggunakan rekursi ekor, jadi sepele bagi kompiler untuk mengoptimalkan rekursi.
Darhuuk
10

Berikut adalah sedikit metode yang saya buat untuk memeriksa bahwa suatu objek diturunkan dari tipe tertentu. Bekerja sangat bagus untuk saya!

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

sumber
7

Mungkin berlebihan tetapi saya menggunakan metode ekstensi seperti berikut ini. Mereka memeriksa antarmuka serta subkelas. Itu juga dapat mengembalikan tipe yang memiliki definisi generik yang ditentukan.

Misalnya untuk contoh dalam pertanyaan itu dapat menguji terhadap antarmuka generik serta kelas generik. Tipe yang dikembalikan dapat digunakan dengan GetGenericArgumentsuntuk menentukan bahwa tipe argumen generik adalah "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;
}
codybartfast
sumber
Pos bagus! Senang membagi kekhawatiran dalam dua metode.
Wiebe Tijsma
4

Membangun jawaban yang sangat baik di atas oleh fir3rpho3nixx dan David Schmitt, saya telah memodifikasi kode mereka dan menambahkan tes ShouldInheritOrImplementTypedGenericInterface (yang terakhir).

    /// <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>)));
    } 
Menno Deij - van Rijswijk
sumber
4

Ini semua bisa dilakukan dengan mudah dengan LINQ. Ini akan menemukan semua jenis yang merupakan subkelas dari GenericBaseType kelas dasar generik.

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

    IEnumerable<Type> mySubclasses = allTypes.Where(t => t.BaseType != null 
                                                            && t.BaseType.IsGenericType
                                                            && t.BaseType.GetGenericTypeDefinition() == typeof(GenericBaseType<,>));
DVK
sumber
Ini adalah satu-satunya solusi yang berhasil untuk saya. Sederhana dan elegan. Terima kasih.
Silkfire
4

Solusi sederhana: cukup buat dan tambahkan antarmuka non-generik kedua ke kelas generik:

public interface IGenericClass
{
}

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

Kemudian hanya memeriksa bahwa dalam cara apapun yang Anda suka menggunakan is, as, IsAssignableFrom, dll

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

Jelas hanya mungkin jika Anda memiliki kemampuan untuk mengedit kelas generik (yang tampaknya dimiliki OP), tetapi ini sedikit lebih elegan dan mudah dibaca daripada menggunakan metode ekstensi kriptik.

kad81
sumber
1
Namun, hanya memeriksa apakah sesuatu itu tipe IGenericClasstidak akan menjamin Anda bahwa GenericClassatau GenericInterfacebenar-benar diperpanjang atau diterapkan. Ini berarti, kompiler Anda juga tidak akan memungkinkan Anda untuk mengakses anggota kelas generik mana pun.
B12Toaster
4

Ditambahkan ke jawaban @ jaredpar, inilah yang saya gunakan untuk memeriksa antarmuka:

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
kesal
sumber
Tambahan yang bagus Memberi Anda dan jaredpar suara positif. Satu-satunya komentar saya adalah Anda membalik posisi jenis (dari jawaban jaredpar) karena metode ekstensi. Saya menghapusnya sebagai metode ekstensi dan itu melemparkan saya sedikit. Bukan masalahmu tapi milikku. Hanya ingin memberi orang berikutnya kepala. Terima kasih lagi.
Tony
@ Tony, terima kasih atas tipnya tapi lain kali Perbarui jawabannya.
johnny 5
@vexe, pengujian penting. Jawaban awal Anda rusak, hanya berfungsi karena Anda mengujinya pada antarmuka. Kedua, Anda membuang-buang kekuatan pemrosesan dengan menjalankan fungsi ini terlepas dari jenisnya.
johnny 5
2

JaredPar,

Ini tidak berfungsi untuk saya jika saya melewatkan typeof (ketik <>) sebagai toCheck. Inilah yang saya ubah.

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;
}
Cirem
sumber
Solusi JaredPar memang berfungsi typeof(type<>)sebagai toCheck. Anda juga benar-benar membutuhkan cek nol seperti pada solusi JaredPar. Selain itu, saya tidak tahu apa lagi yang Anda capai dengan mengganti cur == genericsolusinya dengan cur.IsGenericType && generic.GetGenericTypeDefinition() == cur.GetGenericTypeDefinition()selain membatasi metode Anda untuk bekerja hanya untuk tipe generik. Dengan kata lain, hal seperti ini gagal dengan pengecualian:IsSubclassOfRawGeneric(typeof(MyClass), typeof(MyClass<>))
nawfal
2

@EnocNRoll - Jawaban Ananda Gopal menarik, tetapi jika sebuah instance tidak dipakai sebelumnya atau Anda ingin memeriksa dengan definisi tipe generik, saya akan menyarankan metode ini:

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

dan gunakan seperti:

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

Ada empat kasus bersyarat ketika keduanya t(akan diuji) dan dmerupakan tipe generik dan dua kasus dicakup oleh t==dyang (1) tidak tjuga dmerupakan definisi generik atau (2) keduanya merupakan definisi generik . Kasus sisanya adalah salah satunya adalah definisi generik, hanya ketika dsudah definisi generik kita memiliki kesempatan untuk mengatakan a tadalahd tetapi tidak sebaliknya.

Ini harus bekerja dengan kelas atau antarmuka yang sewenang-wenang yang ingin Anda uji, dan mengembalikan apa yang seolah-olah Anda uji instance dari tipe itu dengan isoperator.

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

sumber
0

Anda dapat mencoba ekstensi ini

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

terlambat ke permainan pada ini ... saya juga belum permutasi lain dari jawaban JarodPar.

inilah Type.IsSubClassOf (Type) milik reflektor:

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

dari itu, kita melihat bahwa itu tidak melakukan apa-apa juga cray cray dan mirip dengan pendekatan berulang JaredPar. sejauh ini baik. inilah versi saya (penafian: tidak diuji secara menyeluruh, jadi saya tahu jika Anda menemukan masalah)

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

pada dasarnya ini hanyalah metode ekstensi untuk System.Type - saya melakukan ini dengan sengaja membatasi Tipe "thisType" untuk Jenis beton, karena penggunaan langsung saya adalah untuk permintaan LINQ "di mana" predikat terhadap objek Jenis. saya yakin semua orang pintar di luar sana dapat menggedornya menjadi metode statis serba guna yang efisien jika Anda perlu :) kode melakukan beberapa hal kode jawaban tidak

  1. buka saja hingga "ekstensi" umum - saya sedang mempertimbangkan warisan (kelas think) serta implementasi (antarmuka); nama metode dan parameter diubah untuk mencerminkan hal ini dengan lebih baik
  2. masukan null validasi (meah)
  3. input dengan tipe yang sama (kelas tidak dapat memperluas dirinya sendiri)
  4. eksekusi korsleting jika Ketik yang dimaksud adalah antarmuka; karena GetInterfaces () mengembalikan semua antarmuka yang diimplementasikan (bahkan yang diimplementasikan dalam kelas-super), Anda dapat dengan mudah melewati kumpulan itu tanpa harus memanjat pohon warisan

sisanya pada dasarnya sama dengan kode JaredPar

isandburn
sumber