Laden...

Generische Vektorklasse und operator+

Erstellt von Therion vor 17 Jahren Letzter Beitrag vor 17 Jahren 4.869 Views
T
Therion Themenstarter:in
56 Beiträge seit 2006
vor 17 Jahren
Generische Vektorklasse und operator+

Hallo, ich habe mir eine generische Vektorklasse geschrieben und nun wollte ich den operator+ überladen:


        public static Vector<T> operator+ (Vector<T> x1, Vector<T> x2)
        {
            try
            {
                Vector<T> result = new Vector<T>(x1._Vector.Capacity);
                int i = 0;
                foreach (T value in x1._Vector)
                {
                    result._Vector.Insert(i, x1._Vector[i] + x2._Vector[i]);
                    i++;
                }
                // Return sum of two vectors
                return result;
            }
            catch(ArgumentOutOfRangeException e)
            {
                throw e;
            }
        }

Exception:


Fehler	1	Der Operator "+" kann nicht auf Operanden vom Typ "T" und "T" angewendet werden.

Ich dachte er sucht je nach Typ T dann die entsprechende Methode von + aus. Bei double halt die für doubles und falls in den Vektoren wiederrum Vektoren als Elemente eingetragen sind, dann die entsprechende Funktion, in diesem Fall die selbe Methode nur für die einzelnen Einträge im zweiten Fall..

Ich hoffe ihr versteht was ich meine, wie kann ich das realisieren, da er das oben so nicht mag 😠

Mfg, Therion

B
1.529 Beiträge seit 2006
vor 17 Jahren

Mit Hilfe von where.

public class Vector<T> where T: long, float, double, decimal

Nur dann ist garantiert, dass für jeden erlaubten Typ eine Operation + definiert ist. Desweiteren macht ein Vektor von String oder Button auch kaum Sinn...

H
240 Beiträge seit 2006
vor 17 Jahren
  
catch(ArgumentOutOfRangeException e)  
{  
   throw e;  
}  
  

WTF? 🤔

P.S.: Sorry, bei deinem Problem kann ich Dir leider nicht helfen...

MfG hulkstar

T
Therion Themenstarter:in
56 Beiträge seit 2006
vor 17 Jahren

@hulkstar Die Vektorklasse ist intern als List<T> definiert, d.h. wenn man 2 Vektoren addiert, die unterschiedliche Dimension haben (Listenlänge), dann kommt es beim Zugriff auf einer der Listen zur einer OutOfRange Exception. Da diese Vektorklasse aber kein Hauptprogramm ist, sondern in diverse andere Anwendungen eingebunden werden soll, möchte ich die Fehler nicht hier behandeln. Deswegen reiche ich die Exception mit throw e; weiter. Dann kann z.b. das Hauptprogramm darauf reagieren und ne rote Meldung in die Statusleiste schreiben oder eine MessageBox öffnen, die den Fehler anzeigt. Wichtig ist nur dass irgendwer diese Exception mal behandelt sonst crasht die ganze Anwendung..

Mfg, Gekayn

T
512 Beiträge seit 2006
vor 17 Jahren

Original von Borg
Mit Hilfe von where.

public class Vector<T> where T: long, float, double, decimal  

Nur dann ist garantiert, dass für jeden erlaubten Typ eine Operation + definiert ist. Desweiteren macht ein Vektor von String oder Button auch kaum Sinn...

Diese Constraints funktionieren auch nicht. Abgesehen davon, dass man von diesen Typen nicht erben kann und sie somit prinzipiell nicht für Constraints in Frage kommen, sind die Constraints auch immer UND verknüpft.

Es gibt in C# keinen Weg das hinzubekommen. Generics sind eben doch was anderes als Templates. Bei C++ Templates wird einfach implizit angenommen, dass es die Funktionen die man verwendet schon für diese Typen geben wird.
Bei C# Generics läuft es andersrum, da legt man fest für welche Typen der Generic definiert ist, und kann dann auch nur diese Funktionen verwenden. Das hängt imo auch mit Reflection zusammen.

