Bagaimana saya bisa menggunakan antarmuka sebagai batasan tipe generik C #?


164

Apakah ada cara untuk mendapatkan deklarasi fungsi berikut?

public bool Foo<T>() where T : interface;

yaitu. di mana T adalah tipe antarmuka (mirip dengan where T : class, dan struct).

Saat ini saya sudah puas dengan:

public bool Foo<T>() where T : IBase;

Di mana IBase didefinisikan sebagai antarmuka kosong yang diwarisi oleh semua antarmuka khusus saya ... Tidak ideal, tetapi harus berfungsi ... Mengapa Anda tidak dapat menentukan bahwa tipe generik harus berupa antarmuka?

Untuk apa nilainya, saya ingin ini karena Foomelakukan refleksi di mana ia membutuhkan jenis antarmuka ... Saya bisa meneruskannya sebagai parameter normal dan melakukan pemeriksaan yang diperlukan dalam fungsi itu sendiri, tetapi ini tampak jauh lebih aman untuk mengetik (dan saya misalkan sedikit lebih banyak pemain, karena semua pemeriksaan dilakukan pada waktu yang bersamaan).


4
Sebenarnya, dease IBase Anda adalah yang terbaik yang pernah saya lihat sejauh ini. Sayangnya, Anda tidak dapat menggunakannya untuk antarmuka yang bukan milik Anda. Semua C # harus lakukan adalah memiliki semua antarmuka mewarisi dari IOjbect sama seperti semua kelas mewarisi dari Objek.
Rhyous

1
Catatan: Ini adalah ide yang agak umum. Antarmuka kosong seperti IBase- digunakan dengan cara ini - disebut antarmuka penanda . Mereka memungkinkan perilaku khusus untuk tipe 'ditandai'.
pius

Jawaban:


132

Yang paling dekat yang dapat Anda lakukan (kecuali untuk pendekatan antarmuka-dasar Anda) adalah " where T : class", yang berarti tipe referensi. Tidak ada sintaks yang berarti "antarmuka apa pun".

Ini (" where T : class") digunakan, misalnya, di WCF untuk membatasi klien untuk kontrak layanan (antarmuka).


7
jawaban yang bagus, tetapi apakah Anda tahu mengapa sintaks ini tidak ada? Sepertinya itu akan menjadi fitur yang bagus untuk dimiliki.
Stephen Holt

@StephenHolt: Saya pikir para pembuat .NET, dalam menentukan batasan apa yang diizinkan, berfokus pada yang akan membuat kelas dan metode umum melakukan hal-hal dengan tipe generik yang mereka tidak bisa lakukan, daripada mencegah mereka digunakan dalam cara yang tidak masuk akal. Bahwa telah dikatakan, suatu interfacekendala pada Tharus memungkinkan perbandingan referensi antara Tdan semua jenis referensi lainnya, karena perbandingan referensi diperbolehkan antara setiap antarmuka dan hampir semua jenis referensi lainnya, dan memungkinkan perbandingan bahkan kasus itu tidak menimbulkan masalah.
supercat

1
@supercat aplikasi lain yang bermanfaat dari kendala hipotetis seperti itu adalah membuat proksi untuk instance tipe dengan aman. Untuk antarmuka dijamin aman, sedangkan untuk kelas tertutup itu akan gagal, sama seperti untuk kelas dengan metode non-virtual.
Ivan Danilov

@IvanDanilov: Ada sejumlah kendala yang mungkin, jika diizinkan, akan berguna memblokir beberapa konstruksi yang tidak masuk akal. Saya setuju kendala untuk "semua jenis antarmuka" akan bagus, tapi saya tidak melihat itu akan memungkinkan untuk apa pun yang tidak dapat dilakukan tanpanya, kecuali untuk generasi kompilasi waktu squawks ketika upaya dilakukan untuk melakukan hal-hal yang bisa gagal saat runtime.
supercat

113

Saya tahu ini agak terlambat tetapi bagi mereka yang tertarik Anda dapat menggunakan pemeriksaan runtime.

typeof(T).IsInterface

11
+1 sebagai satu-satunya jawaban untuk menunjukkan ini. Saya baru saja menambahkan jawaban dengan pendekatan untuk meningkatkan kinerja dengan memeriksa setiap jenis hanya sekali daripada setiap kali metode dipanggil.
phoog

