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 : class
und 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 Foo
es 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.
quelle
IBase
- auf diese Weise verwendet - werden als Marker-Schnittstellen bezeichnet . Sie ermöglichen spezielle Verhaltensweisen für 'markierte' Typen.Antworten:
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.quelle
interface
AllerdingsT
sollte eine Einschränkung von Referenzvergleiche zwischenT
und 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.Ich weiß, dass dies etwas spät ist, aber für diejenigen, die interessiert sind, können Sie eine Laufzeitprüfung verwenden.
quelle
Foo(Type type)
.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.Nein, eigentlich, wenn Sie denken
class
und es und sstruct
meinen , liegen Sie falsch. bedeutet jeden Referenztyp (zB enthält Schnittstellen zu) und bedeutet eine beliebigen Wert Typen (z , ).class
struct
class
struct
struct
enum
quelle
where T : struct
.class
, aber das Deklarieren eines Speicherorts eines Schnittstellentyps deklariert den Speicherort tatsächlich als Klassenreferenz, die diesen Typ implementiert.where T : struct
entsprichtNotNullableValueTypeConstraint
, bedeutet also, dass es sich um einen anderen Werttyp als handeln mussNullable<>
. (SoNullable<>
ist ein Strukturtyp, der diewhere T : struct
Bedingung nicht erfüllt .)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:
Ich stelle auch fest, dass Ihre "sollte funktionieren" -Lösung tatsächlich nicht funktioniert. Erwägen:
Jetzt hindert dich nichts mehr daran, Foo so anzurufen:
Die
Actual
Klasse erfüllt schließlich dieIBase
Bedingung.quelle
static
Konstruktor kann nicht seinpublic
, daher sollte dies einen Fehler bei der Kompilierung ergeben. Außerdemstatic
enthält Ihre Klasse eine Instanzmethode, die ebenfalls einen Fehler bei der Kompilierung darstellt.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:
(Ich habe das
where T:class
hier 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:
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:
Beispiel 2:
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:
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:
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
MakeGenericType
in einem Codepfad aufgerufen wird). Ich werde hier nicht auf Details eingehen ...Fall 4: Reflexion, Laufzeit RTTI
Beispiel:
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:
Den Code verwenden
Nun, das ist der einfache Teil :-)
quelle
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.
quelle
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.
IInterface
.IInterface
In der Quelle sieht es so aus:
Jede Schnittstelle, die als generischer Parameter übergeben werden soll:
IInterface:
Die Klasse, für die Sie die Typeinschränkung festlegen möchten:
quelle
T
sind nicht auf Schnittstellen beschränkt, sondern auf alles, was implementiert wirdIInterface
- was jeder Typ tun kann, wenn er möchte, z. B.struct Foo : IInterface
weil SieIInterface
höchstwahrscheinlich öffentlich sind (andernfalls müsste alles, was dies akzeptiert, intern sein).Was Sie sich vorgenommen haben, ist das Beste, was Sie tun können:
quelle
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;
}}
Grundverwendung:
Sie müssen sich Ihren eigenen Mechanismus vorstellen, aber ein Beispiel könnte eine Methode sein, die einen InterfaceType-Parameter anstelle eines Typs verwendet
Eine zu überschreibende Methode, die Schnittstellentypen zurückgeben soll:
Es gibt vielleicht auch Dinge mit Generika zu tun, aber ich habe es nicht versucht
Hoffe das kann helfen oder gibt Ideen :-)
quelle
Lösung A: Diese Kombination von Einschränkungen sollte gewährleisten, dass
TInterface
es sich um eine Schnittstelle handelt:Es erfordert eine einzelne Struktur
TStruct
als Zeuge, um zu beweisen, dassTInterface
es sich um eine Struktur handelt.Sie können eine einzelne Struktur als Zeugen für alle Ihre nicht generischen Typen verwenden:
Lösung B: Wenn Sie keine Strukturen als Zeugen erstellen möchten, können Sie eine Schnittstelle erstellen
und verwenden Sie eine Einschränkung:
Implementierung für Schnittstellen:
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.quelle
Verwenden Sie stattdessen eine abstrakte Klasse. Sie hätten also so etwas wie:
quelle