Wie kann ich die Schnittstelle als generische C # -Einschränkung verwenden?


163

Gibt es eine Möglichkeit, die folgende Funktionsdeklaration zu erhalten?

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

dh. Dabei ist T ein Schnittstellentyp (ähnlich where T : classund struct).

Derzeit habe ich mich entschieden für:

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

Wo IBase als leere Schnittstelle definiert ist, die von allen meinen benutzerdefinierten Schnittstellen geerbt wird ... Nicht ideal, aber es sollte funktionieren ... Warum können Sie nicht definieren, dass ein generischer Typ eine Schnittstelle sein muss?

Für das, was es wert ist, möchte ich dies, weil Fooes Reflexion macht, wo es einen Schnittstellentyp benötigt ... Ich könnte es als normalen Parameter übergeben und die notwendige Überprüfung in der Funktion selbst durchführen, aber dies schien viel typsicherer zu sein (und ich Nehmen wir an, etwas performanter, da alle Überprüfungen zur Kompilierungszeit durchgeführt werden.


4
Eigentlich ist Ihre IBase dea die beste, die ich bisher gesehen habe. Leider können Sie es nicht für Schnittstellen verwenden, die Sie nicht besitzen. Alles, was C # tun müsste, ist, alle Schnittstellen von IOjbect erben zu lassen, genau wie alle Klassen von Object erben.
Rhyous

1
Hinweis: Dies ist eine weit verbreitete Idee. Leere Schnittstellen wie IBase- auf diese Weise verwendet - werden als Marker-Schnittstellen bezeichnet . Sie ermöglichen spezielle Verhaltensweisen für 'markierte' Typen.
Pius

Antworten:


131

Das nächste, was Sie tun können (mit Ausnahme Ihres Basisschnittstellenansatzes), ist " where T : class", was Referenztyp bedeutet. Es gibt keine Syntax für "irgendeine Schnittstelle".

Dies (" where T : class") wird beispielsweise in WCF verwendet, um Clients auf Serviceverträge (Schnittstellen) zu beschränken.


7
nette Antwort, aber haben Sie eine Idee, warum diese Syntax nicht existiert? Scheint, als wäre es eine nette Funktion.
Stephen Holt

@StephenHolt: Ich denke, die Entwickler von .NET haben sich bei der Entscheidung, welche Einschränkungen zulässig sind, auf diejenigen konzentriert, mit denen generische Klassen und Methoden Dinge mit generischen Typen tun können, die sie sonst nicht könnten, anstatt zu verhindern, dass sie verwendet werden unsinnige Wege. interfaceAllerdings Tsollte eine Einschränkung von Referenzvergleiche zwischen Tund jedem anderen Referenztyp ermöglichen, da Referenzvergleiche zwischen jeder Schnittstelle und fast jedem anderen Referenztyp zulässig sind und das Zulassen von Vergleichen auch in diesem Fall kein Problem darstellen würde.
Supercat

1
@supercat Eine weitere nützliche Anwendung einer solchen hypothetischen Einschränkung wäre das sichere Erstellen eines Proxys für Instanzen dieses Typs. Für die Schnittstelle ist die Sicherheit garantiert, für versiegelte Klassen würde dies fehlschlagen, genau wie für Klassen mit nicht virtuellen Methoden.
Ivan Danilov

@IvanDanilov: Es gibt eine Reihe denkbarer Einschränkungen, die, wenn sie zulässig sind, einige unsinnige Konstrukte sinnvoll blockieren würden. Ich bin damit einverstanden, dass eine Einschränkung für "jeden Schnittstellentyp" nett wäre, aber ich sehe nicht, dass sie alles zulässt, was ohne sie nicht möglich wäre, abgesehen von der Erzeugung von Squawks zur Kompilierungszeit, wenn versucht wird, dies zu tun Dinge, die sonst zur Laufzeit fehlschlagen könnten.
Supercat

113

Ich weiß, dass dies etwas spät ist, aber für diejenigen, die interessiert sind, können Sie eine Laufzeitprüfung verwenden.

typeof(T).IsInterface

11
+1 für die einzige Antwort, die darauf hinweist. Ich habe gerade eine Antwort mit einem Ansatz zur Verbesserung der Leistung hinzugefügt, indem jeder Typ nur einmal und nicht jedes Mal überprüft wird, wenn die Methode aufgerufen wird.
Phoog

9
Die ganze Idee von Generika in C # ist die Sicherheit während der Kompilierung. Was Sie vorschlagen, kann genauso gut mit einer nicht generischen Methode durchgeführt werden Foo(Type type).
Jacek Gorgoń

Ich mag die Laufzeitprüfung. Vielen Dank.
Tarık Özgün Güner

Außerdem können Sie zur Laufzeit if (new T() is IMyInterface) { }überprüfen, ob eine Schnittstelle von der T-Klasse implementiert ist. Könnte nicht das effizienteste sein, aber es funktioniert.
Tkerwood

26

Nein, eigentlich, wenn Sie denken classund es und s structmeinen , liegen Sie falsch. bedeutet jeden Referenztyp (zB enthält Schnittstellen zu) und bedeutet eine beliebigen Wert Typen (z , ).classstructclassstructstructenum


1
Ist das nicht die Definition des Unterschieds zwischen einer Klasse und einer Struktur? Dass jede Klasse ein Referenztyp ist (und umgekehrt) und ebenso für Struktur- / Werttypen
Matthew Scharley

Matthew: Es gibt mehr Werttypen als C # -Strukturen. Aufzählungen sind beispielsweise Werttypen und Übereinstimmungsbeschränkungen where T : struct.
Mehrdad Afshari

Es ist erwähnenswert, dass ein in Einschränkungen verwendeter Schnittstellentyp nicht impliziert class, aber das Deklarieren eines Speicherorts eines Schnittstellentyps deklariert den Speicherort tatsächlich als Klassenreferenz, die diesen Typ implementiert.
Supercat

4
Um noch genauer zu sein, where T : structentspricht NotNullableValueTypeConstraint, bedeutet also, dass es sich um einen anderen Werttyp als handeln muss Nullable<>. (So Nullable<>ist ein Strukturtyp, der die where T : structBedingung nicht erfüllt .)
Jeppe Stig Nielsen

19

Um Roberts Antwort zu verfolgen, ist dies noch später, aber Sie können eine statische Hilfsklasse verwenden, um die Laufzeitprüfung nur einmal pro Typ durchzuführen:

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() { /*...*/ }
}