9
Seluruh gagasan generik dalam C # adalah memiliki keamanan waktu kompilasi. Apa yang Anda sarankan dapat dilakukan dengan metode non-generik Foo(Type type).
Jacek Gorgoń

Saya suka cek runtime. Terima kasih.
Tarık Özgün Güner

Juga pada saat dijalankan Anda dapat menggunakan if (new T() is IMyInterface) { }untuk memeriksa apakah antarmuka diimplementasikan oleh kelas T. Mungkin bukan yang paling efisien, tetapi berhasil.
tkerwood

26

Tidak, sebenarnya, jika Anda berpikir classdan structberarti classes dan structs, Anda sedang salah. classberarti semua jenis referensi (mis. termasuk antarmuka juga) dan structsarana semua jenis nilai (misalnya struct, enum).


1
Bukankah itu definisi perbedaan antara kelas dan struct meskipun: bahwa setiap kelas adalah tipe referensi (dan sebaliknya) dan juga untuk tipe stuct / nilai
Matthew Scharley

Matius: Ada lebih banyak tipe nilai daripada C # struct. Enum, misalnya adalah tipe nilai dan where T : structbatasan pencocokan .
Mehrdad Afshari

Perlu dicatat bahwa jenis antarmuka yang digunakan dalam kendala tidak menyiratkan class, tetapi menyatakan lokasi penyimpanan jenis antarmuka benar-benar menyatakan lokasi penyimpanan sebagai referensi kelas yang menerapkan jenis itu.
supercat

4
Untuk menjadi lebih tepat, where T : structbersesuaian dengan NotNullableValueTypeConstraint, jadi itu berarti ia harus menjadi jenis nilai lainnya selain Nullable<>. (Jadi Nullable<>adalah tipe struct yang tidak memenuhi where T : structkendala.)
Jeppe Stig Nielsen

19

Untuk menindaklanjuti jawaban Robert, ini bahkan lebih lambat, tetapi Anda dapat menggunakan kelas pembantu statis untuk melakukan pemeriksaan runtime hanya sekali per jenis:

public bool Foo<T>() where T : class
{
    FooHelper<T>.Foo();
}

private static class FooHelper<TInterface> where TInterface : class
{
    static FooHelper()
    {
        if (!typeof(TInterface).IsInterface)
            throw // ... some exception
    }
    public static void Foo() { /*...*/ }
}

Saya juga mencatat bahwa solusi "seharusnya bekerja" Anda ternyata tidak berhasil. Mempertimbangkan:

public bool Foo<T>() where T : IBase;
public interface IBase { }
public interface IActual : IBase { string S { get; } }
public class Actual : IActual { public string S { get; set; } }

Sekarang tidak ada yang menghentikan Anda dari memanggil Foo demikian:

Foo<Actual>();

The Actualkelas, setelah semua, memenuhi yang IBasekendala.


Sebuah statickonstruktor tidak bisa public, jadi ini harus memberikan kesalahan saat kompilasi. staticKelas Anda juga berisi metode instance, itu juga kesalahan waktu kompilasi.
Jeppe Stig Nielsen

Terlambat terima kasih kepada nawfal karena mengoreksi kesalahan yang dicatat oleh @JeppeStigNielsen
phoog

10

Untuk beberapa waktu sekarang saya telah memikirkan kendala waktu dekat, jadi ini adalah kesempatan yang sempurna untuk meluncurkan konsep.

Ide dasarnya adalah bahwa jika Anda tidak dapat melakukan waktu kompilasi cek, Anda harus melakukannya sedini mungkin, yang pada dasarnya adalah saat aplikasi dimulai. Jika semua cek baik-baik saja, aplikasi akan berjalan; jika cek gagal, aplikasi akan gagal secara instan.

Tingkah laku

Hasil terbaik yang mungkin adalah bahwa program kami tidak dapat dikompilasi jika kendala tidak terpenuhi. Sayangnya itu tidak mungkin dalam implementasi C # saat ini.

Hal terbaik berikutnya adalah bahwa program macet saat dimulai.

Opsi terakhir adalah bahwa program akan macet saat kode dipukul. Ini adalah perilaku default .NET. Bagi saya, ini benar-benar tidak dapat diterima.

Prasyarat

Kita perlu memiliki mekanisme kendala, jadi karena tidak ada yang lebih baik ... mari kita gunakan atribut. Atribut akan hadir di atas batasan umum untuk memeriksa apakah itu cocok dengan kondisi kita. Jika tidak, kami memberikan kesalahan yang buruk.

