c# im dritten Anlauf



  • pascal2009 schrieb:

    Warum das nicht gleich klar war, weiß ich nicht. Ich kann es mir im nachhinein nicht erklären

    Falls das nicht nur eine rhetorische Frage war, sondern ernstgemeint... Nun hier findet sich ein Hinweis:

    pascal2009 schrieb:

    Ich hab mir jetzt statt learning by doing doch tatsächlich ein Buch gekauft. Frischalowski Visual C# Einstieg für Anspruchsvolle. Bin jetzt bei Seite 98 angekommen.

    Soso, Du glaubst also keine Bücher zu brauchen weil Du Dich für etwas Besseres hälst. Und wenn Du Dich schon (als absoluter Anfänger) dazu herablässt ein Buch zu kaufen, dann _muss_ es ja gleich etwas für Anspruchsvolle sein um Deinem überlegenen Intellekt zu schmeicheln. Daher musste es ja auch zwangsläufig an der Sprache liegen (C#) denn an Dir konnte es ja nicht liegen gelle?

    Ich formulierte es schonmal so: Der Grad der Selbstüberschätzung ist zumeist umgekehrt proportional zum Kenntnisstand eines Programmierers.



  • pascal2009 schrieb:

    In der .NET Bibliothek finden sich ganz feine Sachen wie ArrayList.

    Vergiss das Ding wieder. Nimm eine List<T>. ArrayList ist ein Überbleibsel aus .NET 1.1 Zeiten.



  • loks schrieb:

    Ich formulierte es schonmal so: Der Grad der Selbstüberschätzung ist zumeist umgekehrt proportional zum Kenntnisstand eines Programmierers.

    Du kannst nach ein paar Wochen unregelmäßiger Beschäftigung mit dem Thema nicht zuviel erwarten. Gesunde Selbsteinschätzung führt auch dazu, daß man sich mit der nötigen Leidensbereitschaft durcharbeitet und nicht gleich aufgibt.

    Also, der Kenntnisstand C# ist einige Wochen damit beschäftigt. Eine ganze Menge Dinge sind schon klar, insbesondere mit der GUI Forms und WPF komme ich ganz gut zurecht. Vieles ist aber noch unklar. Es fehlt an systematischen Kenntnissen der Zusammenhänge. Dazu lese ich ja dieses Buch, z. B.

    Hier ist ein Stück Code, was der Compiler nimmt, was aber einen Laufzeitfehler auslöst: Null-Exception nicht behandelt. Das kommt aus der i-Schleife heraus. Ursache ist mir nicht klar. Public muß sein, weil ich die Daten aus dem Class-Array Land[] an anderer Stelle auslesen will. Falls jemand einen freundlichen Hinweis dazu hätte, danke.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Domination_WinForms
    {
        public class CL_World
        {
            public const int MAX_NACHBARN = 4;
            public const int MAX_LÄNDER = 3;
    
            public class CL_Land
            {
                public int nr;
                public string name;
                public int gehört_zu_spieler;
                public int farbe;
                public int armeen;
                public int[] nachbar;
            }
    
            public CL_Land[] Land = new CL_Land[MAX_LÄNDER]; 
    
            public bool init_Länder()
            {
                for (int i = 0; i < MAX_LÄNDER; i++)
                {
                    Land[i] = new CL_Land();
                    Land[i].nr = i;
                    for (int k=0;k<MAX_NACHBARN;k++)Land[i].nachbar[k]=-1; // ausnullen
                } 
                            Land[0].name = "England";
                            Land[0].nachbar[0] = 1;
                            Land[0].nachbar[1] = 2;
    
                            Land[1].name = "Schottland";
                            Land[1].nachbar[0] = 0;
    
                            Land[2].name = "Belgien";
                            Land[2].nachbar[0] = 0;
    
                            return true;
    
            } // init Länder
    
        } // class CL_World
    
    } // namespace
    


  • nachbar ist null.

    Mit dem Debugger hättest du das sofort gefunden.



  • Bashar schrieb:

    nachbar ist null.

    Mit dem Debugger hättest du das sofort gefunden.

    Entschärfte Version, Array Nachbar[] rausgenommen.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Domination_WinForms
    {
        public class CL_World
        {
            public const int MAX_LÄNDER = 3;
    
            public class CL_Land
            {
                public int nr;
                public string name;
    //            public int gehört_zu_spieler;
    //            public int farbe;
    //            public int armeen;
                public int nachbar1;
                public int nachbar2;
                public int nachbar3;
            }
    
            public CL_Land[] Land = new CL_Land[MAX_LÄNDER]; 
    
            public bool init_Länder()
            {
                for (int i = 0; i < MAX_LÄNDER-1; i++)
                {
                    Land[i] = new CL_Land();
                    Land[i].nr = i;
                    Land[i].name = ""; // ausnullen
                    Land[i].nachbar1 = -1;
                    Land[i].nachbar2 = -1;
                    Land[i].nachbar3 = -1;
    
                } 
                            Land[0].name = "England";
                            Land[0].nachbar1 = 1;
                            Land[0].nachbar2 = 2;
    
                            Land[1].name = "Schottland";
                            Land[1].nachbar1 = 0;
    
                            Land[2].name = "Belgien";
                            Land[2].nachbar1 = 0;
    
                            return true;
    
            } // init Länder
    
        } // class CL_World
    
    } // namespace
    

    Laufzeitfehler: Null ReferenceException wurde nicht behandelt.

    Debugger sagt:

    [0].name = England etc. die anderen Felder alles ok.
    [1].name = Schottland etc. ok.
    [2] ist null.

    Datensatz mit Index 2 ist also insgesamt Null. Warum jetzt 0+1 funktionieren und Nr. 2 nicht, nun ja. Wäre wirklich interessant zu wissen, warum.

    Sorry, Fehler gefunden es ist

    for (int i = 0; i < MAX_LÄNDER-1; i++)
    

    Da hängt noch eine -1. Aus dem Austesten.
    So geht´s natürlich nicht. 🤡

    Aber wenn ich ein geschachteltes Array mit gemischten Typen deklariere, muß ich für die geschachtelten Array mit new wieder separat Speicher anfordern? Also Speicher innerhalb des Speichers?

    Pascal



  • pascal2009 schrieb:

    Aber wenn ich ein geschachteltes Array mit gemischten Typen deklariere, muß ich für die geschachtelten Array mit new wieder separat Speicher anfordern? Also Speicher innerhalb des Speichers?

    Ja.

    Statt rohe Arrays solltest Du die schon mehrfach empfohlenen Container verwenden. List<T> ist dabei Allzweckwaffe.

    List<CL_Land> Land = new List<CL_Land>();
    
    CL_Land land = new CL_Land();
    land.name = "Belgien";
    Land.Add(land);
    

    Falls der Vorteil nicht sofort ersichtlich ist: Diese Container wachsen dynamisch. Überbleibsel im C-Stil wie Arrays fester Größe und irgendwelchen MAX_LÄNDER Konstanten sind nicht nötig.



  • µ schrieb:

    pascal2009 schrieb:

    Aber wenn ich ein geschachteltes Array mit gemischten Typen deklariere, muß ich für die geschachtelten Array mit new wieder separat Speicher anfordern? Also Speicher innerhalb des Speichers?

    Ja.

    Statt rohe Arrays solltest Du die schon mehrfach empfohlenen Container verwenden. List<T> ist dabei Allzweckwaffe.

    List<CL_Land> Land = new List<CL_Land>();
    
    CL_Land land = new CL_Land();
    land.name = "Belgien";
    Land.Add(land);
    

    Falls der Vorteil nicht sofort ersichtlich ist: Diese Container wachsen dynamisch. Überbleibsel im C-Stil wie Arrays fester Größe und irgendwelchen MAX_LÄNDER Konstanten sind nicht nötig.

    Das werde ich mir mal zu Gemüte führen. Danke für den Hinweis.

    Feste Arrays sind dann Ballast, wenn man die Datenmenge nicht kenn. Wenn es sich aber wie bei dem Brettspiel Risiko um 42 Länder handelt und um 4 Spieler, kann man drüber wegsehen.

    Als learner by doer oder try and error hab ich den Code jetzt doch zum Laufen gebracht. Das ist allerdings wirklich nicht elegant, mit new innerhalb der Klasse für jedes Array nochmal Speicher anzufordern. Wie gesagt, ich werde mir das mit List<T> mal zu Gemüte führen.

    So läuft es jedenfalls fehlerfrei (man bemüht sich, sich in den Compiler hineinzudenken 🤡 ):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Domination_WinForms
    {
        public class CL_World
        {
            public const int MAX_LÄNDER = 3;
            public const int MAX_NACHBARN = 4;
    
            public class CL_Land
            {
                public int nr;
                public string name;
    //            public int gehört_zu_spieler;
    //            public int farbe;
    //            public int armeen;
                public int[] nachbar;
            }
    
            public CL_Land[] Land = new CL_Land[MAX_LÄNDER]; 
    
            public bool init_Länder()
            {
                for (int i = 0; i < MAX_LÄNDER; i++)
                {
                    Land[i] = new CL_Land();
                    Land[i].nr = i;
                    Land[i].name = ""; // ausnullen
                    Land[i].nachbar=new int[MAX_NACHBARN];
                    for (int k = 0; k < MAX_NACHBARN; k++)
                    {
                        Land[i].nachbar[k] = new int();
                        Land[i].nachbar[k] = -1;
                    }
                } 
                            Land[0].name = "England";
                            Land[0].nachbar[0] = 1;
                            Land[0].nachbar[1] = 2;
    
                            Land[1].name = "Schottland";
                            Land[1].nachbar[0] = 0;
    
                            Land[2].name = "Belgien";
                            Land[2].nachbar[0] = 0;
    
                            return true;
            } // init Länder
        } // class CL_World
    } // namespace
    

    Und zeigt dann auf dem Hauptbildschirm der Form1 sehr schön die Länder mit den Daten an.

    Ich sehe es aus der AnsiC Perspektive so, daß mit dem

    public CL_Land[] Land = new CL_Land[MAX_LÄNDER]; sowie
    Land[i].nachbar=new int[MAX_NACHBARN]; für das eingeschlossene Array

    jeweils Zeiger auf den Startpunkt bzw. die Elemente der Array-Liste bereitgestellt werden, aber kein Speicherplatz für die Elemente bereitsteht, die man dann für jedes Element wieder neu anfordern muß mit:

    Land[i] = new CL_Land(); sowie für die eingeschlossenen Array-Elemente mit:
    Land[i].nachbar[k] = new int();

    Das ist verglichen mit AnsiC wirklich etwas heftig, muß man überhaupt erstmal drauf kommen. Man kommt dahin, wenn man die (kritischen) Programmelemente stufenweise ausklammert. Dann bekommt man einen Hinweis, wo der Hase im Gebüsch sitzen könnte.

    Pascal



  • new int() ist nicht notwendig. Für Wertetypen muss man den Speicher nicht mit new besorgen.

    Das ist allerdings wirklich nicht elegant, mit new innerhalb der Klasse für jedes Array nochmal Speicher anzufordern.

    Stimmt. Momentan ist Deine Klasse aber nicht mehr als ein Struct im C-Stil. Die Eleganz kommt noch, lies einfach mal weiter.



  • µ schrieb:

    new int() ist nicht notwendig. Für Wertetypen muss man den Speicher nicht mit new besorgen.

    Das ist allerdings wirklich nicht elegant, mit new innerhalb der Klasse für jedes Array nochmal Speicher anzufordern.

    Stimmt. Momentan ist Deine Klasse aber nicht mehr als ein Struct im C-Stil. Die Eleganz kommt noch, lies einfach mal weiter.

    Du hast recht, wenn man das rausnimmt, läuft es auch. Irgendwie klar, daß man für Wertetypen keinen Speicher auf dem Heap anfordert. Ogottogott 🤡 Sind eben Übergangsprobleme.

    Ich hoffe, deine Prognose bzgl. der Eleganz wird eintreffen. 😉 Ich arbeite dran.

    Pascal



  • pascal2009 schrieb:

    Du hast recht, wenn man das rausnimmt, läuft es auch. Irgendwie klar, daß man für Wertetypen keinen Speicher auf dem Heap anfordert. Ogottogott 🤡 Sind eben Übergangsprobleme.

    Achtung!
    new ist in C# erstmal nur Syntax.
    new heisst nicht unbedingt dass Speicher vom managed Heap besorgt wird!
    Es heisst nur: hier wird ein neues Objekt erzeugt/initialisiert.

    Wenn der verwendete Typ ein Value-Type (short, int, long, ..., DateTime, TimeSpan, eigene structs, ...) ist, dann wird nix vom Heap genommen.

    Für Value-Types wird nur was vom Heap genommen, wenn sie "boxed" werden. Was man allerdings nicht dadurch erreicht dass man "new" schreibt, sondern indem man sie als "object" behandelt/übergibt.

    Wenn der verwendete Typ dagegen ein Reference-Type ist ("class"), dann schon.



  • Um deinen Code mal ein bisschen zu verschönern:

    public class CL_World
        {
            public class CL_Land
            {
                //public int gehört_zu_spieler; 
                //public int farbe; 
                //public int armeen
                public string name;
                public int[] nachbar;
    
                public CL_Land(string name, int[] nachbar)
                {
                    this.name = name;
                    this.nachbar = nachbar;
                }
            }
    
            public CL_Land[] Land;
    
            public CL_World()
            {
                Land = new CL_Land[3];
                Land[0] = new CL_Land("England", new int[] { 1, 2 });
                Land[1] = new CL_Land("Schottland", new int[] { 0 });
                Land[2] = new CL_Land("Belgien", new int[] { 0 });
    
            } // init Länder 
        } // class CL_World
    

    Für Arrays fixer Größer nimmst du natürlich auch in C# Arrays, alles andere ist Unsinn.
    Falls du CL_Land statt Class zu einer Struct machen würdest, müsstest du nicht für jedes Element extra Speicher anfordern (hat aber dann andere Nachteile). Wenn du jetzt aber die Länder direkt über den Konstruktor füllst (wie es gedacht ist) dann macht das "extra Speicher reservieren müssen" auch nix mehr. 😉



  • hustbaer schrieb:

    pascal2009 schrieb:

    Du hast recht, wenn man das rausnimmt, läuft es auch. Irgendwie klar, daß man für Wertetypen keinen Speicher auf dem Heap anfordert. Ogottogott 🤡 Sind eben Übergangsprobleme.

    Achtung!
    new ist in C# erstmal nur Syntax.
    new heisst nicht unbedingt dass Speicher vom managed Heap besorgt wird!
    Es heisst nur: hier wird ein neues Objekt erzeugt/initialisiert.

    Wenn der verwendete Typ ein Value-Type (short, int, long, ..., DateTime, TimeSpan, eigene structs, ...) ist, dann wird nix vom Heap genommen.

    Ich denke, ein Wert-Typ wird einfach auf den Stack gepackt.

    Mit class x // x cx = new x(); wird ein Zeiger erzeugt, der auf die Startadresse des Objekts(der Klasse) verweist. Ob der Zeiger auf dem Stack abgelegt ist oder im Heap, weiß ich nicht. Stack würde ja Sinn machen, um ihn danach wieder runterzunehmen. Der ganze Speicher für die Elemente der Liste muß vom Heap kommen, weil er ja wahlfrei ist und das dynamisch organisiert werden muß.

    Ich hab zu List<T>mal einen kleinen Test probiert. Besteht aus Performance Test im Vergleich Array-List. Zugleich hab ich noch den Speicher ausgelesen, dieser Teil ist aber hier deaktiviert.

    using System.Linq;
    using System.Text;
    using System.Collections;
    using System.Diagnostics;
    
    namespace Test
    {
        using System;
    using System.Collections.Generic;
    
        class Program
            {
    /* alles unwichtig, die Speicherauslesung deaktiviert
            class RAM
            {
                public void zeige_RAM()
                {
                    long lr;
                    Process currentProcess = Process.GetCurrentProcess();
                    //To get the current process and use:
                    lr = currentProcess.PrivateMemorySize64/1024;
                    Console.WriteLine(lr.ToString());
                    Console.ReadKey();
                }
            }
    
            class diag
            {
                // Define variables to track the peak 
                // memory usage of the process. 
                long peakPagedMem = 0,
                    peakWorkingSet = 0,
                    peakVirtualMem = 0;
    
                public void zeige_Diag()
                {
                    Process myProcess = Process.GetCurrentProcess();
                    // Refresh the current process property values.
                    myProcess.Refresh();
    
                    Console.WriteLine();
    
                    // Display current process statistics.
    
                    Console.WriteLine("{0} -", myProcess.ToString());
                    Console.WriteLine("-------------------------------------");
    
                    Console.WriteLine("  physical memory usage: {0}",
                        myProcess.WorkingSet64/1024);
                    Console.WriteLine("  base priority: {0}",
                        myProcess.BasePriority);
                    Console.WriteLine("  priority class: {0}",
                        myProcess.PriorityClass);
                    Console.WriteLine("  user processor time: {0}",
                        myProcess.UserProcessorTime);
                    Console.WriteLine("  privileged processor time: {0}",
                        myProcess.PrivilegedProcessorTime);
                    Console.WriteLine("  total processor time: {0}",
                        myProcess.TotalProcessorTime);
                    Console.WriteLine("  PagedSystemMemorySize64: {0}",
                        myProcess.PagedSystemMemorySize64 / 1024);
                    Console.WriteLine("  PagedMemorySize64: {0}",
                       myProcess.PagedMemorySize64 / 1024);
    
                    Console.ReadKey();
    
                } // RAM
            } // Class DIAG
    
     */ 
    
                static void Main(string[] args)
                {
                    int t1, t2;
                    int dummy;
                    int zeit = 0;
                    string str = "Das ist das Haus vom Nikolaus";
                    string buf = "";
                    ArrayList a1 = new ArrayList();
                    List<int> list1 = new List<int>();
                    t1 = Environment.TickCount;
                    for (int i = 0; i < 1000000; i++)
                        for (int k = 0; k < 5; k++)
                            list1.Add(i);
    
                    for (int i = 0; i < 1000000; i++)
                        for (int k = 0; k < 5; k++)
                            dummy = list1[i];
    
                    t2 = Environment.TickCount;
                    zeit = t2 - t1;
                    Console.WriteLine("Dauer für List: " + zeit.ToString());
    
                    t1 = Environment.TickCount;
                    for (int i = 0; i < 1000000; i++)
                        for (int k = 0; k < 5; k++)
                            a1.Add(i);
                    for (int i = 0; i < 1000000; i++)
                        for (int k = 0; k < 5; k++)
                            dummy = (int)a1[i];
    
                    t2 = Environment.TickCount;
                    zeit = t2 - t1;
                    Console.WriteLine("Dauer für ArrayList: " + zeit.ToString());
    
                    ArrayList a2 = new ArrayList();
                    List<string> list2 = new List<string>();
                    t1 = Environment.TickCount;
                    for (int i = 0; i < 1000000; i++)
                        for (int k = 0; k < 5; k++)
                            list2.Add(str);
                    for (int i = 0; i < 1000000; i++)
                        for (int k = 0; k < 5; k++)
                            buf = list2[i];
    
                    t2 = Environment.TickCount;
                    zeit = t2 - t1;
                    Console.WriteLine("Dauer für String List: " + zeit.ToString());
    
                    t1 = Environment.TickCount;
                    for (int i = 0; i < 1000000; i++)
                        for (int k = 0; k < 5; k++)
                            a2.Add(str);
                    for (int i = 0; i < 1000000; i++)
                        for (int k = 0; k < 5; k++)
                            buf = (string)a2[i];
    
                    t2 = Environment.TickCount;
                    zeit = t2 - t1;
                    Console.WriteLine("Dauer für ArrayList mit String: " + zeit.ToString());
                    Console.ReadKey();
                } // main
            } // class program
    } // namespace
    

    Ergebnis:

    Die Klasse list ist bei Integer um den Faktor 5-6 schneller als ArrayList. Das kommt sicher daher, daß beim Zurücklesen eine Typumwandlung erfolgen muß (Cast).

    Bei Strings ist allerdings ArrayList mindestens genausoschnell. Warum?

    Ich weiß es nicht.

    Würde ich aber gern wissen. Falls von den Strings nur die Speicheradresse gespeichert wird, wäre das ja auch ein INT32, und die Rückumwandlung müßte dieselbe Zeit beanspruchen wie für den Wertetyp INT. Tut sie aber nicht.

    Mein schlaues Buch sagt, ArrayList ist nichtgenerisch. List dagegen ist generisch (Typgebunden), was eine Reihe von Vorteilen hat bzgl. der Sicherheit der Codierung mit sich bringen soll. Nun, von Ansi-C aus gesehen, ist der Vorteil so nicht ganz nachzuvollziehen, wenn wir eine Datei binär speichern, egal von welchem Typ, kann man die ohne den passenden struct/Typ sowieso nicht auslesen, und von welchem Typ die Binärdatei stammt, sieht man ja in der Speicherfunktion (allerdings, ja, das ist ein Fortschritt, nicht im intellisense, den gibt es in Ansi C nicht, und der ist wirklich nicht zu verachten. Daher ist in C# wohl List der Arraylist vorzuziehen.

    Insgesamt gesehen, hab ich noch was Delegates, Klassenvererbung und viele andere Sachen betrifft

    ehrlich gesagt

    sehr große Verständnisprobleme. Ich frag mich dabei: gut, das gibt es. Aber wer braucht das und wozu soll das nütze sein? Wenn ich keinen Nutzen sehe, kann ich mich schlecht reindenken.

    Aber was solls, da muß man durch.

    Pascal



  • DarkShadow44 schrieb:

    Um deinen Code mal ein bisschen zu verschönern:

    public class CL_World
        {
            public class CL_Land
            {
                //public int gehört_zu_spieler; 
                //public int farbe; 
                //public int armeen
                public string name;
                public int[] nachbar;
    
                public CL_Land(string name, int[] nachbar)
                {
                    this.name = name;
                    this.nachbar = nachbar;
                }
            }
    
            public CL_Land[] Land;
    
            public CL_World()
            {
                Land = new CL_Land[3];
                Land[0] = new CL_Land("England", new int[] { 1, 2 });
                Land[1] = new CL_Land("Schottland", new int[] { 0 });
                Land[2] = new CL_Land("Belgien", new int[] { 0 });
    
            } // init Länder 
        } // class CL_World
    

    Für Arrays fixer Größer nimmst du natürlich auch in C# Arrays, alles andere ist Unsinn.
    Falls du CL_Land statt Class zu einer Struct machen würdest, müsstest du nicht für jedes Element extra Speicher anfordern (hat aber dann andere Nachteile). Wenn du jetzt aber die Länder direkt über den Konstruktor füllst (wie es gedacht ist) dann macht das "extra Speicher reservieren müssen" auch nix mehr. 😉

    So allmählich krieg ich die Vorstellung, wie C# Code aussehen sollte. 😉

    Sieht wirklich gut aus.

    Aber wie lese ich aus, ob es nun einen Nachbarn gibt oder viele?

    Die Elemente sind mit {} ja nur teilweise und unterschiedlich befüllt.

    Wie ist die Abbruchbedingung bei der Abfrage, wieviele Nachbarn es gibt? (ich hatte die ja daher in meinem Code mit -1 befüllt, weil 0 ja auch ein gültiger Wert wäre für ein Nachbarland).

    Wäre es bei max 4 Nachbarn vielleicht besser so: {1,2,-1,-1} ?

    Pascal



  • Die Klasse list ist bei Integer um den Faktor 5-6 schneller als ArrayList. Das kommt sicher daher, daß beim Zurücklesen eine Typumwandlung erfolgen muß (Cast).

    Bei Strings ist allerdings ArrayList mindestens genausoschnell. Warum?

    Ich weiß es nicht.

    Würde ich aber gern wissen. Falls von den Strings nur die Speicheradresse gespeichert wird, wäre das ja auch ein INT32, und die Rückumwandlung müßte dieselbe Zeit beanspruchen wie für den Wertetyp INT. Tut sie aber nicht.

    Ist ansich ganz einfach. Bei der generischen List können Werttypen direkt in der List gespeichert werden. Bei der nicht generischen ist Boxing nötig. D.h. es wird für jeden kleinen int ein neues Objekt auf dem Heap angelegt. Ergo: langsam.
    Bei strings ist das nicht der Fall, die sind so oder so Referenztypen. Ergo auch nicht viel langsamer. Nur die Typchecks sind noch nötig, weil von object nach string gecastet wird.

    Btw., solche Mikrobenchmarks erfordern die Beachtung einiger Regeln. Z.B. solltest du den GC beachten, und den Releasebuild benchmarken (ohne unterdrückte Optimierungen wenn unter Kontrolle des Debuggers).

    Aber wie lese ich aus, ob es nun einen Nachbarn gibt oder viele?

    Die Elemente sind mit {} ja nur teilweise und unterschiedlich befüllt.

    Das mit den Klammern ist nur schöne Syntax. Das ist die Erstellung eines Arrays.
    Arrays wissen in C# ihre Größe, du kannst also einfach "nachbar.Length" schreiben und hast die Anzahl der Nachbarn. Irgendwelche Spezialwerte um zu signalisieren dass das der letzte Nachbar war sind also nicht nötig. 😉



  • DarkShadow44 schrieb:

    Ist ansich ganz einfach ...

    Btw., solche Mikrobenchmarks erfordern die Beachtung einiger Regeln. Z.B. solltest du den GC beachten, und den Releasebuild benchmarken (ohne unterdrückte Optimierungen wenn unter Kontrolle des Debuggers).

    Hast du wirklich gut erklärt.

    Wie ich allerdings einen "Releasebuild benchmarken" soll, nun ja.

    Hmm ... 🤡

    Pascal



  • Du stellst von "Debug" auf "Release" um und lässt es ohne Debugger laufen ? 😃



  • pascal2009 schrieb:

    Die Klasse list ist bei Integer um den Faktor 5-6 schneller als ArrayList. Das kommt sicher daher, daß beim Zurücklesen eine Typumwandlung erfolgen muß (Cast).

    Bei Strings ist allerdings ArrayList mindestens genausoschnell. Warum?

    Ich weiß es nicht.

    Das liegt daran dass ArrayList immer "object" speichert. Und Value-Typen müssen geboxt werden wenn man sie als "object" rumreicht.
    D.h. es wird für jeden int ein Reference-Type (die "Schachtel" in die der int zum Zwecke der ver-obejktung eingepackt wird) auf dem Managed-Heap erzeugt wenn man den int in die ArrayList tut.
    Deswegen nimmt man auch List<int> - da ist das nämlich nicht so.

    string dagegen ist ein Value-TypeReference-Type, und der kann direkt in der ArrayList gespeichert werden.



  • Da mir hier schon ganz nett geholfen wurde, kommt noch ein kleines Rätsel hinterher. Ist es so, daß man mit List keine struct bzw. Klassenlemenente speichern kann? Wenn nein, was mach ich falsch?

    class CL_Konten
        {
            public class CL_Konto
            {
               public int kontonr;
               public string kontobez;
               public double eroeffnung;
               public double abschluss;
               public char art;
            }
    
            public bool init_Kontenrahmen_version1()
            { 
                 // Todo: Konten aus Datei auslesen und in Liste ablegen
                List <string> kontenliste = new List<string>();
                CL_Konto kbuf = new CL_Konto();
                kbuf.kontonr = 4900;
                kbuf.kontobez = "Sonstige Kosten";
                kontenliste.Add(kbuf.kontobez);
                MessageBox.Show(kontenliste.Count.ToString());
                kbuf.kontobez = "geändert";
                MessageBox.Show(kontenliste[0]); // Ausgabe hier: "Sonstige Kosten" 
                return true;      
            }
            public bool init_Kontenrahmen_version2()
            {
                // Todo: Konten aus Datei auslesen und in Liste ablegen
                List<CL_Konto> kontenliste = new List<CL_Konto>();
                CL_Konto kbuf = new CL_Konto();
                kbuf.kontonr = 4900;
                kbuf.kontobez = "Sonstige Kosten";
                kontenliste.Add(kbuf);
                MessageBox.Show(kontenliste.Count.ToString());
                kbuf.kontobez = "geändert";
                MessageBox.Show(kontenliste[0].kontobez); // Ausgabe:"geändert", also falsch
                // so geht es auch nicht:
                kbuf.kontobez = kontenliste[0].kontobez;
                MessageBox.Show(kbuf.kontobez); // Ausgabe auch hier "geändert", wieder falsch
                return true;
            }
    
        }
    

    Version 1 speichert nur das Textfeld des Datensatzes, ist natürlich Unsinn aber es funktioniert.

    Bei Version 2 (das ist das, was gewünscht ist) geschieht seltsames:
    Der Datensatz wird der Liste übergeben, und diese zeigt auch den richtigen Wert.

    Dann wird der Datensatz geändert, die Änderungen werden aber nicht der Liste übergeben. Trotzdem zeigt die Liste jetzt den geänderten Text.

    Und wenn man das zurückschreiben will, dasselbe. Nimmt man mehrere Datensätze, wird für alle der Wert des letzten zugewiesenen Elements angezeigt, also völlig falsch.

    Die einzig logische Erklärung dafür wäre, daß die Liste die Elemente gar nicht speichert, sondern lediglich einen Adressoperator auf den Speicherplatz von kbuf enthält.

    Sie funktioniert also nicht als Liste, sondern nur als Zeiger. Was mach ich falsch?

    Pascal



  • pascal2009 schrieb:

    Ich denke, ein Wert-Typ wird einfach auf den Stack gepackt.

    Wenn ein Wertetyp ein Feld einer Klasse ist, wird er nicht auf dem Stack gespeichert.
    Konkret: Du hast ein Integer in einer Klasse und legst nun ein Objekt dieser Klasse an. Dann befindet sich der Integer nicht auf dem Stack. Wie sollte das auch funktionieren? Der Stack ist für methodenlokale Wertetypen und Referenzen (nicht Objekte hinter einer Referenz!) und Übergabe- sowie Rückgabeparameter.
    Aber wie schon gesagt spielt es kaum eine Rolle wo was gespeichert wird. Mit new konstruierst Du ein neues Objekt und für Wertetypen ist das nicht zwingend notwendig.
    string ist eine Ausnahme. Zwar Klasse, aber der Compiler zaubert ein bisschen, damit es sich wie ein Wertetyp anfühlt. So kannst Du einer string-Referenz die null ist ein Literal zuweisen, ohne das Objekt vorher mit new zu konstruieren.



  • pascal2009 schrieb:

    Bei Version 2 (das ist das, was gewünscht ist) geschieht seltsames:
    Der Datensatz wird der Liste übergeben, und diese zeigt auch den richtigen Wert.

    Dann wird der Datensatz geändert, die Änderungen werden aber nicht der Liste übergeben. Trotzdem zeigt die Liste jetzt den geänderten Text.

    Kurz: In der List werden nur die Referenzen gespeichert. Die sind vergleichbar mit Zeigern aus der C und C++ Welt.
    CL_Konto ist eine Klasse und damit Referenztyp. Wenn Du testweise mal CL_Konto zu einer struct (Wertetyp!) umwandelst, wird Deine Änderung an kontobez sich nicht mehr auf das Objekt in der List auswirken.

    Ich sage damit nicht, dass Du generell alles als structs modellieren sollst. Der von Dir beobachtete Effekt ist oft ein Segen und genau das was man möchte. Erfordert aber etwas umdenken, wenn man von C/C++ kommt.

    Nachtrag: Wie im vorherigen Post schon geschrieben ist string eine Ausnahme. Obwohl eine Klasse, beobachtest Du im ersten Test ein anderes Verhalten als im zweiten. string ist ein "immutable reference type" dem zusätzlich ein wenig Compilermagie beim Zuweisungsoperator spendiert wurde. Um den Unterschied zwischen Referenz- und Wertetypen zu verstehen, solltest Du string vielleicht vorerst mal außer acht lassen, denn das Verhalten dieser Klasse passt absolut nicht zum Rest der C#-Welt.


Anmelden zum Antworten