Laden...

Generics Typparameter geeignet einschränken?

Erstellt von CPlusPlusSharp vor einem Jahr Letzter Beitrag vor einem Jahr 651 Views
C
CPlusPlusSharp Themenstarter:in
7 Beiträge seit 2022
vor einem Jahr
Generics Typparameter geeignet einschränken?

Einen wunderschönen guten Tag,
nach langer erfolgloser Suche im Internet, mein programmiertechnisches Problem in den Griff zu bekommen, habe ich mich auf die Suche nach einem Forum gemacht und bin hier gelandet.

Mit C# beschäftige ich mich schon sehr lange. Manche Freunde von mir behaupten gerne scherzhaft ich sei als Kind in den Kessel mit den Programmiersprachen gefallen, naja wer weiß. Nichtsdestotrotz lese ich manche Bücher gerne zur Auffrischung mancher Themen noch einmal von Anfang an, auch um ein Einrosten bei manchen Sachen entgegenzuwirken.

Bei meinem Problem geht es konkret um ein Projekt: Mathematische Vektoren und Matrizen. Diese hatte ich geplant mit Generics zu realisieren. Nun würde ich aber gerne einschränken, dass Vektoren und Matrizen zum Beispiel nur mit int, float, double, decimal Einträgen erzeugt werden dürfen. Suchen im Internet haben ergeben, dass man Typparameter auf struct einschränken kann, sowie auf Interfaces die zum Beispiel die integrierten Werttypen erben. Ist das ausreichend?
Eine Alternative, die ich gesehen habe, war im Konstruktor den Typparameter mit den gewünschten Typen abzugleichen. Aber diese Variante war mir nicht elegant genug. Gibt es eventuell mittlerweile etwas Neues und Elegantes im C# Angebot? Die Sprache wird schließlich ständig weiterentwickelt.

Ich würde mich sehr über ein paar Anregungen oder Einwände freuen.
Jan

16.835 Beiträge seit 2008
vor einem Jahr

Eine Einschränkung durch Runtime Checks (auf Typ prüfen) sind immer möglich, sind aber halt Runtime-Checks.
Generics sind eine Einschränkung zur Entwicklungszeit, haben keinen direkten Impact auf die Laufzeit (Benefit ist jedoch zB. dass man auf Dinge wie Boxing i.d.R. verzichten kann).

Eine Einschränkung nur auf int, float, double, decimal auf Generic Ebene ist nicht möglich.
Wenn Dir Runtime Checks nicht gefallen, was ich verstehe, dann bleiben Dir an der Stelle halt nur noch die Möglichkeiten der Objektorientierten Programmierung.

Ich weiß nicht 100% was Du vor hast, aber auf Deinen Nickname bezogen: versuch nicht in C# zu programmieren, wie man es in C++ macht 😉
Generics in C++ sind ja eh noch neu.

6.911 Beiträge seit 2009
vor einem Jahr

Hallo CPlusPlusSharp,

"Generic Math" ist ein neues Feature von .NET -- wird mit .NET 7 kommen und könnte etwas sein wonach du suchst.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

C
CPlusPlusSharp Themenstarter:in
7 Beiträge seit 2022
vor einem Jahr

Guten Abend.
Vielen Dank für die Antworten.

@gfoidl: Das scheint tatsächlich etwas zu sein wonach ich suche. Ich werde das Thema auf jeden Fall mal im Auge behalten.

@Abt: Du hattest geschrieben, wenn mir Runtime Checks nicht gefallen, blieben mir nur noch Möglichkeiten der OOP. Welche meinst du da genau?

Mein Ziel ist Code wie:


Vector<int> v1 = new(3, 1);
Matrix<double> m1 = new(2, 2, 3.3);

und mein Wunsch wäre, dass sich Visual Studio schon beim coden aufregt wenn ich zum Beispiel etwas verfasse wie


Vector<string> s1 = new(2);

Ich habe eben etwas herumprobiert, habe mal die Definition einiger Datentypen eingesehen und habe nun folgende Klassendefinition bei mir stehen mit der nachfolgenden Einschränkung.