Und das macht eben diese Sachen leider unmöglich. Es gibt kein Interface für mathematische Operationen, die Operatoren selbst sind sogar statische Funktionen. Es wäre wirklich schön, wenn es neben IComparable auch sowas wie IArithmetic geben würde.

Du hättest höchstens die Chance in der Klasse selbst über Templates zu gehen und darin erst die Addition auszuführen. Das heißt, dass dort Code zur Laufzeit geschrieben wird, wenn der Konkrete Typparameter bekannt ist.

Bei der Exception solltest du vieleicht ein simples throw; in Erwägung ziehen. Durch throw e; wird der Stacktrace neu aufgebaut, das heißt du kannst den Fehler nur genau bis zu der Funktion zurückverfolgen, die throw e; aufgerufen hat. Alles weiter verschachtelte geht verloren. Durch throw; kannst du im catch-Block Fehlerbehandlung machen, die Exception weiter nach unten reichen, und der komplette Stacktrace bleibt erhalten. Wenn du aber keine Behandlung im catch-Block machst, kannst du ihn auch gleich komplett weglassen.

e.f.q.

Aus Falschem folgt Beliebiges

1.271 Beiträge seit 2005
vor 17 Jahren

Hallo Therion,

Schau dir mal Operator Overloading with Generics und
Using generics for calculations bei Codeproject an.

Gruß,
Thomas

A wise man can learn more from a foolish question than a fool can learn from a wise answer!
Bruce Lee

Populanten von Domizilen mit fragiler, transparenter Außenstruktur sollten sich von der Translation von gegen Deformierung resistenter Materie distanzieren!
Wer im Glashaus sitzt, sollte nicht mit Steinen werfen.

T
Therion Themenstarter:in
56 Beiträge seit 2006
vor 17 Jahren

@Traumtänzer danke 🙂

@Progger wow, das ist genau dass was ich gesucht habe, besonders der 2te link ist sehr gut, werde das gleich implementieren, danke dir 🙂

Mfg, Therion

T
Therion Themenstarter:in
56 Beiträge seit 2006
vor 17 Jahren

So bin schon ganzes Stück weiter, aber leider schein ich das mit der "Factory" irgendwie nciht wirklich verstanden zu haben. Denn für die Operationen +, -, und Multiplikation bzw. Division mit skalaren ist mir die Implementierung gelungen, aber der Versuch das Skalarprodukt zu berechnen, scheitert kläglich. Ich wollte das Skalarprodukt berechnen indem ich erstmal die 2 Vektoren rekursiv miteinander multipliziere und danach wollte ich die Einträge des resultierenden Ergebnisvektors miteinander addieren. Leider klappt das multiplizieren der einzelnen Einträge nicht, obwohl statt Opcodes.Add nur Opcodes.Mul nötig ist:


static class BinaryOperatorFactory<TLeft, TRight, TResult, TOwner>
    {
        private static BinaryOperator<TLeft, TRight, TResult> mul;
        public static BinaryOperator<TLeft, TRight, TResult> Mul
        {
            get
            {
                // if we haven't created the delegate yet, do so now
                if (mul == null)
                {
                    // create the DynamicMethod
                    DynamicMethod method =
                    new DynamicMethod(
                        "op_DotProduct"
                        + ":" + typeof(TLeft).ToString()
                        + ":" + typeof(TRight).ToString()
                        + ":" + typeof(TResult).ToString()
                        + ":" + typeof(TOwner).ToString(),
                        typeof(TLeft),
                        new Type[] { 
                            typeof(TLeft), 
                            typeof(TRight) 
                        },
                        typeof(TOwner)
                    );

                    ILGenerator generator = method.GetILGenerator();

                    // generate the opcodes for the method body
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Ldarg_1);

                    if (typeof(TLeft).IsPrimitive)
                    {
                        // if we're working with a primitive, 
                        // use the IL Add OpCode
                        generator.Emit(OpCodes.Mul);
                    }
                    else
                    {
                        // otherwise, bind to the definition 
                        // with the given type
                        MethodInfo info = typeof(TLeft).GetMethod(
                            "op_DotProduct",
                            new Type[] { 
                            typeof(TLeft), 
                            typeof(TRight) 
                        },
                            null
                            );

                        generator.EmitCall(OpCodes.Call, info, null);
                    }

                    // insert a return statement
                    generator.Emit(OpCodes.Ret);

                    // store the delegate for later use
                    mul = (BinaryOperator<TLeft, TRight, TResult>)
                    method.CreateDelegate(typeof(BinaryOperator<TLeft, TRight, TResult>));
                }

                return mul;
            }
        }