Ich stelle auch fest, dass Ihre "sollte funktionieren" -Lösung tatsächlich nicht funktioniert. Erwägen:

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

Jetzt hindert dich nichts mehr daran, Foo so anzurufen:

Foo<Actual>();

Die ActualKlasse erfüllt schließlich die IBaseBedingung.


Ein staticKonstruktor kann nicht sein public, daher sollte dies einen Fehler bei der Kompilierung ergeben. Außerdem staticenthält Ihre Klasse eine Instanzmethode, die ebenfalls einen Fehler bei der Kompilierung darstellt.
Jeppe Stig Nielsen

Verspätet danke an nawfal für die Korrektur der von @JeppeStigNielsen
Phoog

10

Seit einiger Zeit denke ich über zeitnahe Einschränkungen nach, daher ist dies eine perfekte Gelegenheit, um das Konzept auf den Markt zu bringen.

Die Grundidee ist, dass Sie, wenn Sie keine Überprüfungskompilierungszeit durchführen können, dies zum frühestmöglichen Zeitpunkt tun sollten. Dies ist im Grunde der Moment, in dem die Anwendung gestartet wird. Wenn alle Überprüfungen in Ordnung sind, wird die Anwendung ausgeführt. Wenn eine Prüfung fehlschlägt, schlägt die Anwendung sofort fehl.

Verhalten

Das bestmögliche Ergebnis ist, dass unser Programm nicht kompiliert wird, wenn die Einschränkungen nicht erfüllt sind. Leider ist dies in der aktuellen C # -Implementierung nicht möglich.

Das nächstbeste ist, dass das Programm in dem Moment abstürzt, in dem es gestartet wird.

