![]() |
[.NET] Generics und Operatorüberladung
Moinmoin!
Ich implementiere gerade eine Matrix-Klasse in C#, und brauche diese mit generischem Typ. Hintergrund ist, dass eine Matrix wiederum Matrizen als Elemente beinhalten können muss, dies aber nicht zwangsweise tut. Es kommen also auch normale Typen wie Double oder Integer vor. Das Problem hier ist nun, dass der Compiler (zurecht) meckert, dass die Operatoren die ich da überlade nicht unbedingt für den Typ <T> vorhanden sind. Ich suche nun also eine Möglichkeit, dem Compiler klar zu machen, dass dort lediglich Typen kommen, für die die Operatoren sehr wohl definiert sind. Hier mal mein grobes Codegerüst (unwichtigen Krams ausgelassen):
Code:
Ich habe bis jetzt kein Interface oder Constraint gefunden, mit dem ich das realisieren kann. Gibbets da wat?public class Matrix<T> { private T[,] M; public Matrix(int sizeX, int sizeY) { M = new T[sizeX, sizeY]; } public Matrix(T[,] init) { M = (T[,])init.Clone(); } // Und hier knallt es dann natürlich, da nicht alle Nachkommen von Object + rechnen können public static Matrix<T> operator +(Matrix<T> a, Matrix<T> b) { if ((a.XDimension != b.XDimension) || (a.YDimension != b.YDimension)) throw new ArrayTypeMismatchException("Operation + on mismatching matrices"); else{ Matrix<T> t = new Matrix<T>(a.XDimension, a.YDimension); for (int x=0; x<t.XDimension; x++) for (int y=0; y<t.YDimension; y++) t[x, y] = a[x, y] + b[x, y]; return t; } } } |
Re: [.NET] Generics und Operatorüberladung
Nein. Du musst einen Wrapper (Structs, die nur deinen Typen enthalten, bieten sich da an) definieren, der ein entsprechendes Interface implementiert.
add: Diese Structs haben dann idealerweise implizite Konversionen zum/vom gewrapten Typ, dann merkt man das (fast) nicht, nur bei der Deklaration der Matrix. |
Re: [.NET] Generics und Operatorüberladung
Zitat:
Es wird sich wohl eher lohnen Klassen für den Wrapper herzunehmen. man kann es auch so lösen, dass du Delegates für einen Satz T hinterlegst, die wiederum den Code des Operators enthalten. Mit ein paar Tricks kostet dich das fast gar nix zur Laufzeit. Ich muss jetzt aber los, vllt kann ich es vor Montag noch genauer zeigen... |
Re: [.NET] Generics und Operatorüberladung
Verdammt, ich bin auch gerade auf diverse Seiten gestoßen wo sich groß darüber ausgelassen wird, dass das ein riesen Leck in C# ist. Seit Jahren schon, und trotz vieler vorgeschlagener Wege das zu umgehen, ist keiner davon für mich wirklich geeignet (bzw. die die es wären, sind teils vollständige Parser mit 1000+ Zeilen... zu langsam, zu komplex - Sitchwort LINQ).
Warum zum Geier stoße ich bei Fun-Projekten immer schon nach unter einer Stunde an die Grenzen meiner Tools? :stupid: Ein einfaches Interface "IArithmetic" dass alle numerischen Typen implementieren wäre doch so eine tolle Sache. Aber Dax, was du da vorschlägst klingt nicht uninteressant, jedoch habe ich gerade keine Vorstellung davon wie das in Code gegossen aussehen könnte. (Ich bin noch nicht allzu geübt in C#.) Magst du mir ein kurzes strukturelles Beispiel zeigen? \\Edit: Elvis' roter Kasten is kaputt :) Um ehrlich zu sein wäre Boxing ein vergleichsweise kleiner Tradeoff. Wie sähe sowas aus? Auch die Variante mit den Delegates klingt nicht unspannend! Einfach wäre es ja, wenn T nur von mir erstellte Typen beinhalten würde. Irgendwie muss ich da nur noch die primitiven mit rein kriegen... |
Re: [.NET] Generics und Operatorüberladung
Zitat:
|
Re: [.NET] Generics und Operatorüberladung
Ein Beispiel für beides:
Code:
Wobei ich die Delegate-Variante im Nachhinein doch eleganter finde. Schneller ist sie auch, was mich nicht sehr wundert ;)
interface IArithmetic<T, V>
{ T Add(T left, T right); V Value { get; set; } } struct DoubleArithmetic : IArithmetic<DoubleArithmetic, double> { public double Value { get; set; } public DoubleArithmetic Add(DoubleArithmetic left, DoubleArithmetic right) { return new DoubleArithmetic { Value = left.Value + right.Value }; } public static implicit operator DoubleArithmetic(double value) { return new DoubleArithmetic { Value = value }; } public static implicit operator double(DoubleArithmetic da) { return da.Value; } } class Combiner<T> { public Func<T, T, T> Add { get; private set; } public Combiner(Func<T, T, T> _add) { Add = _add; } } public class MainClass { static void test<T, V>(V inc) where T : IArithmetic<T, V>, new() { var d = DateTime.Now; T da = new T(); int max = 10000000; for (int i = 0; i < max; i++) { da = da.Add(da, new T { Value = inc }); } Console.WriteLine((DateTime.Now - d).TotalMilliseconds / max * 1000000); } static void test2<T>(T inc, Combiner<T> ops) where T: new() { var d = DateTime.Now; T da = new T(); int max = 10000000; for (int i = 0; i < max; i++) { da = ops.Add(da, inc); } Console.WriteLine((DateTime.Now - d).TotalMilliseconds / max * 1000000); } public static void Main(string[] args) { test<DoubleArithmetic, double>(1.0); test2<double>(1.0, new Combiner<double>((a, b) => a + b)); } } |
Re: [.NET] Generics und Operatorüberladung
Beide Varianten führen letztlich leider nicht zu dem, was ich mir so vorgestellt hatte. Mir ist es am wichtigsten, dass die Anwendung von Operatoren auf die Klasse Matrix am Ende so simpel ist wie auf primitive Datentypen. Der Grund ist, dass die Formeln die diese Matrizen enthalten an und für sich schon komplex genug sind, so dass formales "Rumgehampel" hier zu totaler Unübersichtlichkeit führen würde.
Ich möchte einfach "P = M*(A+B)" schreiben können, egal ob die Elemente der einzelnen Matrizen nun vom Typ double, int oder Matrix sind. Und für den Fall dass eine nicht lösbare Konstruktion auftritt (wenn z.B. Matrix+double auftaucht), würde ich das am liebsten via Exception aus dem entsprechenden Operator heraus abfangen. Insbesondere die Version mit dem Interface (die mir an sich gut gefiel) hat das Problem, dass Interfaces keine Operatoren vorschreiben können :( Das erklärt nun auch, warum es nicht schon längst ein IArithmetic Interface gibt. Ich fürchte ich werde es ganz hässlich lösen müssen: Da maximal eine Matrix von Matrizen von Doubles auftauchen kann (keine tiefere Verschachtelung), werde ich wohl doch 2 Matrix-Klassen nehmen. Eine für double, eine für Matrix. Das wird nur etwas interessanter bei den Operatoren, die dann eine Fülle an Kombinationen von Parametern bedenken müssen. Es sei denn es lässt sich nicht doch eleganter lösen! Danke euch beiden schon mal kräftig! |
Re: [.NET] Generics und Operatorüberladung
Wenn du dir eine MatrixFactory<T> definierst, die einen Combiner<T> als Konstruktorargument verlangt und eine Methode hat, die Matrizen erstellt und denen auch gleich den Combiner mitgibt, kannst du wunderbar Operatoren auf die Matrizen und deren Elementtypen definieren ;)
|
Re: [.NET] Generics und Operatorüberladung
:freak: Ooookay, klingt toll, aber bis ich das verstanden und umgesetzt habe dürfte es .NET 10 geben ;). Das ärgerliche ist für mich grad, dass das der aller erste Schritt von zig anderen nicht minder komplexen Dingen - ganz im Gegenteil - ist die vor mir liegen, und dass nur um mal was auszuprobieren. Ich dachte mir halt: Okay, fängste mit was einfachem an, ne Matrixklasse ist ja fix runtergetippert. Ich bin schon etwas demoralisiert nun =] Mich erwarten noch ganz andere Brocken. Man hätte mir sagen sollen, dass Spline-Patches an und für sich schon nichts für ein gemütliches Wochenende sind, und die sind nur die absolute Basis des Ganzen *soifz*.
|
Re: [.NET] Generics und Operatorüberladung
Wieso denn, das geht doch ganz fix ;)
Code:
class Combiner<T>
{ public Func<T, T, T> Add { get; private set; } public Combiner(Func<T, T, T> _add) { Add = _add; } } class MatrixFactory<T> { Combiner<T> combiner; public MatrixFactory(Combiner<T> combiner) { this.combiner = combiner; } public Matrix<T> New(int dimX, int dimY) { return new Matrix<T>(combiner, dimX, dimY); } } class Matrix<T> { Combiner<T> ops; T[] values; int dimX, dimY; public Matrix(Combiner<T> ops, int dimX, int dimY) { this.ops = ops; this.dimX = dimX; this.dimY = dimY; this.values = new T[dimX * dimY]; } public T this[int x, int y] { get { return values[(y - 1) * dimY + (x - 1)]; } set { values[(y - 1) * dimY + (x - 1)] = value; } } public override string ToString() { string result = ""; for (int y = 1; y <= dimY; y++) { if (y > 0) result += "\n"; for (int x = 1; x <= dimX; x++) { if (x > 0) result += "\t"; result += this[x, y].ToString(); } } return result; } public static Matrix<T> operator +(T left, Matrix<T> right) { Matrix<T> result = new Matrix<T>(right.ops, right.dimX, right.dimY); result.values = new T[right.values.Length]; for (int i = 0; i < result.values.Length; i++) { result.values[i] = right.ops.Add(left, right.values[i]); } return result; } public static Matrix<T> operator +(Matrix<T> left, Matrix<T> right) { Matrix<T> result = new Matrix<T>(right.ops, right.dimX, right.dimY); result.values = new T[right.values.Length]; for (int i = 0; i < result.values.Length; i++) { result.values[i] = right.ops.Add(left.values[i], right.values[i]); } return result; } } public class MainClass { public static void Main(string[] args) { MatrixFactory<double> mf = new MatrixFactory<double>(new Combiner<double>((a, b) => a + b)); Matrix<double> m = mf.New(4, 4); Matrix<double> m2 = mf.New(4, 4); Random r = new Random(); for (int x = 1; x <= 4; x++) for (int y = 1; y <= 4; y++) m2[x, y] = r.NextDouble(); m = 1 + m2 + m; Console.WriteLine(m); } } |
Alle Zeitangaben in WEZ +1. Es ist jetzt 00:11 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz