Cara menentukan apakah suatu tipe mengimplementasikan tipe antarmuka generik tertentu

226

Asumsikan definisi tipe berikut:

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

Bagaimana cara mengetahui apakah tipe Foomengimplementasikan antarmuka generik IBar<T>ketika hanya tipe yang rusak tersedia?

sduplooy
sumber

Jawaban:

387

Dengan menggunakan jawaban dari TcK itu juga dapat dilakukan dengan permintaan LINQ berikut:

bool isBar = foo.GetType().GetInterfaces().Any(x =>
  x.IsGenericType &&
  x.GetGenericTypeDefinition() == typeof(IBar<>));
sduplooy
sumber
1
Ini adalah solusi yang sangat elegan! Yang lain yang saya lihat di SO menggunakan foreach loop atau query LINQ yang lebih panjang. Perlu diingat bahwa untuk menggunakan ini, Anda harus memiliki .NET framework 3.5.
Daniel T.
7
Saya sarankan Anda menjadikan ini metode ekstensi ala bit.ly/ccza8B - akan membersihkan ini dengan cukup baik!
Brad Heller
1
Bergantung pada kebutuhan Anda, Anda mungkin perlu mengulangi antarmuka yang dikembalikan.
Sebastian Good
4
Saya akan mengatakan ini harus diterapkan dalam .net jauh lebih baik ... sebagai inti ... seperti anggota. Penerapan (IBar) atau CustomType. Penerapan (IBar), atau bahkan lebih baik, menggunakan kata kunci "adalah" .. .. Saya sedang menjelajahi c # dan saya sedikit kecewa dengan .net sekarang ...
Sofija
2
tambahan kecil: jika IBar memiliki beberapa jenis generik, Anda perlu menunjukkan ini seperti: typeof(IBar<,,,>)dengan koma yang bertindak seperti placeholder
Rob Von Nesselrode
33

Anda harus naik melalui pohon warisan dan menemukan semua antarmuka untuk setiap kelas di pohon, dan bandingkan typeof(IBar<>)dengan hasil pemanggilan Type.GetGenericTypeDefinition jika antarmuka generik. Ini semua agak menyakitkan, tentu saja.

Lihat jawaban ini dan yang ini untuk info dan kode lebih lanjut.

Jon Skeet
sumber
mengapa tidak langsung ke IBar <SomeClass> dan periksa nol? (Maksud saya casting dengan 'sebagai' saja)
Pablo Retyk
5
T tidak diketahui dan tidak dapat dilemparkan ke jenis tertentu.
sduplooy
@ sduplooy: mungkin saya kehilangan sesuatu bagaimana mungkin T tidak diketahui? itu akan mengkompilasi kelas publik Foo: IFoo <T> {}
Pablo Retyk
25
public interface IFoo<T> : IBar<T> {}
public class Foo : IFoo<Foo> {}

var implementedInterfaces = typeof( Foo ).GetInterfaces();
foreach( var interfaceType in implementedInterfaces ) {
    if ( false == interfaceType.IsGeneric ) { continue; }
    var genericType = interfaceType.GetGenericTypeDefinition();
    if ( genericType == typeof( IFoo<> ) ) {
        // do something !
        break;
    }
}
TcK
sumber
2
Karena typeof (Foo) mengembalikan objek System.Type (menggambarkan Foo), panggilan GetType () akan selalu mengembalikan tipe untuk System.Type. Anda harus mengubah ke typeof (Foo) .GetInterfaces ()
Michael Meadows
9

Sebagai perpanjangan metode penolong

public static bool Implements<I>(this Type type, I @interface) where I : class
{
    if(((@interface as Type)==null) || !(@interface as Type).IsInterface)
        throw new ArgumentException("Only interfaces can be 'implemented'.");

    return (@interface as Type).IsAssignableFrom(type);
}

Contoh penggunaan:

var testObject = new Dictionary<int, object>();
result = testObject.GetType().Implements(typeof(IDictionary<int, object>)); // true!
GenericProgrammer
sumber
2
"IsAssignableFrom" adalah persis apa yang saya cari - terima kasih
Jesper
22
Ini tidak berfungsi karena persyaratan penanya tidak mengetahui parameter tipe generik. Dari contoh Anda testObject.GetType (). Implements (typeof (IDictionary <,>)); akan kembali salah.
ctusch
@ctusch lalu, ada solusi untuk itu?
Tohid
5

Saya menggunakan metode ekstensi @GenericProgrammers versi yang sedikit lebih sederhana:

public static bool Implements<TInterface>(this Type type) where TInterface : class {
    var interfaceType = typeof(TInterface);

    if (!interfaceType.IsInterface)
        throw new InvalidOperationException("Only interfaces can be implemented.");

    return (interfaceType.IsAssignableFrom(type));
}

Pemakaian:

    if (!featureType.Implements<IFeature>())
        throw new InvalidCastException();
Ben Foster
sumber
5
Itu masih tidak berfungsi sesuai persyaratan pertanyaan asli yang untuk antarmuka generik.
nathanchere
4

Anda harus memeriksa jenis antarmuka generik yang dikonstruksi.

Anda harus melakukan sesuatu seperti ini:

foo is IBar<String>

karena IBar<String>mewakili tipe yang dikonstruksi. Alasan Anda harus melakukan ini adalah karena jika Ttidak ditentukan dalam pemeriksaan Anda, kompiler tidak tahu apakah maksud Anda IBar<Int32>atau IBar<SomethingElse>.

Andrew Hare
sumber
4

Untuk mengatasi sistem jenis yang sama sekali, saya pikir Anda perlu menangani rekursi, misalnya IList<T>: ICollection<T>: IEnumerable<T>, tanpa yang Anda tidak akan tahu bahwa IList<int>pada akhirnya mengimplementasikan IEnumerable<>.

    /// <summary>Determines whether a type, like IList&lt;int&gt;, implements an open generic interface, like
    /// IEnumerable&lt;&gt;. Note that this only checks against *interfaces*.</summary>
    /// <param name="candidateType">The type to check.</param>
    /// <param name="openGenericInterfaceType">The open generic type which it may impelement</param>
    /// <returns>Whether the candidate type implements the open interface.</returns>
    public static bool ImplementsOpenGenericInterface(this Type candidateType, Type openGenericInterfaceType)
    {
        Contract.Requires(candidateType != null);
        Contract.Requires(openGenericInterfaceType != null);

        return
            candidateType.Equals(openGenericInterfaceType) ||
            (candidateType.IsGenericType && candidateType.GetGenericTypeDefinition().Equals(openGenericInterfaceType)) ||
            candidateType.GetInterfaces().Any(i => i.IsGenericType && i.ImplementsOpenGenericInterface(openGenericInterfaceType));

    }
Sebastian Good
sumber
3

Pertama-tama public class Foo : IFoo<T> {}tidak dikompilasi karena Anda perlu menentukan kelas, bukan T, tetapi dengan asumsi Anda melakukan sesuatu sepertipublic class Foo : IFoo<SomeClass> {}

maka jika Anda melakukannya

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

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

Jika Anda menginginkan metode ekstensi yang akan mendukung tipe dasar umum serta antarmuka, saya telah memperluas jawaban sduplooy:

    public static bool InheritsFrom(this Type t1, Type t2)
    {
        if (null == t1 || null == t2)
            return false;

        if (null != t1.BaseType &&
            t1.BaseType.IsGenericType &&
            t1.BaseType.GetGenericTypeDefinition() == t2)
        {
            return true;
        }

        if (InheritsFrom(t1.BaseType, t2))
            return true;

        return
            (t2.IsAssignableFrom(t1) && t1 != t2)
            ||
            t1.GetInterfaces().Any(x =>
              x.IsGenericType &&
              x.GetGenericTypeDefinition() == t2);
    }
Philip Pittle
sumber
1

Metode untuk memeriksa apakah tipe tersebut mewarisi atau mengimplementasikan tipe generik:

   public static bool IsTheGenericType(this Type candidateType, Type genericType)
    {
        return
            candidateType != null && genericType != null &&
            (candidateType.IsGenericType && candidateType.GetGenericTypeDefinition() == genericType ||
             candidateType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericType) ||
             candidateType.BaseType != null && candidateType.BaseType.IsTheGenericType(genericType));
    }
Derek Greer
sumber
0

Coba ekstensi berikut.

public static bool Implements(this Type @this, Type @interface)
{
    if (@this == null || @interface == null) return false;
    return @interface.GenericTypeArguments.Length>0
        ? @interface.IsAssignableFrom(@this)
        : @this.GetInterfaces().Any(c => c.Name == @interface.Name);
}

Untuk mengujinya. membuat

public interface IFoo { }
public interface IFoo<T> : IFoo { }
public interface IFoo<T, M> : IFoo<T> { }
public class Foo : IFoo { }
public class Foo<T> : IFoo { }
public class Foo<T, M> : IFoo<T> { }
public class FooInt : IFoo<int> { }
public class FooStringInt : IFoo<string, int> { }
public class Foo2 : Foo { }

dan metode pengujian

public void Test()
{
    Console.WriteLine(typeof(Foo).Implements(typeof(IFoo)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<>)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<int>)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<string>)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<,>)));
    Console.WriteLine(typeof(FooStringInt).Implements(typeof(IFoo<,>)));
    Console.WriteLine(typeof(FooStringInt).Implements(typeof(IFoo<string,int>)));
    Console.WriteLine(typeof(Foo<int,string>).Implements(typeof(IFoo<string>)));
 }
Waleed AK
sumber
-1

Seharusnya tidak ada yang salah berikut ini:

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

Untuk kredit tambahan, Anda dapat menangkap AmbiguousMatchException jika Anda ingin memberikan parameter tipe-generik tertentu dengan kueri IBar Anda.

mindlace
sumber
Yah, biasanya lebih baik untuk menghindari penggunaan string literal jika memungkinkan. Pendekatan ini akan mempersulit refactor aplikasi, karena mengubah nama antarmuka IBar tidak akan mengubah string literal, dan kesalahan hanya akan terdeteksi saat runtime.
andyroschy
Seperti biasanya saya setuju dengan komentar di atas tentang penggunaan 'string sihir' dll, ini masih merupakan pendekatan terbaik yang saya temukan. Cukup dekat - pengujian untuk PropertyType.Name sama dengan "IWhthing`1".
nathanchere
Kenapa tidak ini? bool implementsGeneric = (anObject.Implements(typeof(IBar<>).Name) != null);
Maxime Gélinas