Ini memungkinkan kami melakukan hal-hal seperti ini dalam kode kami:

public class Clas<[IsInterface] T> where T : class

(Saya telah menyimpannya di where T:classsini, karena saya selalu lebih suka pemeriksaan waktu kompilasi daripada pemeriksaan waktu berjalan)

Jadi, itu hanya menyisakan 1 masalah bagi kami, yaitu memeriksa apakah semua tipe yang kami gunakan cocok dengan batasan tersebut. Seberapa sulitkah itu?

Mari kita hancurkan

Tipe generik selalu baik pada kelas (/ struct / antarmuka) atau pada suatu metode.

Memicu kendala mengharuskan Anda melakukan salah satu dari hal-hal berikut:

  1. Waktu kompilasi, saat menggunakan tipe dalam tipe (pewarisan, batasan generik, anggota kelas)
  2. Waktu kompilasi, saat menggunakan tipe dalam tubuh metode
  3. Run-time, ketika menggunakan refleksi untuk membangun sesuatu berdasarkan kelas dasar generik.
  4. Run-time, ketika menggunakan refleksi untuk membangun sesuatu berdasarkan RTTI.

Pada titik ini, saya ingin menyatakan bahwa Anda harus selalu menghindari melakukan (4) dalam program IMO. Apapun, pemeriksaan ini tidak akan mendukungnya, karena itu secara efektif berarti menyelesaikan masalah penghentian.

Kasus 1: menggunakan tipe

Contoh:

public class TestClass : SomeClass<IMyInterface> { ... } 

Contoh 2:

public class TestClass 
{ 
    SomeClass<IMyInterface> myMember; // or a property, method, etc.
} 

Pada dasarnya ini melibatkan pemindaian semua jenis, warisan, anggota, parameter, dll, dll, dll. Jika suatu jenis adalah tipe generik dan memiliki batasan, kami memeriksa batasannya; jika array, kami memeriksa jenis elemen.

Pada titik ini saya harus menambahkan bahwa ini akan mematahkan fakta bahwa secara default .NET memuat jenis 'malas'. Dengan memindai semua jenis, kami memaksa runtime .NET untuk memuat semuanya. Untuk sebagian besar program ini seharusnya tidak menjadi masalah; tetap saja, jika Anda menggunakan inisialisasi statis dalam kode Anda, Anda mungkin mengalami masalah dengan pendekatan ini ... Yang mengatakan, saya tidak akan menyarankan siapa pun untuk melakukan ini anyways (kecuali untuk hal-hal seperti ini :-), jadi itu seharusnya tidak memberikan Anda banyak masalah.

Kasus 2: menggunakan tipe dalam suatu metode

Contoh:

void Test() {
    new SomeClass<ISomeInterface>();
}

Untuk memeriksa ini kita hanya memiliki 1 opsi: mendekompilasi kelas, periksa semua token anggota yang digunakan dan jika salah satunya adalah tipe generik - periksa argumen.

Kasus 3: Refleksi, konstruksi generik runtime

Contoh:

typeof(CtorTest<>).MakeGenericType(typeof(IMyInterface))

Saya kira secara teori dimungkinkan untuk memeriksa ini dengan trik yang sama seperti kasus (2), tetapi pelaksanaannya jauh lebih sulit (Anda perlu memeriksa jika MakeGenericTypedipanggil dalam beberapa jalur kode). Saya tidak akan membahas detail di sini ...

Kasus 4: Refleksi, runtime RTTI

Contoh:

Type t = Type.GetType("CtorTest`1[IMyInterface]");

Ini adalah skenario terburuk dan seperti yang saya jelaskan sebelumnya umumnya ide yang buruk IMHO. Either way, tidak ada cara praktis untuk mencari tahu ini menggunakan cek.

Menguji banyak

Membuat program yang menguji kasus (1) dan (2) akan menghasilkan sesuatu seperti ini:

[AttributeUsage(AttributeTargets.GenericParameter)]
public class IsInterface : ConstraintAttribute
{
    public override bool Check(Type genericType)
    {
        return genericType.IsInterface;
    }

    public override string ToString()
    {
        return "Generic type is not an interface";
    }
}

public abstract class ConstraintAttribute : Attribute
{
    public ConstraintAttribute() {}