...

Ich bekomme folgende Fehlermeldung:


System.ArgumentNullException wurde nicht behandelt.
  Message="Der Wert darf nicht NULL sein.\r\nParametername: methodInfo"
  Source="mscorlib"
  ParamName="methodInfo"

Ich habe da jetzt schon stundenlang drüber überlegt, alles in Gedanken durchgegangen aber ich find den Fehler nicht. 🙁 Wenn ich für "op_DotProduct" "op_Addition" einsetze aus dem Additionsteil, dann geht es aber er addiert dann die Zahlen statt sie multiplizieren. Wenn ich Versuche mir eine Methode mit Namen "op_DotProduct" zu holen, die ich ja vorher mit DynamicMethod erzeugt habe, dann haut er die Fehlermeldung raus, keine Ahnung warum.

Das müsste doch analog zur Addition gehn...

Vielen Dank, Therion

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo Therion,

die Methode op_DotProduct, de du definierst hat vier Parameter, die die du aufrufst zwei, zumindest sieht das für mich so aus.

Zum Eingrenzen des Fehlers mittels StackTrace siehe: [FAQ] NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt

herbivore

T
Therion Themenstarter:in
56 Beiträge seit 2006
vor 17 Jahren

Der Fehler wird angezeigt bei:


generator.EmitCall(OpCodes.Call, info, null);

und der Grund war, dass info = null war, was es nicht sein sollte. Und gelegen hat es an dem Methodenbezeichner, dieser darf wohl doch nicht frei gewählt werden, sondern muss je nach Operation passen, z.b. bei Addition muss stehn op_addition.

Ich habe "op_DotProduct" durch "op_Multiply" ersetzt und dann ging es.

Nun hänge ich am Vergleich ==, wieder eine null Exception, diesemal habe ich als Namen "op_Equality", aber der scheint ihm irgendwie nicht zu schmecken, den info ist wieder null...man sieht ich habe die Materie offensichtlich noch immer nicht verstanden, aber ich wurschte mich durch...da sich alles mehrfach rekursiv aufruft öfter, bekommt man beim denken auch fast nen knoten hehe

Mfg, Therion

T
512 Beiträge seit 2006
vor 17 Jahren

Für Gleichheit braucht man doch garnicht den umständlichen Weg gehen.
Dafür kannst du ja festen Code benutzen, weil ja jedes Objekt den == Operator hat.

Ansonsten mal eine Testklasse erstellen und compilieren, und dann mit ILdasm (oder dem Reflector im IL Sprachmodus) das Ergebnis anschauen. Da sind dann die Namen der Funktionen alle ordnungsgemäß umgesetzt.

e.f.q.

Aus Falschem folgt Beliebiges

T
Therion Themenstarter:in
56 Beiträge seit 2006
vor 17 Jahren

Hm, muss doch den umständlichen Weg gehn, da ich den Operator == auch nicht mit generischen Typen überladen kann. Ich habe Doch Vektoren die wiederrum Vektoren usw enthalten können als Einträge. Habe das auch probiert, aber er mag bei == keine generischen Ausdrücke. Natürlich gibt es die Methode == aus Objects schon, aber diese testet ja nur ob die Objekte gleich sind, aber bei Vektoren muss ich die Einträge miteinander vergleichen.