Die letzte Option ist, dass das Programm abstürzt, sobald der Code getroffen wird. Dies ist das Standardverhalten von .NET. Für mich ist das völlig inakzeptabel.

Voraussetzungen

Wir brauchen einen Beschränkungsmechanismus, also mangels etwas Besserem ... verwenden wir ein Attribut. Das Attribut wird zusätzlich zu einer generischen Einschränkung angezeigt, um zu überprüfen, ob es unseren Bedingungen entspricht. Wenn nicht, geben wir einen hässlichen Fehler.

Dies ermöglicht es uns, solche Dinge in unserem Code zu tun:

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

(Ich habe das where T:classhier beibehalten , weil ich immer Überprüfungen zur Kompilierungszeit gegenüber Überprüfungen zur Laufzeit bevorzuge.)

Wir haben also nur ein Problem: Wir prüfen, ob alle von uns verwendeten Typen mit der Einschränkung übereinstimmen. Wie schwer kann es sein?

Lass es uns aufbrechen

Generische Typen befinden sich immer entweder in einer Klasse (/ struct / interface) oder in einer Methode.

Um eine Einschränkung auszulösen, müssen Sie eines der folgenden Dinge tun:

  1. Kompilierungszeit bei Verwendung eines Typs in einem Typ (Vererbung, generische Einschränkung, Klassenmitglied)
  2. Kompilierungszeit, wenn ein Typ in einem Methodenkörper verwendet wird
  3. Laufzeit, wenn Reflection verwendet wird, um etwas basierend auf der generischen Basisklasse zu erstellen.
  4. Laufzeit, wenn mithilfe von Reflektion etwas basierend auf RTTI erstellt wird.

An dieser Stelle möchte ich darauf hinweisen, dass Sie es immer vermeiden sollten, (4) in einem IMO-Programm zu tun. Unabhängig davon werden diese Überprüfungen dies nicht unterstützen, da dies effektiv die Lösung des Stoppproblems bedeuten würde.

Fall 1: Verwenden eines Typs

Beispiel:

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

Beispiel 2:

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

Grundsätzlich umfasst dies das Scannen aller Typen, Vererbungen, Elemente, Parameter usw. usw. usw. Wenn ein Typ ein generischer Typ ist und eine Einschränkung aufweist, überprüfen wir die Einschränkung. Wenn es sich um ein Array handelt, überprüfen wir den Elementtyp.

An dieser Stelle muss ich hinzufügen, dass dies die Tatsache zunichte macht, dass .NET standardmäßig die Typen 'faul' lädt. Durch das Scannen aller Typen erzwingen wir, dass die .NET-Laufzeit alle lädt. Für die meisten Programme sollte dies kein Problem sein. Wenn Sie jedoch statische Initialisierer in Ihrem Code verwenden, können Probleme mit diesem Ansatz auftreten ... Trotzdem würde ich niemandem raten, dies zu tun (außer solchen Dingen :-), also sollte es nicht geben Sie viele Probleme.

Fall 2: Verwenden eines Typs in einer Methode

Beispiel:

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

Um dies zu überprüfen, haben wir nur eine Option: Dekompilieren Sie die Klasse, überprüfen Sie alle verwendeten Mitgliedstoken und überprüfen Sie die Argumente, wenn eines davon der generische Typ ist.

Fall 3: Reflexion, generische Laufzeitkonstruktion

Beispiel:

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

Ich nehme an, es ist theoretisch möglich, dies mit ähnlichen Tricks wie in Fall (2) zu überprüfen, aber die Implementierung ist viel schwieriger (Sie müssen überprüfen, ob MakeGenericTypein einem Codepfad aufgerufen wird). Ich werde hier nicht auf Details eingehen ...

Fall 4: Reflexion, Laufzeit RTTI

Beispiel:

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

Dies ist das Worst-Case-Szenario und, wie ich zuvor erklärt habe, meiner Meinung nach generell eine schlechte Idee. In beiden Fällen gibt es keine praktische Möglichkeit, dies mithilfe von Schecks herauszufinden.

