Laden...

Überladener Konstruktoraufruf bei generische Klasse

Erstellt von brainwave vor 10 Jahren Letzter Beitrag vor 10 Jahren 4.276 Views
brainwave Themenstarter:in
436 Beiträge seit 2007
vor 10 Jahren
Überladener Konstruktoraufruf bei generische Klasse

Hallo zusammen,

wie kann ich aus einem Defaultkonstruktor einen überladenen Konstruktor aufrufen, wenn meine Klasse generisch ist?


class Foo<T>
{
   Foo(string name) : this(name, ???)
   {}

   Foo(string name, ref T obj){}
}

Und zwar sollte beim Aufruf obj = null sein, wenn das möglich ist.

D
96 Beiträge seit 2012
vor 10 Jahren
  1. Ohne Type-Constraints kann obj nicht null sein, da T auch ein Wertetyp sein kann.
  2. Ohne ein Feld oder eine lokale Variable kannst du keine ref-Übergabe machen.
  3. Ohne ref könntest du default(T) nutzen oder, falls du ein class-Constraint einbaust, auch direkt null.
brainwave Themenstarter:in
436 Beiträge seit 2007
vor 10 Jahren

Somit keine Chance den Konstruktor so aufzurufen?

16.807 Beiträge seit 2008
vor 10 Jahren

Was ist denn T bzw. was solls werden?
Wenns nen einfaches Objekt ist wirds doch sowieso by reference übergeben -> ref-Einschränkung also unnötig.
Siehe Verdeutlichung in meinem nächsten Post.

Aber wenns so bleiben soll: nein, so direkt nicht möglich.

L
168 Beiträge seit 2008
vor 10 Jahren

Was ist denn T bzw. was solls werden?
Wenns nen einfaches Objekt ist wirds doch sowieso by reference übergeben -> ref-Einschränkung also unnötig.

Wäre mir neu. Standardmäßig wird alles by Value übergeben, d.h. bei "einfachen" Objekten wird eine Kopie der Referenz übergeben.

16.807 Beiträge seit 2008
vor 10 Jahren

Ja, prinzipiell wird alles bei C# call by value übergeben; hab mich evtl. undeutlich ausgedrückt.
Trotzdem wird bei einer Objekt-Übergabe (=>Referenztyp) die Referenz übergeben => reference will be passed by value.

Siehe dazu auch Referenztyp mit ref Parameter übergeben? Sinnlos? [==> es gibt sinnvolle Fälle] und [Artikel] Parameter-Übergabemechanismen: call by value vs. call by reference (ref/out)

brainwave Themenstarter:in
436 Beiträge seit 2007
vor 10 Jahren

Die Objekte werden hier als Referenz übergeben, da das Originalobjekt verändert wird.

16.807 Beiträge seit 2008
vor 10 Jahren

Ja, deswegen die Frage: wieso willst Du ref nutzen?
Brauchst Du das wegen eines der in meinem verlinkten Thread vorkommenden Beispiele _für _ref?
Ansonsten kannst es ja sparen...

brainwave Themenstarter:in
436 Beiträge seit 2007
vor 10 Jahren

Das war die Antwort auf deine Frage...ich muss das Objekt als ref übergeben, da das Original verändert wird. Anders wird beim Ändern eine Referenzkopie angelegt.

Es wird ja grundsätzlich immer der Zeiger übergeben und keine Kopie des Objektes. Aber beim ändern des Objektes muss doch eine Kopie angelegt werden oder täusche ich mich?

16.807 Beiträge seit 2008
vor 10 Jahren

Ich glaub Du hast es nicht verstanden, wie das mit Referenztypen funktioniert, oder?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        public class Person
        {
            public Person(String name)
            {
                this.Name = name;
            }
            public String Name { get; set; }
        }

        public static class PersonHelpers
        {
            public static void SetName(Person p, string neuerName)
            {
                p.Name = neuerName;
            }

            public static void SetName(ref Person p, string neuerName)
            {
                p.Name = neuerName;
            }

            public static void SetName(string name, string neuerName)
            {
                name = neuerName;
            }
            public static void SetName(ref string name, string neuerName)
            {
                name = neuerName;
            }
        }


        static void Main(string[] args)
        {
            Console.WriteLine("BSP1: " + Environment.NewLine);
            // Referenztyp

            var p = new Person("Peter");
            Console.WriteLine("Name: " + p.Name);

            // Name Ändern über Methode ohne Ref:
            PersonHelpers.SetName(p, "Harald");
            Console.WriteLine("Name: " + p.Name);

            // Name Ändern über Methode mit Ref:
            PersonHelpers.SetName(ref p, "Claudia");
            Console.WriteLine("Name: " + p.Name);

            Console.WriteLine(Environment.NewLine + Environment.NewLine + "BSP2: " + Environment.NewLine);
            // String
            string name = "Peter";
            Console.WriteLine("Name: " + name);

            // Name Ändern über Methode mit Ref:
            PersonHelpers.SetName(name, "Harald");
            Console.WriteLine("Name: " + name);

            // Name Ändern über Methode ohne Ref:
            PersonHelpers.SetName(ref name, "Claudia");
            Console.WriteLine("Name: " + name);

            Console.ReadKey();
        }
    }
}