internal class Vector<T> where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, ISpanFormattable, IFormattable
	{
// ...
	}

Kann ich das so stehen lassen? Ich habe nun mal getestet einen Vector<string> zu erzeugen und Visual Studio meldet direkt eine Fehlermeldung die stark nach dem aussieht was mir vorschwebt:

CS0453 Der Typ "string" muss ein Non-Nullable-Werttyp sein, wenn er als T-Parameter im generischen Typ oder in der generischen Methode "Vector<T>" verwendet werden soll.

Nene keine Sorge, mein Nickname rührt daher, dass ich mit C++ und C# groß geworden bin. 😁

Mit besten Grüßen,
CPlusPlusSharp

PS: Obwohl meine Lösung (von der ich hoffe, dass es eine ist) zu funktionieren scheint habe ich aber ein sehr ungutes Gefühl wenn es Richtung mathematischer Operationen geht. Sei es Vector<float> mit Vector<int> addieren zu wollen oder Matrix<int> mit einem aus mathematisch korrekter Sicht Vector<int> zu multiplizieren usw.

Ich muss dazu sagen, ich hatte das Projekt vor rund einem Jahr verworfen und etwas ähnliches in C++ angefangen, weil ich in C# an meine Grenzen damit stieß. C++ hat diesbezüglich zwar keine Grenzen aber der Code wird schnell so scheußlich und unschön und komplex, dass ich keinen Durchblick mehr bei meinem Code hatte. Und die Fehlermeldungen in Folge des Code Wirrwars mir den Schlaf raubten. Dennoch reizt es mich das Projekt wieder in C# aufzugreifen. Ich sag mal so ich liebe den extrem komfortablen Umgang mit C#.

16.835 Beiträge seit 2008
vor einem Jahr

Du kannst mehr Einschränkungen zu Deinem Generic hinzufügen, aber ich denke nicht (weiß es nicht auswendig), dass es eine Kombination an einer Schnittstelle gibt, die am Schluss nur int, float, double, decimal gemeinsam zulässt.
Mit .NET 7 und Generic Math wird ein neues Interface (INumber) eingeführt, das sowas eingeschränkter zulässt (eben alle Nummern-Typen). Aber auch dort bekommst Du auf Generic-Ebene keine Einschränkung auf nur die vier gewünschten Typen.

Mit OOP habe ich gemeint, den Overhead über eigene Structs oder Klassen zu gehen, sowas wie


    public interface IVectorValue { }
    public readonly record struct IntVector(int Value) : IVectorValue { }
    public readonly record struct FloatVector(float Value) : IVectorValue { }

    public class Vector<TVectorValue> where TVectorValue : IVectorValue { }

Damit bist frei in Deiner Einschränkung durch die Implementierung.

C
CPlusPlusSharp Themenstarter:in
7 Beiträge seit 2022
vor einem Jahr

Mahlzeit,
es hat mich ein wenig Grübelei gekostet, aber ich glaube so langsam steige ich dahinter obwohl Records für mich neu sind. Ich werde nun einfach mal kurz beschreiben wie ich den Code verstehe und falls ich Blödsinn rede, grätscht mir einfach jemand dazwischen😁

Das Interface kann vermutlich leer bleiben, denn es dient nur der Einschränkung auf IntVector und FloatVector. Beide Records implementieren das Interface nämlich. Und der Typ Parameter der Klasse Vektor hat die Einschränkung, dass nur Typen verwendet werden dürfen, die das Interface implementieren.
Die beiden Records speichern jeder für sich jeweils ein int bzw. float.

Mein ursprünglicher Gedanke war in der Klasse Vector nun ein Array von Elementen des Typparameters zu verwenden. Also würde ich nun ein Array von dem entsprechenden Record verwenden?

Eine Vector Variable erzeuge ich dann mit Vector<IntVector> iv = ...; oder Vector<FloatVector> fv = ...; Ich habe es testweise auch mal int in die spitzen Klammern geschrieben und Visual Studio sagt