    public abstract bool Check(Type generic);
}

internal class BigEndianByteReader
{
    public BigEndianByteReader(byte[] data)
    {
        this.data = data;
        this.position = 0;
    }

    private byte[] data;
    private int position;

    public int Position
    {
        get { return position; }
    }

    public bool Eof
    {
        get { return position >= data.Length; }
    }

    public sbyte ReadSByte()
    {
        return (sbyte)data[position++];
    }

    public byte ReadByte()
    {
        return (byte)data[position++];
    }

    public int ReadInt16()
    {
        return ((data[position++] | (data[position++] << 8)));
    }

    public ushort ReadUInt16()
    {
        return (ushort)((data[position++] | (data[position++] << 8)));
    }

    public int ReadInt32()
    {
        return (((data[position++] | (data[position++] << 8)) | (data[position++] << 0x10)) | (data[position++] << 0x18));
    }

    public ulong ReadInt64()
    {
        return (ulong)(((data[position++] | (data[position++] << 8)) | (data[position++] << 0x10)) | (data[position++] << 0x18) | 
                        (data[position++] << 0x20) | (data[position++] << 0x28) | (data[position++] << 0x30) | (data[position++] << 0x38));
    }

    public double ReadDouble()
    {
        var result = BitConverter.ToDouble(data, position);
        position += 8;
        return result;
    }

    public float ReadSingle()
    {
        var result = BitConverter.ToSingle(data, position);
        position += 4;
        return result;
    }
}

internal class ILDecompiler
{
    static ILDecompiler()
    {
        // Initialize our cheat tables
        singleByteOpcodes = new OpCode[0x100];
        multiByteOpcodes = new OpCode[0x100];

        FieldInfo[] infoArray1 = typeof(OpCodes).GetFields();
        for (int num1 = 0; num1 < infoArray1.Length; num1++)
        {
            FieldInfo info1 = infoArray1[num1];
            if (info1.FieldType == typeof(OpCode))
            {
                OpCode code1 = (OpCode)info1.GetValue(null);
                ushort num2 = (ushort)code1.Value;
                if (num2 < 0x100)
                {
                    singleByteOpcodes[(int)num2] = code1;
                }
                else
                {
                    if ((num2 & 0xff00) != 0xfe00)
                    {
                        throw new Exception("Invalid opcode: " + num2.ToString());
                    }
                    multiByteOpcodes[num2 & 0xff] = code1;
                }
            }
        }
    }

    private ILDecompiler() { }

    private static OpCode[] singleByteOpcodes;
    private static OpCode[] multiByteOpcodes;

    public static IEnumerable<ILInstruction> Decompile(MethodBase mi, byte[] ildata)
    {
        Module module = mi.Module;

        BigEndianByteReader reader = new BigEndianByteReader(ildata);
        while (!reader.Eof)
        {
            OpCode code = OpCodes.Nop;

            int offset = reader.Position;
            ushort b = reader.ReadByte();
            if (b != 0xfe)
            {
                code = singleByteOpcodes[b];
            }
            else
            {
                b = reader.ReadByte();
                code = multiByteOpcodes[b];
                b |= (ushort)(0xfe00);
            }

            object operand = null;
            switch (code.OperandType)
            {
                case OperandType.InlineBrTarget:
                    operand = reader.ReadInt32() + reader.Position;
                    break;
                case OperandType.InlineField:
                    if (mi is ConstructorInfo)
                    {
                        operand = module.ResolveField(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes);
                    }
                    else
                    {
                        operand = module.ResolveField(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                    }
                    break;
                case OperandType.InlineI:
                    operand = reader.ReadInt32();
                    break;
                case OperandType.InlineI8:
                    operand = reader.ReadInt64();
                    break;
                case OperandType.InlineMethod:
                    try
                    {
                        if (mi is ConstructorInfo)
                        {
                            operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes);
                        }
                        else
                        {
                            operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                        }
                    }
                    catch
                    {
                        operand = null;
                    }
                    break;
                case OperandType.InlineNone:
                    break;
                case OperandType.InlineR:
                    operand = reader.ReadDouble();
                    break;
                case OperandType.InlineSig:
                    operand = module.ResolveSignature(reader.ReadInt32());
                    break;
                case OperandType.InlineString:
                    operand = module.ResolveString(reader.ReadInt32());
                    break;
                case OperandType.InlineSwitch:
                    int count = reader.ReadInt32();
                    int[] targetOffsets = new int[count];
                    for (int i = 0; i < count; ++i)
                    {
                        targetOffsets[i] = reader.ReadInt32();
                    }
                    int pos = reader.Position;
                    for (int i = 0; i < count; ++i)
                    {
                        targetOffsets[i] += pos;
                    }
                    operand = targetOffsets;
                    break;
                case OperandType.InlineTok:
                case OperandType.InlineType:
                    try
                    {
                        if (mi is ConstructorInfo)
                        {
                            operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes);
                        }
                        else
                        {
                            operand = module.ResolveMember(reader.ReadInt32(), mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments());
                        }
                    }
                    catch
                    {
                        operand = null;
                    }
                    break;
                case OperandType.InlineVar:
                    operand = reader.ReadUInt16();
                    break;
                case OperandType.ShortInlineBrTarget:
                    operand = reader.ReadSByte() + reader.Position;
                    break;
                case OperandType.ShortInlineI:
                    operand = reader.ReadSByte();
                    break;
                case OperandType.ShortInlineR:
                    operand = reader.ReadSingle();
                    break;
                case OperandType.ShortInlineVar:
                    operand = reader.ReadByte();
                    break;

                default:
                    throw new Exception("Unknown instruction operand; cannot continue. Operand type: " + code.OperandType);
            }

            yield return new ILInstruction(offset, code, operand);
        }
    }
}

