erstes Programm in c#



  • Hi,

    ich habe bisher nur mit C++ programmiert und nun das erste kleine C#
    Programm geschrieben. Ich habe versucht eine lineare Liste zu implementieren
    die Zeichenketten (string) aufnehmen kann. Habe ich irgendetwas fälschlicherweise zu sehr im C++ Stiel geschrieben? Oder generell falsch gemacht?

    Ist die "delete_first" Methode so richtig?
    (Irgend etwas in mir sagt mir permanent da fehlt was, was wohl das "delete" aus c++ wäre 😉 )

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace C_Sharp_Test_Konsole
    {
    
        class ListObject
        {
    
            public ListObject(string s) {content = s;}
    
            public string get_Content(){return content;}
            public ListObject get_next() { return next; }
            public void set_next(ListObject n) { next = n; }
    
            private string content;
            private ListObject next;
    
        }
    
        class LinList
        {
    
            public LinList() { first = null; last = null; }
    
            public void add(string s)
            {
                ListObject myObject = new ListObject(s);
    
                if (first == null)
                {
                    first = myObject;
                    last = myObject;
                }else
    
                {
                    ListObject current = first;
                    while (current != last)
                    {
                        current = current.get_next();
                    }
                    current.set_next(myObject);
                    last = myObject;
    
                 }
            }
    
            public string  get_element_with_Index(int index)
            {
                if (index == 0)
                {
                    return first.get_Content();
                }else
                {
                    ListObject current = first;
                    while ((index != 0) && (current != last))
                    {
                        current = current.get_next();
                        index--;
                    }
                    return current.get_Content();
    
                }         
            }
    
            public string get_first()
            {
                if (first != null)
                {
                    return first.get_Content();
                }
                return "";
    
            }
    
            public string get_last()
            {
                if (last != null)
                {
                    return last.get_Content();
                }
                return "";
    
            }
    
            public void delete_first()
            {
                first = first.get_next();
            }
    
            ListObject first;
            ListObject last;
        }  
    }
    

  • Administrator

    Einiges. Abgesehen davon, dass du natürlich gleich LinkedList verwenden hättest können. Aber soll ja wohl nur eine Übung sein. 🙂

    Ich schreib den Code einfach mal etwas um und versehe ihn mit Kommentaren:

    using System;
    using System.Collections.Generic;
    // using System.Linq; Brauchst du hier nicht
    // using System.Text; Brauchst du hier nicht
    
    /*
    Keine Underscores. In C# wird für das meiste PascalCase verwendet.
    Für Variablen, Parameter, etc. wird camelCase verwendet.
    Ist ein ziemlicher Standard.
    */
    namespace CSharpTestKonsole
    {
        class ListObject
        {
            public ListObject(string s)
            {
              this.Content = s;
            }
    
            // In C# haben wir Properties.        
            public string Content { get; private set; }
    
            // ebenfalls Properties.
            public ListObject Next { get; set; }
        }
    
        // Wenn du deine Klasse irgendwo ausserhalb dieser Assembly verwenden
        // möchtest, dann solltest du sie als public deklarieren.
        public class LinList
            : IEnumerable<string> // <- sollte im Minimum jede Liste haben, weiteres siehe unten
        {
            public LinList()
            {
              first = null;
              last = null;
            }
    
            // Methoden Name PascalCase
            public void Add(string s)
            {
                // var ist wie auto in C++11
                var myObject = new ListObject(s);
    
                if(first == null)
                {
                    first = myObject;
                    last = myObject;
                }
                else
                {
                    var current = first;
                    while (current != last)
                    {
                        current = current.Next;
                    }
    
                    current.Next  = myObject;
                    last = myObject;
                }
            }
    
            // In C# gibt es ebenfalls Operatorüberladung!
            public string this[int index]
            {
                get
                {
                    // TODO Was ist wenn index negativ ist?
                    if (index == 0)
                    {
                        return first.Content;
                    }else
                    {
                        var current = first;
                        while ((index != 0) && (current != last))
                        {
                            current = current.Next;
                            index--;
                        }
                        // TODO: Was ist wenn current null ist? ;)
                        return current.Content;
                    }
                    // Tipp: IndexOutOfRangeException
                }
            }
            /*
            public string get_element_with_Index(int index)
            {
                if (index == 0)
                {
                    return first.get_Content();
                }else
                {
                    ListObject current = first;
                    while ((index != 0) && (current != last))
                    {
                        current = current.get_next();
                        index--;
                    }
                    return current.get_Content();
    
                }         
            }
            */
    
            // PascalCase
            public string GetFirst()
            {
                if (first != null)
                {
                    return first.Content;
                }
    
                // Bisschen seltsame Verhalten,
                // willst du nicht eher eine Exception werfen?
                return "";
            }
    
            // PascalCase
            public string GetLast()
            {
                if (last != null)
                {
                    return last.Content;
                }
    
                // Bisschen seltsame Verhalten,
                // willst du nicht eher eine Exception werfen?
                return "";
    
            }
    
            // Man könnte GetFirst und GetLast übrigens auch per Property implementieren:
            public string Last { get { return last != null ? last.Content : String.Empty; } }
            public string First { get { return first != null ? first.Content : String.Empty; } }
    
            // Damit kann man nun über deine List mit foreach iterieren.
            public IEnumerator<string> GetEnumerator()
            {
               var current = first;
    
               while (current != null)
               {
                   yield return current;
                   current = current.Next;
               }
            }
    
            // Explizite Interface Implementation
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
    
            // PascalCase
            public void DeleteFirst()
            {
                // TODO Was ist wenn first == null
                // Tipp: throw new InvalidOperationException
    
                // TODO Was ist wenn first == last
                // Tipp: last ebenfalls auf null setzen
                first = first.Next;
            }
    
            private ListObject first;
            private ListObject last;
        }  
    }
    

    Die Namensgebung ist auch nicht gerade optimal, aber soll ja hoffentlich wirklich nur ein Übungsbeispiel sein 🙂

    Die DeleteFirst Methode ist teilweise richtig. Du hast nur vergessen zu prüfen, ob first ungleich null ist. Das würde zu einer NullReferenceException führen. Sowas sollte aber nie fliegen. Wirf lieber eine InvalidOperationException und erklär darin, dass die Liste leer ist, wodurch man kein Element löschen kann. Zudem hast du den Fall nicht abgefangen, wenn first == last ist. In dem Fall würde nur first auf null gesetzt werden, aber nicht last . Dadurch kommt es zu seltsamen Verhalten bei den Properties Last & First (oder auch bei den Methoden) und beim Index-Zugriff.

    Ich hoffe mal, dass ich nichts wesentliches vergessen haben...

    Vielleicht noch dies: Lies dir mindestens den C# Programming Guide durch, den ich im anderen Thread verlinkt habe. Der Umstieg ist zwar relativ leicht, aber es schadet wirklich nicht.

    Grüssli



  • get_next und set_next: Es gibt in C# Properties.

    string als content: C# kennt Generics. Sowas wie Templates für Arme.

    get_element_with_Index: Es gibt Indexer. Also der Operator[] kann überladen werden.

    "Ist die "delete_first" Methode so richtig?"
    Da Du Dich in C# nicht weiter um Speicherfreigabe kümmern musst, kannst Du einfach Referenzen umbiegen und die vorherigen Objekte vergessen.

    return ""; in get_last und get_first: Warum? Wirf eine Exception.

    Keine Interfaces implementiert. Das Framework hat eine ganze Reihe von nützlichen Interfaces, z.B. von IEnumerable welches das Iterator-Konzept in .NET abstrahiert.

    Underlines in Bezeichnern: Das ist ohne Zweifel jedem selbst überlassen, aber in C# sieht man, meiner Erfahrung nach, eher CamelCase-Code

    Habe den Code nur kurz überflogen weil wenig Zeit (d.h. ich habe nicht versucht die Korrektheit Gedanklich durchzuspielen) aber das ist mir direkt aufgefallen.

    Edit: Zu langsam 😉



  • Vielen Dank,

    ich habe schon einiges der Vorschläge umgesetzt (Das foreach iterieren noch nicht, das mache ich bald).

    Ich habe aber ein Problem, wenn die Properties Deklaration so kurz ist wie im Beispiel von "Dravere". Wenn sie so kurz mache wird bei "GetFirst" nichts zurückgegeben. Nur wenn ich sie etwas ausführlicher hinschreibe klappt es.

    Bei der kurzen Schreibweise kommt keine Compiler Fehlermeldung oder Warnung und nichts zur Laufzeit, aber die "GetFirst" Methode gibt nichts zurück, obwohl es gültige Elemente in der Liste gibt.

    Warum ist das so?

    using System;
    using System.Collections.Generic;
    
    namespace CSharpTestKonsole
    {
    
        class myTestException : ApplicationException
        {
            public myTestException() : base() { }
            public myTestException(string msg) : base(msg) { }
    
        }
    
        class ListObject<T>
        {
    
            public ListObject(T s) 
            { 
                content = s;
                next = null; 
            }
    
            // Verwende ich dies wird bei der Methode "GetFirst" nichts zurückgegeben, ohne Fehlermeldung
            /*
            public T Content
            { 
                get; private set; 
            }
             */
    
            public T Content
            {
                get { return content; }
                set { content = value; }
            }
    
            public ListObject<T> Next
            {
                get{return next;}
                set{next = value;}
            }
    
            private T content;
            private ListObject<T> next;
    
        }
    
        class LinList<T>
        {
    
            public LinList() { first = null; last = null; }
    
            public void Add(T s)
            {
                ListObject<T> myObject = new ListObject<T>(s);
    
                if (first == null)
                {
                    first = myObject;
                    last = myObject;
                }
                else
                {
                    ListObject<T> current = first;
                    while (current != last)
                    {
                        current = current.Next;
                    }
                    current.Next = myObject;
                    last = myObject;
    
                }
            }
    
            public T this [uint index]
            {
                get
                {
                    uint originalIndex = index;
                    ListObject<T> current = first;
                    while ((index != 0))
                    {
                        if (current == null)
                        {
                            throw new myTestException("LinList: Tryed to get not existing element" + originalIndex.ToString());
                        }
                        current = current.Next;
                        index--;
                    }
                    if (current == null)
                    {
                        throw new myTestException("LinList: Tryed to get not existing element" + originalIndex.ToString());
                    }
    
                    return current.Content;
               }
    
            }
    
            public T GetFirst()
            {
                if (first != null)
                {
                   return first.Content;
                }
                throw new myTestException("LinList: Tryed to get not existing first element");
    
            }
    
            public T GetLast()
            {
                if (last != null)
                {
                    return last.Content;              
                }
                throw new myTestException("LinList: Tryed to get not existing last element");
            }
    
            public void DeleteFirst()
            {
                if (first != null)
                {
                    first = first.Next;
                    if (first == null)
                    {
                        last = null; 
                    }
                }
                else
                {
                    throw new myTestException("LinList: Tryed to delete not existing first element");
                }
            }
    
            ListObject<T> first;
            ListObject<T> last;
        }
    }
    

    Hauptprogram

    using System;
    using System.Collections.Generic;
    
    namespace CSharpTestKonsole
    {
        class Program
        {
            static void Main(string[] args)
            {
                LinList<string> l = new LinList<string>();
                l.Add(" hi1 ");
                l.Add(" hi2 ");
                l.Add(" hi3 ");
    
                try
                {
                    l.DeleteFirst();
                    System.Console.WriteLine(l[0]);            
                }
                catch (Exception e)
                {
                    System.Console.WriteLine(e.ToString());
                }
                System.Console.ReadLine();
            }
        }
    }
    

  • Administrator

    Andreas XXL schrieb:

    Warum ist das so?

    Keine Ahnung, kann ich nicht nachvollziehen. Funktioniert bei mir problemlos. Du verwendest Visual Studio 2008? Hast du das SP1 installiert? Für welche .Net Version entwickelst du?

    Zusätzliche Tipps:

    • Erbe nicht von ApplicationException sondern direkt von Exception.
    • myTestException ... PascalCase! 😃
    • ToString wird automatisch aufgerufen, wenn du etwas an einen String anhängst.
    • Schau dir Console.ReadKey an. Ist besser als dein ReadLine .
    • Wenn du oben ein using System; machst, musst du nicht System.Console.WriteLine aufrufen, es reicht ein Console.WriteLine .

    Grüssli



  • Dravere schrieb:

    Andreas XXL schrieb:

    Warum ist das so?

    Keine Ahnung, kann ich nicht nachvollziehen. Funktioniert bei mir problemlos. Du verwendest Visual Studio 2008? Hast du das SP1 installiert? Für welche .Net Version entwickelst du?

    Zusätzliche Tipps:

    • Erbe nicht von ApplicationException sondern direkt von Exception.
    • myTestException ... PascalCase! 😃
    • ToString wird automatisch aufgerufen, wenn du etwas an einen String anhängst.
    • Schau dir Console.ReadKey an. Ist besser als dein ReadLine .
    • Wenn du oben ein using System; machst, musst du nicht System.Console.WriteLine aufrufen, es reicht ein Console.WriteLine .

    Grüssli

    Bei mir steht:

    Microsoft .NET Framework version 3.5 SP1

    in Visual Studio. Und es funktioniert mit der Kurzschreibweise nur bei "Content" nicht, wenn ich es bei "Next" verwende geht es 😕

    Edit: In dieser Methode wird einfach "null" zurückgegeben, wenn ich weiter oben die Kurzschreibweise für den Zugriff auf "Content" verwende

    public T this [uint index]
            {
                get
                {
                    uint originalIndex = index;
                    ListObject<T> current = first;
                    while ((index != 0))
                    {
                        if (current == null)
                        {
                            throw new MyTestException("LinList: Tryed to get not existing element" + originalIndex.ToString());
                        }
                        current = current.Next;
                        index--;
                    }
                    if (current == null)
                    {
                        throw new MyTestException("LinList: Tryed to get not existing element" + originalIndex.ToString());
                    }
    
                    return current.Content;
               }
    
            }
    

    Bild mit den Werten die der Debugger anzeigt:
    http://imageshack.us/photo/my-images/842/fehlerz.jpg/



  • Hallo Andreas,

    wenn du die automatischen Properties verwendest, dann darfst du selber keine private Membervariable (in deinem Fall 'content') anlegen, sondern mußt durchgängig in deinem Code auf dieses Property ('Content') zugreifen (der C# Compiler legt in diesem Fall intern selber eine sog. BackingField-Membervariable an - sieht man z.B. per Reflection):

    class ListObject<T>
    {
        public ListObject(T t)
        {
            Content = t;
            Next = null; // diese Zeile kannst du auch weglassen, da in C# automatisch alle Member auf null
                         // (bzw. bei Wertetypen 0 oder false) initialisiert werden.
        }
    
        public T Content
        {
            get;
            private set;
        }
    
        public ListObject<T> Next
        {
            get;
            set;
        }
    }
    

    Kurz und knapp -> das ist C# 😉



  • Th69 schrieb:

    Hallo Andreas,

    wenn du die automatischen Properties verwendest, dann darfst du selber keine private Membervariable (in deinem Fall 'content') anlegen, sondern mußt durchgängig in deinem Code auf dieses Property ('Content') zugreifen (der C# Compiler legt in diesem Fall intern selber eine sog. BackingField-Membervariable an - sieht man z.B. per Reflection):

    class ListObject<T>
    {
        public ListObject(T t)
        {
            Content = t;
            Next = null; // diese Zeile kannst du auch weglassen, da in C# automatisch alle Member auf null
                         // (bzw. bei Wertetypen 0 oder false) initialisiert werden.
        }
    
        public T Content
        {
            get;
            private set;
        }
    
        public ListObject<T> Next
        {
            get;
            set;
        }
    }
    

    Kurz und knapp -> das ist C# 😉

    Danke! Jetzt funktioniert es!

    Ich habe im Konstruktor nun

    public ListObject(T s) 
            { 
               Content = s;
               Next = null; 
            }
    

    und die Properties

    public T Content
            {
                get;
                set;
            }
    
            public ListObject<T> Next
            {
                get;
                set;
            }
    

    und keine eigenen Deklarationen von content und next mehr.

    Jetzt verstehe ich auch wie das Ergebnis vorher Zustande kam.

    Es wurden ebenfalls BackingField-Membervariable angelegt. Ich habe aber im Konstruktor, die eigenen Member Variablen auf die erzeugten Objekte zeigen lassen. Dann aber mit den automatischen get und set Properties die Werte der un initialisierten BackingField-Membervariablen abgefragt, die immer noch auf null zeigen. Daher kam dann "null" als Ergebnis zurück bei der Abfrage der Liste.

    Habe ich das so richtig verstanden?

    Edit: Mit Structs kann man sich da ne ziemlich böse Falle bauen:

    struct Point
        {
    
            public int X
            {
                get;
                set;
            }
    
            public int Y
            {
                get;
                set;
            }
    
            public Point(int x, int y)
            {
                X = x;
                Y = y;
    
            }
        }
    

    Das Problem ist der Konstruktor. Mit X = x; wird ja die set Methode der BackingField-Membervariable aufgerufen. Dann kommt als Fehlermedlung:

    Das this-Objekt kann erst verwendet werden, wenn alle Felder zugeordnet wurden.

    Da man aber auf die BackingField-Membervariable nicht direkt zugreifen kann, kann man eigentlich das Problem nicht lösen diese zu initialisieren, bevor die set Methode aufgerufen wird. Eigentlich ein Teufelskreis.

    Durch ausprobieren habe ich rausgefunden, dass ein :this() hinter den Konstruktor das Problem lößt:

    struct Point
        {
    
            public int X
            {
                get;
                set;
            }
    
            public int Y
            {
                get;
                set;
            }
    
            public Point(int x, int y):this()
            {
                X = x;
                Y = y;
    
            }
        }
    

    Warum funktioniert das mit dem this()?


  • Administrator

    Andreas XXL schrieb:

    Es wurden ebenfalls BackingField-Membervariable angelegt. Ich habe aber im Konstruktor, die eigenen Member Variablen auf die erzeugten Objekte zeigen lassen. Dann aber mit den automatischen get und set Properties die Werte der un initialisierten BackingField-Membervariablen abgefragt, die immer noch auf null zeigen. Daher kam dann "null" als Ergebnis zurück bei der Abfrage der Liste.

    Habe ich das so richtig verstanden?

    Ja. Einzig vielleicht dass das Backing-Field nicht unintialisiert ist. Einen uninitialisierten Zustand gibt es in C# nicht. Referenzen haben immer nach der Erstellung den Wert null und Wertetypen werden mit dem Standardkonstruktor initialisiert. Und deshalb musst du auch nicht den Wert explizit auf null setzen 😉
    Zudem muss ich mich wohl entschuldigen, habe das mit dem Auto-Property nicht genau genug erklärt gehabt. Das mit dem Backing-Field hätte ich wohl noch hinschreiben sollen. Auf der anderen Seite wäre für sowas eben der C# Programming Guide da.

    Andreas XXL schrieb:

    Edit: Mit Structs kann man sich da ne ziemlich böse Falle bauen:

    Was soll daran eine Falle sein? Das ist erwartetes Verhalten. Man darf Methoden eines Wertetypen erst aufrufen, wenn er vollständig initialisiert wurde.
    Das Problem mit den Auto-Properties tritt aber normalerweise nirgendwo auf, da Wertetypen normalerweise sowieso nur lesbar sein sollten. Wenn du einen Wertetypen in C# veränderbar machst, kann das zu sehr seltsamen Verhalten führen. Zwar korrektes aber nicht wirklich erwartetes.

    Andreas XXL schrieb:

    Warum funktioniert das mit dem this()?

    Durch den Standardkonstruktor wurden alle Werte korrekt initialisiert. Damit kannst du nun Methoden aufrufen. Und ja, das Schreiben und Lesen auf ein Property sind Methodenaufrufe. Es sind nur etwas schönere Get- und Set-Methoden.

    Übrigens: Wir haben hier C# Tags. [cs]

    Grüssli



  • Was lernt man daraus: C# != C++.



  • TeaTime schrieb:

    Was lernt man daraus: C# != C++.

    Oder das Lesen die wichtigste Fähigkeit eines Programmiereres ist:

    http://msdn.microsoft.com/de-de/library/yyaad03b(v=VS.90).aspx

    Gleich der erste Punkt der Liste 😉


Anmelden zum Antworten