Kann mir jemand sagen, ob es mit Generika eine Möglichkeit gibt, ein generisches Typargument T
nur auf Folgendes zu beschränken :
Int16
Int32
Int64
UInt16
UInt32
UInt64
Ich kenne das where
Schlüsselwort, kann aber keine Schnittstelle nur für diese Typen finden.
Etwas wie:
static bool IntegerFunction<T>(T value) where T : INumeric
c#
generics
constraints
Corin Blaikie
quelle
quelle
Antworten:
C # unterstützt dies nicht. Hejlsberg hat in einem Interview mit Bruce Eckel die Gründe für die Nichtimplementierung des Features beschrieben :
Dies führt jedoch zu einem ziemlich komplizierten Code, bei dem der Benutzer
Calculator<T>
für jedenT
, den er verwenden möchte, eine eigene Implementierung bereitstellen muss . Solange es nicht erweiterbar sein muss, dh wenn Sie nur eine feste Anzahl von Typen unterstützen möchten, wie z. B.int
unddouble
, können Sie mit einer relativ einfachen Oberfläche davonkommen:( Minimale Implementierung in einem GitHub Gist. )
Sobald Sie jedoch möchten, dass der Benutzer seine eigenen benutzerdefinierten Typen bereitstellen kann, müssen Sie diese Implementierung öffnen, damit der Benutzer seine eigenen
Calculator
Instanzen bereitstellen kann . Um beispielsweise eine Matrix zu instanziieren, die eine benutzerdefinierte Dezimal-Gleitkomma-Implementierung verwendet,DFP
müssen Sie diesen Code schreiben:… Und implementieren alle Mitglieder für
DfpCalculator : ICalculator<DFP>
.Eine Alternative, die leider dieselben Einschränkungen aufweist, besteht darin, mit Richtlinienklassen zu arbeiten, wie in der Antwort von Sergey Shandar erläutert .
quelle
Operator
/Operator<T>
; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.htmlOperator<T>
Code tun (da das Interview lange vor der Existenz desExpressions
Frameworks gegeben wurde, obwohl man es könnte KursgebrauchReflection.Emit
) - und ich würde mich wirklich für seine Problemumgehung interessieren .Angesichts der Popularität dieser Frage und des Interesses an einer solchen Funktion bin ich überrascht zu sehen, dass es noch keine Antwort für T4 gibt.
In diesem Beispielcode werde ich ein sehr einfaches Beispiel dafür zeigen, wie Sie die leistungsstarke Template-Engine verwenden können, um das zu tun, was der Compiler hinter den Kulissen mit Generika tut.
Anstatt die Rahmen zu durchlaufen und die Sicherheit der Kompilierungszeit zu opfern, können Sie einfach die gewünschte Funktion für jeden gewünschten Typ generieren und diese entsprechend verwenden (zur Kompilierungszeit!).
Um dies zu tun:
Das ist es. Du bist jetzt fertig.
Wenn Sie diese Datei speichern, wird sie automatisch in diese Quelldatei kompiliert:
In Ihrer
main
Methode können Sie überprüfen, ob Sie über eine Sicherheit zur Kompilierungszeit verfügen:Ich komme einer Bemerkung voraus: Nein, dies ist kein Verstoß gegen das DRY-Prinzip. Das DRY-Prinzip soll verhindern, dass Benutzer Code an mehreren Stellen duplizieren, wodurch die Wartung der Anwendung schwierig wird.
Dies ist hier überhaupt nicht der Fall: Wenn Sie eine Änderung wünschen, können Sie einfach die Vorlage ändern (eine einzige Quelle für Ihre gesamte Generation!) Und fertig.
Um es mit Ihren eigenen benutzerdefinierten Definitionen zu verwenden, fügen Sie Ihrem generierten Code eine Namespace-Deklaration hinzu (stellen Sie sicher, dass es dieselbe ist wie die, in der Sie Ihre eigene Implementierung definieren), und markieren Sie die Klasse als
partial
. Fügen Sie anschließend diese Zeilen zu Ihrer Vorlagendatei hinzu, damit sie in die spätere Kompilierung aufgenommen werden:Seien wir ehrlich: Das ist ziemlich cool.
Haftungsausschluss: Dieses Beispiel wurde stark von Metaprogramming in .NET von Kevin Hazzard und Jason Bock, Manning Publications, beeinflusst .
quelle
T
, der von den verschiedenenIntX
Klassen stammt oder von diesen erbt ? Ich mag diese Lösung, weil sie Zeit spart, aber damit sie das Problem zu 100% löst (obwohl sie nicht so gut ist, als ob C # diese Art von eingebauter Einschränkung unterstützt hätte), sollte jede der generierten Methoden immer noch generisch sein, damit Sie können ein Objekt eines Typs zurückgeben, der von einer derIntXX
Klassen erbt .IntXX
Typen sind Strukturen, was bedeutet, dass sie die Vererbung überhaupt nicht unterstützen . Und selbst wenn dies der Fall wäre, gilt das Liskov-Substitutionsprinzip (das Sie möglicherweise aus dem SOLID-Idiom kennen): Wenn die Methode definiert istX
undY
ein Kind vonX
dann ist, sollte per Definition jedeY
als Ersatz für diese Methode übergeben werden können sein Basistyp.Hierfür gibt es keine Einschränkungen. Es ist ein echtes Problem für alle, die Generika für numerische Berechnungen verwenden möchten.
Ich würde weiter gehen und sagen, wir brauchen
Oder auch
Leider haben Sie nur Schnittstellen, Basisklassen und die Schlüsselwörter
struct
(müssen Werttyp sein),class
(müssen Referenztyp sein) undnew()
(müssen Standardkonstruktor haben)Sie könnten die Nummer in etwas anderes (ähnlich
INullable<T>
) wie hier auf codeproject einschließen .Sie können die Einschränkung zur Laufzeit anwenden (indem Sie für die Operatoren nachdenken oder nach Typen suchen), aber das verliert den Vorteil, dass das Generikum überhaupt vorhanden ist.
quelle
where T : operators( +, -, /, * )
ist legal C #? Entschuldigung für die Frage des Neulings.where T : operators( +, -, /, * )
, aber nicht können.Problemumgehung mithilfe von Richtlinien:
Algorithmen:
Verwendungszweck:
Die Lösung ist kompilierzeitsicher. CityLizard Framework bietet eine kompilierte Version für .NET 4.0. Die Datei lautet lib / NETFramework4.0 / CityLizard.Policy.dll.
Es ist auch in Nuget verfügbar: https://www.nuget.org/packages/CityLizard/ . Siehe CityLizard.Policy.I- Struktur.
quelle
struct
? was ist, wenn ich Singleton-Klasse statt und ändern Instanz zu verwendenpublic static NumericPolicies Instance = new NumericPolicies();
und fügen Sie dann diesen Konstruktorprivate NumericPolicies() { }
.T Add<T> (T t1, T t2)
,Sum()
funktioniert aber nur, wenn sie ihren eigenen T-Typ aus ihren Parametern abrufen kann, was nicht möglich ist wenn es in eine andere generische Funktion eingebettet ist.Diese Frage ist ein bisschen wie eine FAQ, also poste ich sie als Wiki (da ich vorher ähnliche gepostet habe, aber dies ist eine ältere); wie auch immer...
Welche Version von .NET verwenden Sie? Wenn Sie .NET 3.5 verwenden, habe ich eine generische Operator-Implementierung in MiscUtil (kostenlos usw.).
Dies hat Methoden wie
T Add<T>(T x, T y)
und andere Varianten für die Arithmetik für verschiedene Typen (wieDateTime + TimeSpan
).Darüber hinaus funktioniert dies für alle eingebauten, angehobenen und maßgeschneiderten Bediener und speichert den Delegaten für die Leistung zwischen.
Einige zusätzliche Hintergrundinformationen darüber, warum dies schwierig ist, finden Sie hier .
Vielleicht möchten Sie auch wissen, dass
dynamic
(4.0) dieses Problem auch indirekt löst - dhquelle
Leider können Sie in dieser Instanz nur struct in der where-Klausel angeben. Es scheint seltsam, dass Sie Int16, Int32 usw. nicht speziell angeben können, aber ich bin sicher, dass der Entscheidung, Werttypen in einer where-Klausel nicht zuzulassen, ein tiefgreifender Implementierungsgrund zugrunde liegt.
Ich denke, die einzige Lösung besteht darin, eine Laufzeitprüfung durchzuführen, die leider verhindert, dass das Problem beim Kompilieren erkannt wird. Das würde ungefähr so aussehen: -
Was ein bisschen hässlich ist, weiß ich, aber zumindest die erforderlichen Einschränkungen bietet.
Ich würde auch mögliche Auswirkungen auf die Leistung dieser Implementierung untersuchen. Vielleicht gibt es da draußen einen schnelleren Weg.
quelle
// Rest of code...
möglicherweise nicht kompiliert werden, wenn dies von den durch die Einschränkungen definierten Operationen abhängt.// Rest of code...
likevalue + value
odervalue * value
auszuführen, tritt ein Kompilierungsfehler auf.Wahrscheinlich ist das nächste, was Sie tun können
Ich bin mir nicht sicher, ob Sie Folgendes tun könnten
Für etwas so Spezifisches, warum nicht einfach Überladungen für jeden Typ haben, ist die Liste so kurz und es würde möglicherweise weniger Speicherbedarf haben.
quelle
Ab C # 7.3 können Sie eine nähere Annäherung verwenden - die nicht verwaltete Einschränkung, um anzugeben, dass ein Typparameter ein nicht zeigerfreier, nicht nullfähiger, nicht verwalteter Typ ist.
Die nicht verwaltete Einschränkung impliziert die Strukturbeschränkung und kann weder mit der Struktur noch mit den Einschränkungen new () kombiniert werden.
Ein Typ ist ein nicht verwalteter Typ, wenn es sich um einen der folgenden Typen handelt:
Um weitere Einschränkungen vorzunehmen und Zeiger- und benutzerdefinierte Typen zu entfernen, die IComparable nicht implementieren, fügen Sie IComparable hinzu (Enum wird jedoch weiterhin von IComparable abgeleitet. Beschränken Sie also Enum durch Hinzufügen von IEquatable <T>. Sie können je nach Ihren Umständen weitere Schritte ausführen und zusätzliche Schnittstellen hinzufügen. unmanaged ermöglicht es, diese Liste kürzer zu halten):
quelle
DateTime
fällt unterunmanaged, IComparable, IEquatable<T>
Zwang ..Es gibt keine Möglichkeit, Vorlagen auf Typen zu beschränken, Sie können jedoch je nach Typ verschiedene Aktionen definieren. Als Teil eines generischen numerischen Pakets benötigte ich eine generische Klasse, um zwei Werte hinzuzufügen.
Beachten Sie, dass die typeofs zur Kompilierungszeit ausgewertet werden, sodass die if-Anweisungen vom Compiler entfernt werden. Der Compiler entfernt auch falsche Casts. Also würde sich etwas im Compiler auflösen
quelle
Ich habe eine kleine Bibliotheksfunktionalität erstellt, um diese Probleme zu lösen:
Anstatt:
Sie könnten schreiben:
Den Quellcode finden Sie hier: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number
quelle
Ich habe mich genauso gefragt wie Samjudson, warum nur zu ganzen Zahlen? In diesem Fall möchten Sie möglicherweise eine Hilfsklasse oder ähnliches erstellen, um alle gewünschten Typen aufzunehmen.
Wenn Sie nur Ganzzahlen wünschen, verwenden Sie kein Generikum, das ist kein Generikum. oder noch besser, lehnen Sie jeden anderen Typ ab, indem Sie seinen Typ überprüfen.
quelle
Hierfür gibt es noch keine „gute“ Lösung. Sie können das Typargument jedoch erheblich eingrenzen, um viele Fehlanpassungen für Ihre hypotetische 'INumeric'-Einschränkung auszuschließen, wie Haacked oben gezeigt hat.
static bool IntegerFunction <T> (T-Wert) wobei T: IComparable, IFormattable, IConvertible, IComparable <T>, IEquatable <T>, struct {...
quelle
Wenn Sie .NET 4.0 und höher verwenden, können Sie einfach dynamic als Methodenargument verwenden und zur Laufzeit überprüfen, ob der übergebene dynamische Argumenttyp ein numerischer / ganzzahliger Typ ist.
Wenn der Typ der übergebenen dynamischen ist nicht numerisch / Integer - Typ dann Ausnahme auslösen.
Ein Beispiel kurzer Code, implementiert ist die Idee , so etwas wie:
Natürlich funktioniert diese Lösung nur zur Laufzeit, aber niemals zur Kompilierungszeit.
Wenn Sie eine Lösung suchen, die immer zur Kompilierungszeit und nie zur Laufzeit funktioniert, müssen Sie die Dynamik mit einer öffentlichen Struktur / Klasse umschließen, deren überladene öffentliche Konstruktoren nur Argumente des gewünschten Typs akzeptieren und der Struktur / Klasse den entsprechenden Namen geben.
Es ist sinnvoll, dass die umschlossene Dynamik immer ein privates Mitglied der Klasse / Struktur ist und das einzige Mitglied der Struktur / Klasse ist und der Name des einzigen Mitglieds der Struktur / Klasse "Wert" ist.
Sie müssen bei Bedarf auch öffentliche Methoden und / oder Operatoren definieren und implementieren , die mit den gewünschten Typen für das private dynamische Mitglied der Klasse / Struktur arbeiten.
Es ist auch sinnvoll, dass die Struktur / Klasse einen speziellen / eindeutigen Konstruktor hat, der die Dynamik als Argument akzeptiert , das das einzige private dynamische Mitglied namens "Wert" initialisiert, aber der Modifikator dieses Konstruktors ist natürlich privat .
Sobald die Klasse / Struktur fertig ist, definieren Sie den Typ der IntegerFunction des Arguments als die Klasse / Struktur, die definiert wurde.
Ein Beispiel für einen langen Code, der die Idee implementiert, ist wie folgt:
Beachten Sie, dass Sie zur Verwendung von Dynamic in Ihrem Code einen Verweis auf Microsoft.CSharp hinzufügen müssen
Wenn die Version des .NET-Frameworks unter / unter / kleiner als 4.0 liegt und die Dynamik in dieser Version nicht definiert ist, müssen Sie stattdessen ein Objekt verwenden und in den Integer-Typ umwandeln, was problematisch ist. Ich empfehle daher, dass Sie at verwenden Mindestens .NET 4.0 oder neuer, wenn Sie können, damit Sie dynamisch anstelle von Objekt verwenden können .
quelle
Leider bietet .NET keine Möglichkeit, dies nativ zu tun.
Um dieses Problem zu beheben, habe ich die OSS-Bibliothek Genumerics erstellt, die die meisten numerischen Standardoperationen für die folgenden integrierten numerischen Typen und ihre nullbaren Entsprechungen bietet und die Unterstützung für andere numerische Typen bietet.
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,float
,double
,decimal
, UndBigInteger
Die Leistung entspricht einer numerischen typspezifischen Lösung, mit der Sie effiziente generische numerische Algorithmen erstellen können.
Hier ist ein Beispiel für die Codeverwendung.
quelle
Was ist der Sinn der Übung?
Wie bereits erwähnt, könnte eine nicht generische Funktion das größte Element übernehmen, und der Compiler konvertiert automatisch kleinere Ints für Sie.
Wenn sich Ihre Funktion auf einem leistungskritischen Pfad befindet (sehr unwahrscheinlich, IMO), können Sie Überlastungen für alle erforderlichen Funktionen bereitstellen.
quelle
Ich würde ein generisches verwenden, das Sie extern handhaben könnten ...
quelle
Diese Einschränkung betraf mich, als ich versuchte, Operatoren für generische Typen zu überladen. Da es keine "INumeric" -Einschränkung gab und aus einer Reihe anderer Gründe, die die guten Leute im Stackoverflow gerne bereitstellen, können Operationen für generische Typen nicht definiert werden.
Ich wollte so etwas wie
Ich habe dieses Problem mithilfe der dynamischen Laufzeiteingabe von .net4 umgangen.
Die beiden Dinge bei der Verwendung
dynamic
sindquelle
Die numerischen Primitivtypen von .NET haben keine gemeinsame Schnittstelle, über die sie für Berechnungen verwendet werden könnten. Es wäre möglich , eigene Schnittstellen (zB zu definieren
ISignedWholeNumber
) , die solche Operationen durchführen würde, definieren Strukturen , die eine einzelne enthaltenInt16
,Int32
usw. und diese Schnittstellen implementieren und dann Methoden , die auf generische Typen akzeptieren eingeschränktISignedWholeNumber
, aber mit numerischen Werten konvertieren für Ihre Strukturtypen wäre wahrscheinlich ein Ärgernis.Ein alternativer Ansatz wäre statische Klasse zu definieren ,
Int64Converter<T>
mit einer statischen Eigenschaftbool Available {get;};
und statisch DelegiertenInt64 GetInt64(T value)
,T FromInt64(Int64 value)
,bool TryStoreInt64(Int64 value, ref T dest)
. Der Klassenkonstruktor kann hartcodiert sein, um Delegaten für bekannte Typen zu laden, und möglicherweise Reflection verwenden, um zu testen, ob der TypT
Methoden mit den richtigen Namen und Signaturen implementiert (falls es sich um eine Struktur handelt, die eineInt64
und eine Zahl enthält, diese aber hat eine benutzerdefinierteToString()
Methode). Dieser Ansatz würde die mit der Typprüfung zur Kompilierungszeit verbundenen Vorteile verlieren, aber dennoch Boxoperationen vermeiden, und jeder Typ müsste nur einmal "geprüft" werden. Danach werden Operationen, die diesem Typ zugeordnet sind, durch einen Delegatenversand ersetzt.quelle
Int64
Ergebnis zu erhalten, bietet jedoch kein Mittel, mit dem z. B. eine Ganzzahl eines beliebigen Typs erhöht werden kann, um eine andere Ganzzahl desselben Typs zu erhalten .Ich hatte eine ähnliche Situation, in der ich mit numerischen Typen und Zeichenfolgen umgehen musste. scheint ein bisschen bizarr zu sein, aber los geht's.
Wie viele andere habe ich mich auch hier mit Einschränkungen befasst und eine Reihe von Schnittstellen entwickelt, die unterstützt werden mussten. A) es war jedoch nicht 100% wasserdicht und b) jeder, der sich diese lange Liste von Einschränkungen noch einmal ansieht, wäre sofort sehr verwirrt.
Mein Ansatz war es also, meine gesamte Logik in eine generische Methode ohne Einschränkungen zu integrieren, diese generische Methode jedoch privat zu machen. Ich habe es dann mit öffentlichen Methoden verfügbar gemacht, von denen eine explizit den Typ behandelt, den ich behandeln wollte - meiner Meinung nach ist der Code sauber und explizit, z
quelle
Wenn Sie nur einen numerischen Typ verwenden möchten , können Sie einen Alias in C ++ mit erstellen
using
.Also anstatt das sehr generische zu haben
du könntest haben
Auf diese Weise können Sie bei Bedarf problemlos von
double
zuint
oder zu anderen wechseln, können diese jedoch nichtComputeSomething
mitdouble
undint
im selben Programm verwenden.Aber warum nicht alle
double
bisint
dahin ersetzen ? Weil Ihre Methode möglicherweise verwenden möchte,double
ob die Eingabedouble
oder istint
. Mit dem Alias können Sie genau wissen, welche Variable den dynamischen Typ verwendet.quelle
Thema ist alt, aber für zukünftige Leser:
Diese Funktion ist eng verwandt und
Discriminated Unions
in C # bisher nicht implementiert. Ich habe das Problem hier gefunden:https://github.com/dotnet/csharplang/issues/113
Diese Ausgabe ist noch offen und die Funktion ist für geplant
C# 10
Wir müssen also noch etwas länger warten, aber nach der Veröffentlichung können Sie dies folgendermaßen tun:
quelle
Ich denke, Sie verstehen Generika falsch. Wenn der Vorgang, den Sie ausführen möchten, nur für bestimmte Datentypen geeignet ist, führen Sie keine "generischen" Vorgänge aus.
Da Sie nur zulassen möchten, dass die Funktion für int-Datentypen funktioniert, sollten Sie für jede bestimmte Größe keine separate Funktion benötigen. Durch einfaches Aufnehmen eines Parameters im größten spezifischen Typ kann das Programm die kleineren Datentypen automatisch auf diesen übertragen. (dh das Übergeben eines Int16 wird beim Aufruf automatisch in Int64 konvertiert).
Wenn Sie verschiedene Operationen ausführen, basierend auf der tatsächlichen Größe von int, die an die Funktion übergeben wird, sollten Sie ernsthaft überlegen, ob Sie versuchen, das zu tun, was Sie tun. Wenn Sie die Sprache zum Narren halten müssen, sollten Sie etwas mehr darüber nachdenken, was Sie erreichen möchten, als darüber, wie Sie das tun, was Sie wollen.
Andernfalls kann ein Parameter vom Typ Object verwendet werden. Anschließend müssen Sie den Typ des Parameters überprüfen und entsprechende Maßnahmen ergreifen oder eine Ausnahme auslösen.
quelle