Der Funktionsname scheint nach ILdasm in Ordnung zu sein mit "op_Equality". Ich hatte vorher eine Rückgabe von int32 vorgesehn, weil es bei Opcodes garkeine Möglichkeit zu geben scheint zwei Werte zu vergleichen und ein bool zurückzugeben. Da gibt es nur etwas das nennt sich Opcodes.Ceq und liefert bei Gleichheit 1 und sonst 0.
er findet ja auf jedenfall die MethodenInfo mal wieder nicht und dachte, vielleicht erwartet ja "op_Equality" ein bool als Rückgabe. Nur jetzt die Frage wie ich das hinbekomme, da es kein passenden Opcode gibt. Gibt nichtmal einen wo ich nen true z.b. auf den Stack legen kann und dann zurückgeben mit Opcodes.Ret.


private static BinaryOperator<TLeft, TRight, TResult> eq;
        public static BinaryOperator<TLeft, TRight, TResult> Eq
        {
            get
            {
                // if we haven't created the delegate yet, do so now
                if (eq == null)
                {
                    // create the DynamicMethod
                    DynamicMethod method =
                    new DynamicMethod(
                        "op_Equality"
                        + ":" + typeof(TLeft).ToString()
                        + ":" + typeof(TRight).ToString()
                        + ":" + typeof(TResult).ToString()
                        + ":" + typeof(TOwner).ToString(),
                        typeof(TResult),
                        new Type[] { 
                            typeof(TLeft), 
                            typeof(TRight) 
                        },
                        typeof(TOwner)
                    );

                    ILGenerator generator = method.GetILGenerator();

                    // generate the opcodes for the method body
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Ldarg_1);

                    if (typeof(TLeft).IsPrimitive)
                    {
                        // if we're working with a primitive, 
                        // use the IL Add OpCode
                        generator.Emit(OpCodes.Ceq);
                    }
                    else
                    {
                        // otherwise, bind to the definition 
                        // with the given type

                        MethodInfo info = typeof(TResult).GetMethod(
                            "op_Equality",
                            new Type[] { 
                            typeof(TLeft), 
                            typeof(TRight) 
                        },
                            null
                            );
                        if (info != null)
                        {
                            generator.EmitCall(OpCodes.Call, info, null);
                        }
                    }

                    // insert a return statement
                    generator.Emit(OpCodes.Ret);

                    // store the delegate for later use
                    eq = (BinaryOperator<TLeft, TRight, TResult>)
                    method.CreateDelegate(typeof(BinaryOperator<TLeft, TRight, TResult>));
                }

                return eq;
            }
        }

T
512 Beiträge seit 2006
vor 17 Jahren

Original von Therion
Hm, muss doch den umständlichen Weg gehn, da ich den Operator == auch nicht mit generischen Typen überladen kann. Ich habe Doch Vektoren die wiederrum Vektoren usw enthalten können als Einträge. Habe das auch probiert, aber er mag bei == keine generischen Ausdrücke. Natürlich gibt es die Methode == aus Objects schon, aber diese testet ja nur ob die Objekte gleich sind, aber bei Vektoren muss ich die Einträge miteinander vergleichen.

Du musst nur die Equals Funktion überschreiben. Dort testest du, ob das andere Objekt ein Vektor vom gleichen Typ ist. Wenn ja, dann casten. Und dann durch die einzelnen Elemente iterieren und vergleichen.

e.f.q.

Aus Falschem folgt Beliebiges

T
Therion Themenstarter:in
56 Beiträge seit 2006
vor 17 Jahren

Ja eben, das Überprüfen ob es sich um das gleich Objekt handelt, das habe ich gemacht. Aber das durch die Elemente iterieren und vergleichen das macht Probleme. Ich habe den Quelltext zu Eq oben nochmal angepasst, so ist der aktuelle stand.

Problem macht halt, das die Opcodes keine Boolean kennen. Ich habe versucht es mit Ceq zu lösen. Das liefert 1 wenn beide Primitiven Typen gleich sind oder 0 sonst. Damit dann als Rückgabe nicht bool sondern ein Int32 definiert. Aber ich denke der Konflikt kommt daher dass op_equality ein bool als Rückgabe erwartet und kein Int32. Wenn beide Typen nicht Primitiv sind dann wird ja der Vergleichsoperatot == nochmal für die Argumente aufgerufen.

Hmmm, irgendwas mag er nicht daran..