Das Los testen

Das Erstellen eines Programms, das die Fälle (1) und (2) testet, führt zu folgenden Ergebnissen:

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

Den Code verwenden

Nun, das ist der einfache Teil :-)

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

Sie können dies weder in einer veröffentlichten Version von C # noch in der kommenden Version von C # 4.0 tun. Es ist auch keine C # -Einschränkung - es gibt keine "Schnittstellen" -Einschränkung in der CLR selbst.


6

Wenn möglich, habe ich mich für eine solche Lösung entschieden. Dies funktioniert nur, wenn mehrere bestimmte Schnittstellen (z. B. diejenigen, auf die Sie Quellzugriff haben) als generischer Parameter übergeben werden sollen, keine.

  • Ich lasse meine in Frage kommenden Schnittstellen eine leere Schnittstelle erben IInterface.
  • Ich habe den generischen T-Parameter auf "" beschränkt IInterface

In der Quelle sieht es so aus:

  • Jede Schnittstelle, die als generischer Parameter übergeben werden soll:

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

    public interface IInterface
    {
        // Nothing in here, keep moving
    }
  • Die Klasse, für die Sie die Typeinschränkung festlegen möchten:

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

Das bringt nicht viel. Sie Tsind nicht auf Schnittstellen beschränkt, sondern auf alles, was implementiert wird IInterface- was jeder Typ tun kann, wenn er möchte, z. B. struct Foo : IInterfaceweil Sie IInterfacehöchstwahrscheinlich öffentlich sind (andernfalls müsste alles, was dies akzeptiert, intern sein).
AnorZaken

Wenn Sie alle Typen steuern, die Sie trotzdem akzeptieren möchten, können Sie mithilfe der Codegenerierung alle geeigneten Überladungen erstellen, die alle nur zu einer generischen privaten Methode umleiten.
AnorZaken

2

Was Sie sich vorgenommen haben, ist das Beste, was Sie tun können:

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

2

Ich habe versucht, etwas Ähnliches zu tun, und eine Problemumgehungslösung verwendet: Ich habe über implizite und explizite Operatoren für die Struktur nachgedacht: Die Idee ist, den Typ in eine Struktur zu verpacken, die implizit in Typ konvertiert werden kann.

Hier ist eine solche Struktur:

public struct InterfaceType {privater Typ _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));
}

}}

Grundverwendung:

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

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

Sie müssen sich Ihren eigenen Mechanismus vorstellen, aber ein Beispiel könnte eine Methode sein, die einen InterfaceType-Parameter anstelle eines Typs verwendet

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

Eine zu überschreibende Methode, die Schnittstellentypen zurückgeben soll:

public virtual IEnumerable<InterfaceType> GetInterfaces()

Es gibt vielleicht auch Dinge mit Generika zu tun, aber ich habe es nicht versucht

Hoffe das kann helfen oder gibt Ideen :-)


0

Lösung A: Diese Kombination von Einschränkungen sollte gewährleisten, dass TInterfacees sich um eine Schnittstelle handelt:

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

Es erfordert eine einzelne Struktur TStructals Zeuge, um zu beweisen, dass TInterfacees sich um eine Struktur handelt.

Sie können eine einzelne Struktur als Zeugen für alle Ihre nicht generischen Typen verwenden:

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

Lösung B: Wenn Sie keine Strukturen als Zeugen erstellen möchten, können Sie eine Schnittstelle erstellen

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

und verwenden Sie eine Einschränkung:

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

Implementierung für Schnittstellen:

interface IA :ISInterface<IA>{ }

Dies löst einige der Probleme, erfordert jedoch Vertrauen, das niemand ISInterface<T>für Nicht-Schnittstellentypen implementiert , aber das ist aus Versehen ziemlich schwer zu tun.


-4

Verwenden Sie stattdessen eine abstrakte Klasse. Sie hätten also so etwas wie:

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

10
Sie können eine Schnittstelle nicht immer durch eine abstrakte Klasse ersetzen, da C # keine Mehrfachvererbung unterstützt.
Sam
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.