public class ILInstruction
{
    public ILInstruction(int offset, OpCode code, object operand)
    {
        this.Offset = offset;
        this.Code = code;
        this.Operand = operand;
    }

    public int Offset { get; private set; }
    public OpCode Code { get; private set; }
    public object Operand { get; private set; }
}

public class IncorrectConstraintException : Exception
{
    public IncorrectConstraintException(string msg, params object[] arg) : base(string.Format(msg, arg)) { }
}

public class ConstraintFailedException : Exception
{
    public ConstraintFailedException(string msg) : base(msg) { }
    public ConstraintFailedException(string msg, params object[] arg) : base(string.Format(msg, arg)) { }
}

public class NCTChecks
{
    public NCTChecks(Type startpoint)
        : this(startpoint.Assembly)
    { }

    public NCTChecks(params Assembly[] ass)
    {
        foreach (var assembly in ass)
        {
            assemblies.Add(assembly);

            foreach (var type in assembly.GetTypes())
            {
                EnsureType(type);
            }
        }

        while (typesToCheck.Count > 0)
        {
            var t = typesToCheck.Pop();
            GatherTypesFrom(t);

            PerformRuntimeCheck(t);
        }
    }

    private HashSet<Assembly> assemblies = new HashSet<Assembly>();

    private Stack<Type> typesToCheck = new Stack<Type>();
    private HashSet<Type> typesKnown = new HashSet<Type>();

    private void EnsureType(Type t)
    {
        // Don't check for assembly here; we can pass f.ex. System.Lazy<Our.T<MyClass>>
        if (t != null && !t.IsGenericTypeDefinition && typesKnown.Add(t))
        {
            typesToCheck.Push(t);

            if (t.IsGenericType)
            {
                foreach (var par in t.GetGenericArguments())
                {
                    EnsureType(par);
                }
            }

            if (t.IsArray)
            {
                EnsureType(t.GetElementType());
            }
        }

    }

    private void PerformRuntimeCheck(Type t)
    {
        if (t.IsGenericType && !t.IsGenericTypeDefinition)
        {
            // Only check the assemblies we explicitly asked for:
            if (this.assemblies.Contains(t.Assembly))
            {
                // Gather the generics data:
                var def = t.GetGenericTypeDefinition();
                var par = def.GetGenericArguments();
                var args = t.GetGenericArguments();

                // Perform checks:
                for (int i = 0; i < args.Length; ++i)
                {
                    foreach (var check in par[i].GetCustomAttributes(typeof(ConstraintAttribute), true).Cast<ConstraintAttribute>())
                    {
                        if (!check.Check(args[i]))
                        {
                            string error = "Runtime type check failed for type " + t.ToString() + ": " + check.ToString();

                            Debugger.Break();
                            throw new ConstraintFailedException(error);
                        }
                    }
                }
            }
        }
    }

    // Phase 1: all types that are referenced in some way
    private void GatherTypesFrom(Type t)
    {
        EnsureType(t.BaseType);

        foreach (var intf in t.GetInterfaces())
        {
            EnsureType(intf);
        }

        foreach (var nested in t.GetNestedTypes())
        {
            EnsureType(nested);
        }

        var all = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
        foreach (var field in t.GetFields(all))
        {
            EnsureType(field.FieldType);
        }
        foreach (var property in t.GetProperties(all))
        {
            EnsureType(property.PropertyType);
        }
        foreach (var evt in t.GetEvents(all))
        {
            EnsureType(evt.EventHandlerType);
        }
        foreach (var ctor in t.GetConstructors(all))
        {
            foreach (var par in ctor.GetParameters())
            {
                EnsureType(par.ParameterType);
            }

            // Phase 2: all types that are used in a body
            GatherTypesFrom(ctor);
        }
        foreach (var method in t.GetMethods(all))
        {
            if (method.ReturnType != typeof(void))
            {
                EnsureType(method.ReturnType);
            }

            foreach (var par in method.GetParameters())
            {
                EnsureType(par.ParameterType);
            }

            // Phase 2: all types that are used in a body
            GatherTypesFrom(method);
        }
    }

    private void GatherTypesFrom(MethodBase method)
    {
        if (this.assemblies.Contains(method.DeclaringType.Assembly)) // only consider methods we've build ourselves
        {
            MethodBody methodBody = method.GetMethodBody();
            if (methodBody != null)
            {
                // Handle local variables
                foreach (var local in methodBody.LocalVariables)
                {
                    EnsureType(local.LocalType);
                }

                // Handle method body
                var il = methodBody.GetILAsByteArray();
                if (il != null)
                {
                    foreach (var oper in ILDecompiler.Decompile(method, il))
                    {
                        if (oper.Operand is MemberInfo)
                        {
                            foreach (var type in HandleMember((MemberInfo)oper.Operand))
                            {
                                EnsureType(type);
                            }

                        }
                    }
                }
            }
        }
    }

    private static IEnumerable<Type> HandleMember(MemberInfo info)
    {
        // Event, Field, Method, Constructor or Property.
        yield return info.DeclaringType;
        if (info is EventInfo)
        {
            yield return ((EventInfo)info).EventHandlerType;
        }
        else if (info is FieldInfo)
        {
            yield return ((FieldInfo)info).FieldType;
        }
        else if (info is PropertyInfo)
        {
            yield return ((PropertyInfo)info).PropertyType;
        }
        else if (info is ConstructorInfo)
        {
            foreach (var par in ((ConstructorInfo)info).GetParameters())
            {
                yield return par.ParameterType;
            }
        }
        else if (info is MethodInfo)
        {
            foreach (var par in ((MethodInfo)info).GetParameters())
            {
                yield return par.ParameterType;
            }
        }
        else if (info is Type)
        {
            yield return (Type)info;
        }
        else
        {
            throw new NotSupportedException("Incorrect unsupported member type: " + info.GetType().Name);
        }
    }
}

Menggunakan kodenya

Nah, itu bagian yang mudah :-)

// Create something illegal
public class Bar2 : IMyInterface
{
    public void Execute()
    {
        throw new NotImplementedException();
    }
}

// Our fancy check
public class Foo<[IsInterface] T>
{
}

class Program
{
    static Program()
    {
        // Perform all runtime checks
        new NCTChecks(typeof(Program));
    }

    static void Main(string[] args)
    {
        // Normal operation
        Console.WriteLine("Foo");
        Console.ReadLine();
    }
}

8

Anda tidak dapat melakukan ini dalam versi C # dirilis, atau di C # 4.0 mendatang. Ini bukan batasan C #, baik - tidak ada batasan "antarmuka" di CLR itu sendiri.


6

Jika memungkinkan, saya pergi dengan solusi seperti ini. Ini hanya berfungsi jika Anda ingin beberapa antarmuka tertentu (mis. Yang memiliki akses sumber) dilewatkan sebagai parameter umum, bukan apa pun.

  • Saya membiarkan antarmuka saya, yang dipertanyakan, mewarisi antarmuka kosong IInterface.
  • Saya membatasi parameter T generik menjadi IInterface