Ausgabe:
`BSP1:

Name: Peter
Name: Harald
Name: Claudia

BSP2:

Name: Peter
Name: Peter
Name: Claudia`

Fazit:
ref beim Objekt nicht nötig, aber u.a. beim String sehr wohl.

Fällt Dir was auf?
Wenn nicht: nochmal ein Blick in die Links, die ich oben genannt habe.

brainwave Themenstarter:in
436 Beiträge seit 2007
vor 10 Jahren

Hat jetzt nichts direkt hiermit was zutun aber indirekt schon 😃

Eventuell liegt es am Typ byte[] beim Aufruf der Methode SerializeObjectToBuffer<byte[]>(ref obj, objSize, out buffer); muss obj als ref übergeben werden, ansonsten wird der Speicher nicht aufgeräumt nach dem die Methode verlassen wird.


class Program
    {
        static void Main(string[] args)
        {
            byte[] obj = new byte[262144000];
//Mem: ~250MB
            new Random().NextBytes((byte[])obj);

            byte[] buffer;
            SerializeObjectToBuffer<byte[]>(ref obj, objSize, out buffer);
//Mem: ~250MB
            object objout;
            Deserialize<object>(ref buffer, out objout);

            GC.Collect();
            GC.WaitForPendingFinalizers();
//Mem: ~250MB
        }

        private static void SerializeObjectToBuffer<T>(ref T obj, long objSize, out byte[] buffer)
        {
            using (MemoryStream memStream = new MemoryStream((int)objSize))
            {
                new BinaryFormatter().Serialize(memStream, obj);
//Mem: ~500MB
                obj = default(T);
                GC.Collect();
                GC.WaitForPendingFinalizers();
//Mem: ~250MB
                buffer = memStream.GetBuffer();
                memStream.Close();
            }
        }

        private static void Deserialize<T>(ref byte[] buffer, out T obj)
        {
            using (MemoryStream mem = new MemoryStream(buffer, false))
            {
                obj = (T)new BinaryFormatter().Deserialize(mem);
//Mem: ~500MB
                mem.Close();
                mem.Dispose();
            }
            buffer = null;
        }
    }

16.807 Beiträge seit 2008
vor 10 Jahren

Darf man fragen, was das werden soll? Was hast Du konkret mit diesem Code vor?

brainwave Themenstarter:in
436 Beiträge seit 2007
vor 10 Jahren

Hab den Code um ein paar Kommentare ergänzt (aktueller Prozessspeicher). Relevant war für mich zu testen bzw. herauszufinden, dass beim Ser/Deserialisieren von Objekten der Speicher nicht
unnötig aufgebläht wird. Das ist natürlich nur eine Demo.

16.807 Beiträge seit 2008
vor 10 Jahren

Und warum serialisierst Du nicht einfach direkt in das Objekt, statt ein ByteArray zu verwenden (wie es auch normal wäre)?

        public T DeserializeToObject<T>(byte[] bytes)
        {
            using (var memStream = new MemoryStream(bytes))
            {
                var bFormatter = new BinaryFormatter();
                {
                    var obj = (T)bFormatter.Deserialize(memStream);
                    return obj;
                }
            }
        }

Dass beim Erstellen von Objekten Speicher allokiert wird, ist ja völlig normal und okay. Klar, dass der steigt je mehr Objekte Du erstellst.
Daher nicht alle bytes auf einmal einlesen, sondern sequentiell lesen. Aktuell allokierst Du sofort 250 MB.

brainwave Themenstarter:in
436 Beiträge seit 2007
vor 10 Jahren

Ziel war jedes Object (hier obj(byte[])) in einem Buffer (byte[]) und umgekehrt zu serialisieren.

Foo obj;
byte[] objBuffer = GetBuffer(obj);
..
..
mach irgendwas anderes...
..
..
Foo obj = GetObject(objBuffer);

wobei objBuffer zwischengespeichert wird

Wie gesagt, es soll nur die Größe jedes Objektes demonstrieren. Hier sind es 250MB. Kann auch ein X-beliebiges Objekt sein..

16.807 Beiträge seit 2008
vor 10 Jahren

Tut mir leid. Ich versteh den Sinn des Vorhabens nicht (und auch kein Zwischenziel) dadurch kann ich Dir keinen besseren Vorschlag machen.

brainwave Themenstarter:in
436 Beiträge seit 2007
vor 10 Jahren

Eigentlich wollte ich damit nur zeigen, weshalb ich das Objekt als "ref" übergebe.. 😃

16.807 Beiträge seit 2008
vor 10 Jahren

Ja, aber evtl. kann mein Dein ganzes Vorhaben verbessern, sodass Du ref gar nicht brauchst ((was ich eben vermute)

brainwave Themenstarter:in
436 Beiträge seit 2007
vor 10 Jahren

Mein ganzes Vorhaben zu beschreiben wäre etwas zu komplex, deshalb nur eine kleine Demonstration. Ziel ist es Objekte effizient zu serialisieren und vor allem sie Zeitnah aus dem Speicher zu entladen.

16.807 Beiträge seit 2008
vor 10 Jahren

Klingt ziemlich nach (premature optimization is the root of all evil

Weil effizient ist das, was Du da machst nicht. Und aus dem Speicher nimmt der GC normal serialisierte Objekte ja auch erst, wenn wirklich keine Referenz mehr auf dem Objekt besteht.
Sehe also keinerlei Vorteil in Deiner Variante; nur die gesteigerte Komplexität

Wenn Du eine Lösung hast, die wirklich effizienter sein sollte, so poste sie bitte hier, damit die Nachwelt was davon hat.

5.742 Beiträge seit 2007
vor 10 Jahren

Bezogen auf die Eingangsfrage:
Rein von der Syntax geht das mithilfe eines kleines Tricks:


class Foo<T>
{
    Foo(string name)
        : this(name, ref new Helper<T>().obj)
    { }

    Foo(string name, ref T obj) { }
}

class Helper<T>
{
    public T obj;
}

2.078 Beiträge seit 2012
vor 10 Jahren

Mal so nebenbei erwähnt:

Ich halte es für unpassend, im Konstruktor ref zu nutzen.
Ob das in diesem konkreten Fall nun sinnvoll ist, oder nicht, sei mal dahin gestellt, aber wenn ich einen Konstruktor verwende, erwarte ich nicht, dass meine Referenz, die ich übergebe, auf einmal komplett überschrieben wird und auf ganz ein anderes Objekt verweist.

Ich weiß nicht, ob das jetzt so verständlich rüber kam, aber ein Konstruktor hat in meinen Augen nur die Aufgabe, das Objekt für die Verwendung entsprechend und grundlegend vorzubereiten. Also z.B. eine private Liste zu initialisieren, damit sie genutzt werden und das Objekt seinen Dienst erfüllen kann.

Alles, was darüber hinaus geht, gehört in meinen Augen nicht mehr in einen Konstruktor, sondern in eine separate Methode. Sollte eine Klasse Daten aus einer Daten-Quelle lesen müssen/sollen, so landet das bei mir auch nicht im Konstruktor, sondern in einer separaten Methode oder gar in einer anderen Klasse, die dann das Daten-Objekt befüllen kann.

Außerdem versuche ich ein Objekt, das ich als Parameter bekomme, nur dann zu ändern, wenn diese Änderung auch wirklich im Anforderungsbereich der Methode liegt. Die Methode soll nur ihre Aufgabe erfüllen und nichts darüber hinaus. Sie soll also auch die Parameter in Ruhe lassen, außer es ist zwingend erforderlich oder gewünscht, dass ein Parameter als Referenz behandelt und auch abgeändert wird, aber das soll dann entweder aus dem Namen zu erkennen oder in der Dokumentation nachzulesen sein.

Das Gleiche denke ich über out. Das "Ergebnis" von einem Konstruktor ist das initialisierte Objekt und nichts weiter. Da gehört auch keine Berechnung hinein, weshalb ein zusätzliches Ergebnis, das über out abzurufen wäre, gar nicht in Frage kommen sollte.

Vielleicht sehe ich das etwas streng oder gar falsch, aber das ist meine Meinung und von daher tut mir allein der Anblick von ref im Konstruktor schon in den Augen weh.

Selbst wenn es in diesem Fall sinnvoll ist, dann suche ich lieber nach einem alternativen Weg, wie z.B. eine Methode "Initialice", oder besser ein spezifisch angepasster Name, der dann halt noch eine weitere Aufgabe hat, die nicht im Konstruktor steht.

Diese Denkweise ist aus dem Single responsibility principle entstanden, das ich für mich auch auf Methoden übertragen habe. Jede Methode übernimmt exakt eine Aufgabe, nicht mehr, nicht weniger.
Sind es mehr Aufgaben, teile ich die Methode wenn Möglich auf.

Hinweis von herbivore vor 10 Jahren

... aber wenn ich einen Konstruktor verwende, erwarte ich nicht, dass meine Referenz, die ich übergebe, auf einmal komplett überschrieben wird und auf ganz ein anderes Objekt verweist.

Egal ob normale Methode oder Konstruktor: Wo immer ref bei der Signatur auftaucht, muss man es auch beim Aufruf angeben, sonst gibt es einen Syntaxfehler. Man kann also nicht wirklich davon überrascht werden. Das ändert allerdings nichts an der restlichen Argumentationskette.