CS0315 Der Typ "int" kann nicht als Typparameter "TVectorValue" im generischen Typ oder in der generischen Methode "Vector<TVectorValue>" verwendet werden. Es ist keine Boxing-Konvertierung von "int" in "LinearAlgebra.IVectorValue" vorhanden.

Wäre es denn möglich etwas zu bauen, sodass ich Vector<int> aufrufen kann?

Mit besten Grüßen,
CPlusPlusSharp

16.835 Beiträge seit 2008
vor einem Jahr

Ja, steht in der Fehlermeldung: Boxing.
[FAQ] Casten aber richtig: Boxing/Unboxing - () / is / as / Pattern Matching
Ganz unten.

Aber damit weichst Du Dein gesamtes System auf.
Super-strikt und super-weich passt nicht so zusammen.

C
CPlusPlusSharp Themenstarter:in
7 Beiträge seit 2022
vor einem Jahr

Also die Fehlermeldung habe ich schon verstanden. Boxing/Unboxing/is/as sind mir jetzt auch nicht fremd. Zur Vorsicht habe ich es direkt nach der Fehlermeldung auch nochmal nachgeschlagen. Mir ging es bei der Frage darum, gerade in Bezug auf Typeparameter wo ich etwas definieren müsste damit Vector<int> geht. Ich hatte gerade einen verzweifelten Ansatz mit Custom Typkonvertierungen probiert, aber ich glaube dass das Blödsinn von mir war.😁

Warum das mein System aufweicht sehe ich noch nicht wirklich. Wahrscheinlich weil ich auf das Puzzlestück noch nicht gekommen bin.

16.835 Beiträge seit 2008
vor einem Jahr

Steht im Artikel, wie das geht - mit Operatoren. Daher hatte ich den Link gegeben.

Zum Aufweichen:
Du hast eine Einschränkung geschaffen, dass nur noch Vector<IVector> funktioniert, also Vector<T> where T : IVector
Vector<int> verletzt diese Einschränkung, da Du dafür Vector<T> where T : struct bräuchtest, was wiederum alle structs zulässt.

Damit das also funktioniert, musst Du Deine Einschränkungen aufweichen.

6.911 Beiträge seit 2009
vor einem Jahr

Hallo CPlusPlusSharp,

KISS -> sofern möglich nimm "generic math", denn das wurde genau dafür geschaffen.
Ab .NET 7 (kommt im Herbst) ist es dabei, für .NET 6 ist es "experimental" aber verfügbar.

Sonst mach das Konstrukt auch recht einfach -- v.a. wenn du es intern nutzen willst, also kein NuGet-Paket daraus machen willst. Z.B.


internal static Vector<T> MySuperVectorMethod<T>(Vector<T> vector)
{
    if (typeof(T) != typeof(int) || typeof(T) != typeof(double) ...)
    {
        throw new NotSupportedExcepted("Pech gehat, geht nur int, double, ...");
    }

    // Hier passt es dann
}

Der JIT wendet hier auch "dead code elimination", d.h. das if wird zur Laufzeit entfernt und effizienter Code generiert.

PS: hab das Thema nicht mehr ganz verfolgt, daher hoffe ich jetzt nicht am Thema vorbei geschrieben zu haben.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

C
CPlusPlusSharp Themenstarter:in
7 Beiträge seit 2022
vor einem Jahr

Mahlzeit,
nach langem hin und her probieren sowohl mit Runtime Checks als auch mit Interface+zwei zusätzlichen Records, habe ich mich entschieden die OOP Lösung beizubehalten, auch wenn es da ein paar Sachen gibt, die gewöhnungsbedürftig sind.

Auch wenn jetzt alles im Großen und Ganzen zu klappen scheint, sehe ich schon die nächsten Baustellen am Horizont erscheinen. Gerade auch da es mein Ziel ist möglichst exceptionsicher zu programmieren, aber auch mathematische Operatoren werden wie ich weiß auch noch ein spannendes Thema.

Mit besten Grüßen
CPlusPlusSharp