Dalam sumber, terlihat seperti ini:

  • Antarmuka apa pun yang Anda inginkan untuk dilewatkan sebagai parameter umum:

    public interface IWhatever : IInterface
    {
        // IWhatever specific declarations
    }
  • Iinterface:

    public interface IInterface
    {
        // Nothing in here, keep moving
    }
  • Kelas tempat Anda ingin meletakkan batasan tipe:

    public class WorldPeaceGenerator<T> where T : IInterface
    {
        // Actual world peace generating code
    }

Ini tidak menghasilkan banyak. Anda Ttidak dibatasi untuk antarmuka, itu dibatasi pada apa pun yang mengimplementasikan IInterface- yang jenis apa pun dapat lakukan jika ingin, misalnya struct Foo : IInterfacekarena Anda IInterfacekemungkinan besar publik (jika tidak semua yang menerimanya harus internal).
AnorZaken

Jika Anda tetap mengendalikan semua tipe yang ingin Anda terima, maka Anda dapat menggunakan pembuatan kode untuk membuat semua kelebihan yang sesuai, yang semuanya hanya dialihkan ke metode pribadi umum.
AnorZaken

2

Apa yang telah Anda setujui adalah yang terbaik yang dapat Anda lakukan:

public bool Foo<T>() where T : IBase;

2

Saya mencoba melakukan sesuatu yang serupa dan menggunakan solusi penyelesaian: Saya berpikir tentang operator implisit dan eksplisit pada struktur: Idenya adalah untuk membungkus Type dalam struktur yang dapat dikonversi menjadi Type secara implisit.

Berikut adalah struktur seperti itu:

public struct InterfaceType {private Type _type;

public InterfaceType(Type type)
{
    CheckType(type);
    _type = type;
}

public static explicit operator Type(InterfaceType value)
{
    return value._type;
}

public static implicit operator InterfaceType(Type type)
{
    return new InterfaceType(type);
}

private static void CheckType(Type type)
{
    if (type == null) throw new NullReferenceException("The type cannot be null");
    if (!type.IsInterface) throw new NotSupportedException(string.Format("The given type {0} is not an interface, thus is not supported", type.Name));
}

}

penggunaan dasar:

// OK
InterfaceType type1 = typeof(System.ComponentModel.INotifyPropertyChanged);

// Throws an exception
InterfaceType type2 = typeof(WeakReference);

Anda harus membayangkan mekanisme Anda sendiri dalam hal ini, tetapi sebuah contoh dapat berupa metode yang mengambil InterfaceType dalam parameter, bukan tipe

this.MyMethod(typeof(IMyType)) // works
this.MyMethod(typeof(MyType)) // throws exception

Metode untuk mengesampingkan yang seharusnya mengembalikan tipe antarmuka:

public virtual IEnumerable<InterfaceType> GetInterfaces()

Mungkin ada hubungannya dengan obat generik juga, tetapi saya tidak mencoba

Semoga ini bisa membantu atau memberikan ide :-)


0

Solusi A: Kombinasi kendala ini harus menjamin bahwa itu TInterfaceadalah antarmuka:

class example<TInterface, TStruct>
    where TStruct : struct, TInterface
    where TInterface : class
{ }

Dibutuhkan satu struct TStructsebagai Saksi untuk membuktikan bahwa itu TInterfaceadalah struct.

Anda dapat menggunakan struct tunggal sebagai saksi untuk semua jenis non-generik Anda:

struct InterfaceWitness : IA, IB, IC 
{
    public int DoA() => throw new InvalidOperationException();
    //...
}

Solusi B: Jika Anda tidak ingin menjadikan struct sebagai saksi, Anda dapat membuat antarmuka

interface ISInterface<T>
    where T : ISInterface<T>
{ }

dan gunakan batasan:

class example<TInterface>
    where TInterface : ISInterface<TInterface>
{ }

Implementasi untuk antarmuka:

interface IA :ISInterface<IA>{ }

Ini memecahkan beberapa masalah, tetapi membutuhkan kepercayaan bahwa tidak ada yang mengimplementasikan ISInterface<T>untuk jenis non-antarmuka, tetapi itu cukup sulit dilakukan secara tidak sengaja.


-4

Gunakan kelas abstrak sebagai gantinya. Jadi, Anda akan memiliki sesuatu seperti:

public bool Foo<T>() where T : CBase;

10
Anda tidak selalu dapat mengganti antarmuka dengan kelas abstrak karena C # tidak mendukung banyak pewarisan.
Sam
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.