Zum Inhalt springen

Whiz-zarD

Mitglieder
  • Gesamte Inhalte

    2.020
  • Benutzer seit

  • Letzter Besuch

  • Tagessiege

    48

Reputationsaktivitäten

  1. Like
    Whiz-zarD hat eine Reaktion von StefanE erhalten in C# OOP Probleme   
    Wieso sollte man dich töten wollen?
    Softwareentwicklung ist nun mal ein Reifeprozess. Niemand liest nur ein Buch und kann gleich wunderbar sauberen Code schreiben. Mein Code sah zum Anfang auch mies aus und selbst Robert C. Martin, der das Buch "Clean Code" geschrieben hat, sagt von sich aus, dass er nicht die Weisheit mit Löffeln gefressen hat und es auch bei seinen Code-Beispielen sicherlich noch Verbesserungspotenzial gibt aber nur durch Ausprobieren lernt man. 
    Du hast schon richtig erkannt, dass man fürs Einlesen der Datei eine eigene Klasse benötigt. Allerdings gehört die Logik nicht in den Konstruktor. Der Konstruktor dient zur Initialisierung der Klasse. Der Name der Klasse sollte auch die Aufgabe widerspiegeln, was die Klasse tut. "DateiEinlesen" ist vielleicht gut, aber geht es vielleicht noch konkreter? Ich weiß, dass es eine CSV-Datei ist. Vielleicht eher CsvReader?  Wobei dieser Name auch wieder sehr allgemein ist. In der CSV-Datei steckt ja eine Tabelle. Welche Daten besitzt die Tabelle? Vielleicht kann man der Tabelle einen Namen geben. Eine CSV-Datei ist ja eine Art der Serialisierung. Das Verfahrung um so eine Tabelle in ein Objekt zu überführen, nennt man auch Deserialiserung. Das kann man ja erst mal im Hinterkopf behalten.
    Zuerst würde ich mir aber erst mal eine geeignete Datenstruktur überlegen. In der CSV-Datei stecken ja Daten. Ich nehme jetzt mal als Beispiel, dass die CSV-Datei Daten zu Personen beinhaltet:
    Name;Vorname;Geschlecht;Alter Doe;John;Maennlich;38 Also würde ich erst mal eine Klasse für diese Daten erstellen:
    public class Person {     public string Name { get; set; }     public string Vorname { get; set; }     public Geschlecht Geschlecht { get; set; }     public int Alter { get; set; } } public enum Geschlecht {     Maennlich     , Weiblich } Nun könnte ich mich darum kümmern, eine(!) Datei einzulesen. Ich habe eine Datenstruktur und ich weiß, dass ich eine CSV-Datei deserialisieren muss. Also könnte man die Klasse z.B. PersonCsvDeserializer nennen. In dieser Klasse soll es eine Methode geben, die Deserialize() heißt. Ich verzichte hier jetzt erst mal bewusst auf ein Interface, weil ich denke, dass es für dich bis hier hin schon kompliziert genug ist. Das Interface werde ich später noch mal erklären. Erst mal kümmern wir uns darum, was wir alles brauchen, um eine Datei zu deserialisieren. 
    Was muss die Klasse PersonCsvDeserializer alles wissen, um eine CSV-Datei deserialisieren zu können? Man könnte vielleicht im ersten Schritt auf die Idee kommen, dass die Klasse den Pfad und Dateinamen benötigt. Mit den Informationen aus dem letzten Absatz könnte ein erster Entwurf so aussehen: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {       // ...     } } Als Rückgabewert habe ich IEnumerable<Person> gewählt, weil IEnumerable<T> ein sehr allgemeines Interface ist und einen Enumerator (auf deutsch: Aufzählung; in anderen Sprachen auch Iterator genannt) zur Verfügung stellt, mit dem wir über die Daten iterieren können (mit der foreach-Schleife). Sowohl IList<T>, ICollection<T>, IDictionary<T>, Array und weitere Klassen implementieren dieses Interface und mehr als über die Daten iterieren wollen wir nicht. Wenn wir später damit mehr machen wollen, können wir es leicht mit Linq in eine Collection, List, Array oder auch in ein Dictionary umwandeln. Die Deserialize()-Methode soll also eine Aufzählung von Personen zurückliefern.
    Normalerweise macht man es anders, aber aus einfachheit behaupte ich mal frech, dass die erste Zeile in der CSV-Datei immer ein Header besitzt. In der Implementierung überspringe ich den Header per Linq mit der Skip()-Methode. Die Deserialize()-Methode soll also folgendes machen:
    Die Datei lesen Durch die Datenzeilen iterieren Pro Datenzeile ein Person-Objekt erstellen Die Person-Objekte als Aufzählung zurückliefern Der erste Entwurf könnte daher folgendermaßen aussehen:
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string line in File.ReadAllLines(fileName).Skip(1))         {             string[] elements = line.Split(';');             result.Add(new Person             {                 Name = elements[0],                 Vorname = elements[1],                 Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),                 Alter = Convert.ToInt32(elements[3])             });         }         return result;     } } Die Methode macht zwar was sie soll, aber ist sie wirklich übersichtlich? Nicht wirklich. Wir haben hier mehrere Ebenen miteinander vermischt. Wir können also mit dem Refactoring anfangen. z.B. das
    File.ReadAllLines(fileName).Skip(1) Wofür ist das genau gut? Wenn man den gesamten Kontext kennt, weiß man es zwar aber eigentlich liegt der Code-Abschnitt eine Ebene Tiefer. Es hantiert mit Dateien und hat mit der eigentlichen Aufgabe der Deserialiserung wenig zu tun. Also sollte man diesen Teil in eine separate Methode packen:
    private IEnumerable<string> ReadDataFromFile(string fileName) {     return File.ReadAllLines(fileName).Skip(1); } Somit wandert das Skip(1) in eine tiefere Ebene und interessiert uns in der Deserialize()-Methode nicht mehr. Als nächstes fällt aber auf, dass wir ein String mit Split() in ein Array teilen und aus diesem Array dann die einzelnen Personendaten herausfischen. Diesen Vorgang nennt man auch Parsing. Also könnten wir diesen Teil auch in eine Methode auslagern:
    private Person Parse(string serializedData) {     string[] elements = serializedData.Split(';');     return new Person     {         Name = elements[0],         Vorname = elements[1],         Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),         Alter = Convert.ToInt32(elements[3])     }; } Unsere Klasse sieht dann bis jetzt folgendermaßen aus: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string serializedData in ReadDataFromFile(fileName))         {             Person person = this.Parse(serializedData);             result.Add(person);         }         return result;     }     private IEnumerable<string> ReadDataFromFile(string fileName)     {         return File.ReadAllLines(fileName).Skip(1);     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Nun ist Deserialize() doch recht gut lesbar. Wir lesen die Daten aus der Datei, parsen die Daten und erhalten ein Person-Objekt, welches wir dann in eine Liste packen und zum Schluss geben wir die Liste zurück.
    Es gäbe hier noch weiteres Verbesserungspotenzial aber ich belasse es erst mal hierbei. Ein paar Hinweise gebe ich aber noch:
    Fehler-Handling? Was passiert, wenn z.B. die Datei nicht existiert? Ist das erzeugte Objekt List<Person> wirklich eine gute Wahl? Angenommen, wir haben es mit einer riesigen CSV-Datei (mehrere Gigabytes) zu tun, die größer ist, als unser Arbeitsspeicher. Hier schmeiße ich mal das "yield return"-Schlüsselwort in den Raum. Auch ist das indexierte Zugreifen auf das Array in der Methode Parse() nicht wirklich glücklich gelöst. Was passiert nämlich, wenn mal eine Spalte in der Datei hinzukommt? Dann muss man ja auch den Code anpassen. Das will man aber eigentlich gar nicht. Zu diskutieren wäre auch, ob die Variable fileName nicht doch besser eine Instanzvariable sein sollte, die per Konstruktor reingereicht wird. Es fällt ja auf, dass die Methoden Deserialize() und ReadDataFromFile() den Dateinamen benötigen. Also stellt fileName ja eine gewisse Abhängigkeit dar, die die Klasse benötigt, um arbeiten zu können.  Als Überlegung kannst du ja selber mal schauen, wie man mit solchen Situation umgehst.
    Um später im Hauptpgramm alle Personen zu iterieren könntest du nun folgendes schreiben:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");     PersonCsvDeserializer deserializer = new PersonCsvDeserializer();     foreach(string fileName in fileNames)     {         IEnumerable<Person> persons = deserializer.Deserialize(fileName);         foreach (Person person in persons)         {             // ...         }     } } Nach dem selben Prinzip, wie bei der PersonCsvDeserializer-Klasse kannst du ja mal überlegen, wie man nun diesen Code refactoren an.
    Ab hier wird es noch etwas technischer und tiefgreifender. Ich möchte dir noch zwei Techniken zeigen, die du aber erst mal nicht umsetzen brauchst.
    "Inversion of Control" und "Dependeny Injection"
    In der Klasse PersonCsvDeserializer fällt auf, dass die Klasse von einer Datei abhängig ist aber die Daten können vielleicht aus einer Datenbank kommen oder wir schreiben die CSV-Daten direkt in eine grafische Oberfläche. Möchte man jetzt für jeden Anwendungsfall eine eigene Klasse schreiben? Eigentlich nicht. Die Abhängigkeit zur Datei muss also aufgelöst werden. Das .Net-Framework bietet ja die abstrakte Klasse TextReader, die so ziemlich alles darstellen kann. Ein Reader, der eine Datei liest oder aus einem TCP-Stream oder aus einer Datenbank, etc. Anstatt also den Dateinamen reinzureichen, könnte man auch ein TextReader reinreichen.
    Hier mal ein Beispiel, wie so eine Klasse aussehen könnte:
    public class PersonCsvDeserializer {     private TextReader reader;     private bool isHeaderSkipped;     public PersonCsvDeserializer(TextReader reader)     {         this.reader = reader;     }     public IEnumerable<Person> Deserialize()     {         string serializedData;         while ((serializedData = this.ReadNextData()) != null)         {             Person person = this.Parse(serializedData);             yield return person;         }     }     private string ReadNextData()     {         string serializedData = this.reader.ReadLine();         if (!this.isHeaderSkipped)         {             this.isHeaderSkipped = true;             return this.ReadNextData();         }         return serializedData;     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Die Main-Methode sieht dann so aus:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);                          IEnumerable<Person> persons = deserializer.Deserialize();             foreach (Person person in persons)             {                 // ...             }         }       } } Zugegeben, in diesem Beispiel ist die Klasse PersonCsvDeserializer etwas komplizierter geworden aber es ist jetzt egal, woher die Daten stammen, solange wir ein TextReader in den Konstruktor schieben. Das reinrechen der Abhängigkeit in den Konstruktor nennt sich auch "Dependeny Injection". In diesem Beispiel habe ich auch das yield return verwendet. Da wir jetzt nur noch maximal den Speicher für ein Person-Objekt verbrauchen, könnte die Klasse eigentlich nun unendlich viele Daten deserialisieren. Ein Problem stellt aber immer noch die Indexierung des Arrays dar aber das überlasse ich jetzt dir.
    Das Interface
    Das letzte, was ich noch schreiben wollte, wäre ein geeignetes Interface für den Deserializer. Wollen wir jetzt mehrere Deserializer schreiben oder einen Deserializer als Abhängigkeit in eine Klasse reinreichen, ist ein Interface geeignet, damit es später egal ist, um welchen Deserializer es sich handelt. Man könnte sich ja auch vorstellen, dass die Daten nicht in einer CSV-Datei stecken, sondern in einer XML-Datei. Dafür wäre folgendes Interface recht nützlich
    public interface IDeserializer<T> {     IEnumerable<T> Deserialize(); } Mit diesem Interface könnten wir sogar das hässliche using im Hauptprogramm wieder loswerden. Ich finde, das using stört im Lesefluss. Wir haben ja jetzt eine Klasse, die CSV-Daten aus unterschiedlichsten Quellen von Personen deserialisieren kann. Was hindert uns nun daran, einen weiteren Deserializer zu bauen, der aus Dateien deserialisiert? Beispiel:
    public class PersonCsvFileDeserializer : IDeserializer<Person> {     private string fileName;     public PersonCsvFileDeserializer(string fileName)      {         this.fileName = fileName;     }     public IEnumerable<Person> Deserialize()     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);             return deserializer.Deserialize();         }     } } Das using wurde nach PersonCsvFileDeserializer und somit eine ebene tiefer verschoben. Wenn du Dependecy Injection verstanden hast, dann würde dir auffallen, dass die Zeile 
    PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader); eigentlich böse ist, da es eine Abhängigkeit darstellt, die wiederum in den Konstruktor gehört. Ich habe sie aber erst mal hier drinnengelassen, weil das sonst wieder bedeuten würde, dass das using wieder ins Hauptprogramm rein müsste. Eigentlich müsste man sich eine Fabrik-Methode ausdenken, die den PersonCsvFileDeserializer zusammenbaut. Die habe ich hier aber weggelassen. Die kannst du dir ja ausdenken.
    Das Hauptprogramm würde dann so aussehen:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         PersonCsvFileDeserializer deserializer = new PersonCsvFileDeserializer(fileName);                      IEnumerable<Person> persons = deserializer.Deserialize();         foreach (Person person in persons)         {             // ...         }      } } Das wäre doch schon wieder ein Schritt übersichtlicher. 
    Wie du also siehst, haben wir allein nur für das Einlesen von den CSV-Dateien drei Klassen:
    Person PersonCsvDeserializer PersonCsvFileDeserializer und ein Interface:
    IDeserializer<T> geschrieben. Man braucht also kein mega großes Projekt, um mehrere Klassen zu schreiben. Es reicht auch schon was ganz einfaches. Man sollte sich immer bewusst machen, dass Klassen immer nur eine Aufgabe machen sollten und Methoden Teilaspekte dieser Aufgabe sind und sie sollten auch nicht mehr machen, als eine Sache. Es macht auch nichts, wenn man zum Anfang Spagetticode schreibt und diesen später nach und nach einem Refactoring unterzieht. Niemand ist perfekt und niemand schreibt perfekten Code. Man fängt also immer erst mal an und arbeitet sich Schritt für Schritt an eine geeignete und saubere Lösung. Selbst meine Lösung ist mit Sicherheit nicht perfekt und ich habe auch nicht die Weisheit mit Löffeln gefressen. Wenn du mein Beitrag richtig verfolgt haben solltest, hast du vielleicht auch gemerkt, dass ich erst mal eine Lösung geschrieben habe und sie dann nach und nach verfeinert und verbessert habe. Das Wissen kommt erst mit Erfahrung und Erfahrung sammelt man nur, indem man es ausprobiert und darüber mit anderen diskutiert. Also trau dich.
    So, das reicht auch fürs erste. Ich denke, das ist erst mal genug Input.
     
  2. Like
    Whiz-zarD hat eine Reaktion von sas86ks erhalten in C# OOP Probleme   
    Wieso sollte man dich töten wollen?
    Softwareentwicklung ist nun mal ein Reifeprozess. Niemand liest nur ein Buch und kann gleich wunderbar sauberen Code schreiben. Mein Code sah zum Anfang auch mies aus und selbst Robert C. Martin, der das Buch "Clean Code" geschrieben hat, sagt von sich aus, dass er nicht die Weisheit mit Löffeln gefressen hat und es auch bei seinen Code-Beispielen sicherlich noch Verbesserungspotenzial gibt aber nur durch Ausprobieren lernt man. 
    Du hast schon richtig erkannt, dass man fürs Einlesen der Datei eine eigene Klasse benötigt. Allerdings gehört die Logik nicht in den Konstruktor. Der Konstruktor dient zur Initialisierung der Klasse. Der Name der Klasse sollte auch die Aufgabe widerspiegeln, was die Klasse tut. "DateiEinlesen" ist vielleicht gut, aber geht es vielleicht noch konkreter? Ich weiß, dass es eine CSV-Datei ist. Vielleicht eher CsvReader?  Wobei dieser Name auch wieder sehr allgemein ist. In der CSV-Datei steckt ja eine Tabelle. Welche Daten besitzt die Tabelle? Vielleicht kann man der Tabelle einen Namen geben. Eine CSV-Datei ist ja eine Art der Serialisierung. Das Verfahrung um so eine Tabelle in ein Objekt zu überführen, nennt man auch Deserialiserung. Das kann man ja erst mal im Hinterkopf behalten.
    Zuerst würde ich mir aber erst mal eine geeignete Datenstruktur überlegen. In der CSV-Datei stecken ja Daten. Ich nehme jetzt mal als Beispiel, dass die CSV-Datei Daten zu Personen beinhaltet:
    Name;Vorname;Geschlecht;Alter Doe;John;Maennlich;38 Also würde ich erst mal eine Klasse für diese Daten erstellen:
    public class Person {     public string Name { get; set; }     public string Vorname { get; set; }     public Geschlecht Geschlecht { get; set; }     public int Alter { get; set; } } public enum Geschlecht {     Maennlich     , Weiblich } Nun könnte ich mich darum kümmern, eine(!) Datei einzulesen. Ich habe eine Datenstruktur und ich weiß, dass ich eine CSV-Datei deserialisieren muss. Also könnte man die Klasse z.B. PersonCsvDeserializer nennen. In dieser Klasse soll es eine Methode geben, die Deserialize() heißt. Ich verzichte hier jetzt erst mal bewusst auf ein Interface, weil ich denke, dass es für dich bis hier hin schon kompliziert genug ist. Das Interface werde ich später noch mal erklären. Erst mal kümmern wir uns darum, was wir alles brauchen, um eine Datei zu deserialisieren. 
    Was muss die Klasse PersonCsvDeserializer alles wissen, um eine CSV-Datei deserialisieren zu können? Man könnte vielleicht im ersten Schritt auf die Idee kommen, dass die Klasse den Pfad und Dateinamen benötigt. Mit den Informationen aus dem letzten Absatz könnte ein erster Entwurf so aussehen: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {       // ...     } } Als Rückgabewert habe ich IEnumerable<Person> gewählt, weil IEnumerable<T> ein sehr allgemeines Interface ist und einen Enumerator (auf deutsch: Aufzählung; in anderen Sprachen auch Iterator genannt) zur Verfügung stellt, mit dem wir über die Daten iterieren können (mit der foreach-Schleife). Sowohl IList<T>, ICollection<T>, IDictionary<T>, Array und weitere Klassen implementieren dieses Interface und mehr als über die Daten iterieren wollen wir nicht. Wenn wir später damit mehr machen wollen, können wir es leicht mit Linq in eine Collection, List, Array oder auch in ein Dictionary umwandeln. Die Deserialize()-Methode soll also eine Aufzählung von Personen zurückliefern.
    Normalerweise macht man es anders, aber aus einfachheit behaupte ich mal frech, dass die erste Zeile in der CSV-Datei immer ein Header besitzt. In der Implementierung überspringe ich den Header per Linq mit der Skip()-Methode. Die Deserialize()-Methode soll also folgendes machen:
    Die Datei lesen Durch die Datenzeilen iterieren Pro Datenzeile ein Person-Objekt erstellen Die Person-Objekte als Aufzählung zurückliefern Der erste Entwurf könnte daher folgendermaßen aussehen:
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string line in File.ReadAllLines(fileName).Skip(1))         {             string[] elements = line.Split(';');             result.Add(new Person             {                 Name = elements[0],                 Vorname = elements[1],                 Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),                 Alter = Convert.ToInt32(elements[3])             });         }         return result;     } } Die Methode macht zwar was sie soll, aber ist sie wirklich übersichtlich? Nicht wirklich. Wir haben hier mehrere Ebenen miteinander vermischt. Wir können also mit dem Refactoring anfangen. z.B. das
    File.ReadAllLines(fileName).Skip(1) Wofür ist das genau gut? Wenn man den gesamten Kontext kennt, weiß man es zwar aber eigentlich liegt der Code-Abschnitt eine Ebene Tiefer. Es hantiert mit Dateien und hat mit der eigentlichen Aufgabe der Deserialiserung wenig zu tun. Also sollte man diesen Teil in eine separate Methode packen:
    private IEnumerable<string> ReadDataFromFile(string fileName) {     return File.ReadAllLines(fileName).Skip(1); } Somit wandert das Skip(1) in eine tiefere Ebene und interessiert uns in der Deserialize()-Methode nicht mehr. Als nächstes fällt aber auf, dass wir ein String mit Split() in ein Array teilen und aus diesem Array dann die einzelnen Personendaten herausfischen. Diesen Vorgang nennt man auch Parsing. Also könnten wir diesen Teil auch in eine Methode auslagern:
    private Person Parse(string serializedData) {     string[] elements = serializedData.Split(';');     return new Person     {         Name = elements[0],         Vorname = elements[1],         Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),         Alter = Convert.ToInt32(elements[3])     }; } Unsere Klasse sieht dann bis jetzt folgendermaßen aus: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string serializedData in ReadDataFromFile(fileName))         {             Person person = this.Parse(serializedData);             result.Add(person);         }         return result;     }     private IEnumerable<string> ReadDataFromFile(string fileName)     {         return File.ReadAllLines(fileName).Skip(1);     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Nun ist Deserialize() doch recht gut lesbar. Wir lesen die Daten aus der Datei, parsen die Daten und erhalten ein Person-Objekt, welches wir dann in eine Liste packen und zum Schluss geben wir die Liste zurück.
    Es gäbe hier noch weiteres Verbesserungspotenzial aber ich belasse es erst mal hierbei. Ein paar Hinweise gebe ich aber noch:
    Fehler-Handling? Was passiert, wenn z.B. die Datei nicht existiert? Ist das erzeugte Objekt List<Person> wirklich eine gute Wahl? Angenommen, wir haben es mit einer riesigen CSV-Datei (mehrere Gigabytes) zu tun, die größer ist, als unser Arbeitsspeicher. Hier schmeiße ich mal das "yield return"-Schlüsselwort in den Raum. Auch ist das indexierte Zugreifen auf das Array in der Methode Parse() nicht wirklich glücklich gelöst. Was passiert nämlich, wenn mal eine Spalte in der Datei hinzukommt? Dann muss man ja auch den Code anpassen. Das will man aber eigentlich gar nicht. Zu diskutieren wäre auch, ob die Variable fileName nicht doch besser eine Instanzvariable sein sollte, die per Konstruktor reingereicht wird. Es fällt ja auf, dass die Methoden Deserialize() und ReadDataFromFile() den Dateinamen benötigen. Also stellt fileName ja eine gewisse Abhängigkeit dar, die die Klasse benötigt, um arbeiten zu können.  Als Überlegung kannst du ja selber mal schauen, wie man mit solchen Situation umgehst.
    Um später im Hauptpgramm alle Personen zu iterieren könntest du nun folgendes schreiben:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");     PersonCsvDeserializer deserializer = new PersonCsvDeserializer();     foreach(string fileName in fileNames)     {         IEnumerable<Person> persons = deserializer.Deserialize(fileName);         foreach (Person person in persons)         {             // ...         }     } } Nach dem selben Prinzip, wie bei der PersonCsvDeserializer-Klasse kannst du ja mal überlegen, wie man nun diesen Code refactoren an.
    Ab hier wird es noch etwas technischer und tiefgreifender. Ich möchte dir noch zwei Techniken zeigen, die du aber erst mal nicht umsetzen brauchst.
    "Inversion of Control" und "Dependeny Injection"
    In der Klasse PersonCsvDeserializer fällt auf, dass die Klasse von einer Datei abhängig ist aber die Daten können vielleicht aus einer Datenbank kommen oder wir schreiben die CSV-Daten direkt in eine grafische Oberfläche. Möchte man jetzt für jeden Anwendungsfall eine eigene Klasse schreiben? Eigentlich nicht. Die Abhängigkeit zur Datei muss also aufgelöst werden. Das .Net-Framework bietet ja die abstrakte Klasse TextReader, die so ziemlich alles darstellen kann. Ein Reader, der eine Datei liest oder aus einem TCP-Stream oder aus einer Datenbank, etc. Anstatt also den Dateinamen reinzureichen, könnte man auch ein TextReader reinreichen.
    Hier mal ein Beispiel, wie so eine Klasse aussehen könnte:
    public class PersonCsvDeserializer {     private TextReader reader;     private bool isHeaderSkipped;     public PersonCsvDeserializer(TextReader reader)     {         this.reader = reader;     }     public IEnumerable<Person> Deserialize()     {         string serializedData;         while ((serializedData = this.ReadNextData()) != null)         {             Person person = this.Parse(serializedData);             yield return person;         }     }     private string ReadNextData()     {         string serializedData = this.reader.ReadLine();         if (!this.isHeaderSkipped)         {             this.isHeaderSkipped = true;             return this.ReadNextData();         }         return serializedData;     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Die Main-Methode sieht dann so aus:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);                          IEnumerable<Person> persons = deserializer.Deserialize();             foreach (Person person in persons)             {                 // ...             }         }       } } Zugegeben, in diesem Beispiel ist die Klasse PersonCsvDeserializer etwas komplizierter geworden aber es ist jetzt egal, woher die Daten stammen, solange wir ein TextReader in den Konstruktor schieben. Das reinrechen der Abhängigkeit in den Konstruktor nennt sich auch "Dependeny Injection". In diesem Beispiel habe ich auch das yield return verwendet. Da wir jetzt nur noch maximal den Speicher für ein Person-Objekt verbrauchen, könnte die Klasse eigentlich nun unendlich viele Daten deserialisieren. Ein Problem stellt aber immer noch die Indexierung des Arrays dar aber das überlasse ich jetzt dir.
    Das Interface
    Das letzte, was ich noch schreiben wollte, wäre ein geeignetes Interface für den Deserializer. Wollen wir jetzt mehrere Deserializer schreiben oder einen Deserializer als Abhängigkeit in eine Klasse reinreichen, ist ein Interface geeignet, damit es später egal ist, um welchen Deserializer es sich handelt. Man könnte sich ja auch vorstellen, dass die Daten nicht in einer CSV-Datei stecken, sondern in einer XML-Datei. Dafür wäre folgendes Interface recht nützlich
    public interface IDeserializer<T> {     IEnumerable<T> Deserialize(); } Mit diesem Interface könnten wir sogar das hässliche using im Hauptprogramm wieder loswerden. Ich finde, das using stört im Lesefluss. Wir haben ja jetzt eine Klasse, die CSV-Daten aus unterschiedlichsten Quellen von Personen deserialisieren kann. Was hindert uns nun daran, einen weiteren Deserializer zu bauen, der aus Dateien deserialisiert? Beispiel:
    public class PersonCsvFileDeserializer : IDeserializer<Person> {     private string fileName;     public PersonCsvFileDeserializer(string fileName)      {         this.fileName = fileName;     }     public IEnumerable<Person> Deserialize()     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);             return deserializer.Deserialize();         }     } } Das using wurde nach PersonCsvFileDeserializer und somit eine ebene tiefer verschoben. Wenn du Dependecy Injection verstanden hast, dann würde dir auffallen, dass die Zeile 
    PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader); eigentlich böse ist, da es eine Abhängigkeit darstellt, die wiederum in den Konstruktor gehört. Ich habe sie aber erst mal hier drinnengelassen, weil das sonst wieder bedeuten würde, dass das using wieder ins Hauptprogramm rein müsste. Eigentlich müsste man sich eine Fabrik-Methode ausdenken, die den PersonCsvFileDeserializer zusammenbaut. Die habe ich hier aber weggelassen. Die kannst du dir ja ausdenken.
    Das Hauptprogramm würde dann so aussehen:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         PersonCsvFileDeserializer deserializer = new PersonCsvFileDeserializer(fileName);                      IEnumerable<Person> persons = deserializer.Deserialize();         foreach (Person person in persons)         {             // ...         }      } } Das wäre doch schon wieder ein Schritt übersichtlicher. 
    Wie du also siehst, haben wir allein nur für das Einlesen von den CSV-Dateien drei Klassen:
    Person PersonCsvDeserializer PersonCsvFileDeserializer und ein Interface:
    IDeserializer<T> geschrieben. Man braucht also kein mega großes Projekt, um mehrere Klassen zu schreiben. Es reicht auch schon was ganz einfaches. Man sollte sich immer bewusst machen, dass Klassen immer nur eine Aufgabe machen sollten und Methoden Teilaspekte dieser Aufgabe sind und sie sollten auch nicht mehr machen, als eine Sache. Es macht auch nichts, wenn man zum Anfang Spagetticode schreibt und diesen später nach und nach einem Refactoring unterzieht. Niemand ist perfekt und niemand schreibt perfekten Code. Man fängt also immer erst mal an und arbeitet sich Schritt für Schritt an eine geeignete und saubere Lösung. Selbst meine Lösung ist mit Sicherheit nicht perfekt und ich habe auch nicht die Weisheit mit Löffeln gefressen. Wenn du mein Beitrag richtig verfolgt haben solltest, hast du vielleicht auch gemerkt, dass ich erst mal eine Lösung geschrieben habe und sie dann nach und nach verfeinert und verbessert habe. Das Wissen kommt erst mit Erfahrung und Erfahrung sammelt man nur, indem man es ausprobiert und darüber mit anderen diskutiert. Also trau dich.
    So, das reicht auch fürs erste. Ich denke, das ist erst mal genug Input.
     
  3. Like
    Whiz-zarD hat eine Reaktion von Gooose erhalten in C# OOP Probleme   
    Wieso sollte man dich töten wollen?
    Softwareentwicklung ist nun mal ein Reifeprozess. Niemand liest nur ein Buch und kann gleich wunderbar sauberen Code schreiben. Mein Code sah zum Anfang auch mies aus und selbst Robert C. Martin, der das Buch "Clean Code" geschrieben hat, sagt von sich aus, dass er nicht die Weisheit mit Löffeln gefressen hat und es auch bei seinen Code-Beispielen sicherlich noch Verbesserungspotenzial gibt aber nur durch Ausprobieren lernt man. 
    Du hast schon richtig erkannt, dass man fürs Einlesen der Datei eine eigene Klasse benötigt. Allerdings gehört die Logik nicht in den Konstruktor. Der Konstruktor dient zur Initialisierung der Klasse. Der Name der Klasse sollte auch die Aufgabe widerspiegeln, was die Klasse tut. "DateiEinlesen" ist vielleicht gut, aber geht es vielleicht noch konkreter? Ich weiß, dass es eine CSV-Datei ist. Vielleicht eher CsvReader?  Wobei dieser Name auch wieder sehr allgemein ist. In der CSV-Datei steckt ja eine Tabelle. Welche Daten besitzt die Tabelle? Vielleicht kann man der Tabelle einen Namen geben. Eine CSV-Datei ist ja eine Art der Serialisierung. Das Verfahrung um so eine Tabelle in ein Objekt zu überführen, nennt man auch Deserialiserung. Das kann man ja erst mal im Hinterkopf behalten.
    Zuerst würde ich mir aber erst mal eine geeignete Datenstruktur überlegen. In der CSV-Datei stecken ja Daten. Ich nehme jetzt mal als Beispiel, dass die CSV-Datei Daten zu Personen beinhaltet:
    Name;Vorname;Geschlecht;Alter Doe;John;Maennlich;38 Also würde ich erst mal eine Klasse für diese Daten erstellen:
    public class Person {     public string Name { get; set; }     public string Vorname { get; set; }     public Geschlecht Geschlecht { get; set; }     public int Alter { get; set; } } public enum Geschlecht {     Maennlich     , Weiblich } Nun könnte ich mich darum kümmern, eine(!) Datei einzulesen. Ich habe eine Datenstruktur und ich weiß, dass ich eine CSV-Datei deserialisieren muss. Also könnte man die Klasse z.B. PersonCsvDeserializer nennen. In dieser Klasse soll es eine Methode geben, die Deserialize() heißt. Ich verzichte hier jetzt erst mal bewusst auf ein Interface, weil ich denke, dass es für dich bis hier hin schon kompliziert genug ist. Das Interface werde ich später noch mal erklären. Erst mal kümmern wir uns darum, was wir alles brauchen, um eine Datei zu deserialisieren. 
    Was muss die Klasse PersonCsvDeserializer alles wissen, um eine CSV-Datei deserialisieren zu können? Man könnte vielleicht im ersten Schritt auf die Idee kommen, dass die Klasse den Pfad und Dateinamen benötigt. Mit den Informationen aus dem letzten Absatz könnte ein erster Entwurf so aussehen: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {       // ...     } } Als Rückgabewert habe ich IEnumerable<Person> gewählt, weil IEnumerable<T> ein sehr allgemeines Interface ist und einen Enumerator (auf deutsch: Aufzählung; in anderen Sprachen auch Iterator genannt) zur Verfügung stellt, mit dem wir über die Daten iterieren können (mit der foreach-Schleife). Sowohl IList<T>, ICollection<T>, IDictionary<T>, Array und weitere Klassen implementieren dieses Interface und mehr als über die Daten iterieren wollen wir nicht. Wenn wir später damit mehr machen wollen, können wir es leicht mit Linq in eine Collection, List, Array oder auch in ein Dictionary umwandeln. Die Deserialize()-Methode soll also eine Aufzählung von Personen zurückliefern.
    Normalerweise macht man es anders, aber aus einfachheit behaupte ich mal frech, dass die erste Zeile in der CSV-Datei immer ein Header besitzt. In der Implementierung überspringe ich den Header per Linq mit der Skip()-Methode. Die Deserialize()-Methode soll also folgendes machen:
    Die Datei lesen Durch die Datenzeilen iterieren Pro Datenzeile ein Person-Objekt erstellen Die Person-Objekte als Aufzählung zurückliefern Der erste Entwurf könnte daher folgendermaßen aussehen:
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string line in File.ReadAllLines(fileName).Skip(1))         {             string[] elements = line.Split(';');             result.Add(new Person             {                 Name = elements[0],                 Vorname = elements[1],                 Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),                 Alter = Convert.ToInt32(elements[3])             });         }         return result;     } } Die Methode macht zwar was sie soll, aber ist sie wirklich übersichtlich? Nicht wirklich. Wir haben hier mehrere Ebenen miteinander vermischt. Wir können also mit dem Refactoring anfangen. z.B. das
    File.ReadAllLines(fileName).Skip(1) Wofür ist das genau gut? Wenn man den gesamten Kontext kennt, weiß man es zwar aber eigentlich liegt der Code-Abschnitt eine Ebene Tiefer. Es hantiert mit Dateien und hat mit der eigentlichen Aufgabe der Deserialiserung wenig zu tun. Also sollte man diesen Teil in eine separate Methode packen:
    private IEnumerable<string> ReadDataFromFile(string fileName) {     return File.ReadAllLines(fileName).Skip(1); } Somit wandert das Skip(1) in eine tiefere Ebene und interessiert uns in der Deserialize()-Methode nicht mehr. Als nächstes fällt aber auf, dass wir ein String mit Split() in ein Array teilen und aus diesem Array dann die einzelnen Personendaten herausfischen. Diesen Vorgang nennt man auch Parsing. Also könnten wir diesen Teil auch in eine Methode auslagern:
    private Person Parse(string serializedData) {     string[] elements = serializedData.Split(';');     return new Person     {         Name = elements[0],         Vorname = elements[1],         Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),         Alter = Convert.ToInt32(elements[3])     }; } Unsere Klasse sieht dann bis jetzt folgendermaßen aus: 
    public class PersonCsvDeserializer {     public IEnumerable<Person> Deserialize(string fileName)     {         IList<Person> result = new List<Person>();         foreach (string serializedData in ReadDataFromFile(fileName))         {             Person person = this.Parse(serializedData);             result.Add(person);         }         return result;     }     private IEnumerable<string> ReadDataFromFile(string fileName)     {         return File.ReadAllLines(fileName).Skip(1);     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Nun ist Deserialize() doch recht gut lesbar. Wir lesen die Daten aus der Datei, parsen die Daten und erhalten ein Person-Objekt, welches wir dann in eine Liste packen und zum Schluss geben wir die Liste zurück.
    Es gäbe hier noch weiteres Verbesserungspotenzial aber ich belasse es erst mal hierbei. Ein paar Hinweise gebe ich aber noch:
    Fehler-Handling? Was passiert, wenn z.B. die Datei nicht existiert? Ist das erzeugte Objekt List<Person> wirklich eine gute Wahl? Angenommen, wir haben es mit einer riesigen CSV-Datei (mehrere Gigabytes) zu tun, die größer ist, als unser Arbeitsspeicher. Hier schmeiße ich mal das "yield return"-Schlüsselwort in den Raum. Auch ist das indexierte Zugreifen auf das Array in der Methode Parse() nicht wirklich glücklich gelöst. Was passiert nämlich, wenn mal eine Spalte in der Datei hinzukommt? Dann muss man ja auch den Code anpassen. Das will man aber eigentlich gar nicht. Zu diskutieren wäre auch, ob die Variable fileName nicht doch besser eine Instanzvariable sein sollte, die per Konstruktor reingereicht wird. Es fällt ja auf, dass die Methoden Deserialize() und ReadDataFromFile() den Dateinamen benötigen. Also stellt fileName ja eine gewisse Abhängigkeit dar, die die Klasse benötigt, um arbeiten zu können.  Als Überlegung kannst du ja selber mal schauen, wie man mit solchen Situation umgehst.
    Um später im Hauptpgramm alle Personen zu iterieren könntest du nun folgendes schreiben:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");     PersonCsvDeserializer deserializer = new PersonCsvDeserializer();     foreach(string fileName in fileNames)     {         IEnumerable<Person> persons = deserializer.Deserialize(fileName);         foreach (Person person in persons)         {             // ...         }     } } Nach dem selben Prinzip, wie bei der PersonCsvDeserializer-Klasse kannst du ja mal überlegen, wie man nun diesen Code refactoren an.
    Ab hier wird es noch etwas technischer und tiefgreifender. Ich möchte dir noch zwei Techniken zeigen, die du aber erst mal nicht umsetzen brauchst.
    "Inversion of Control" und "Dependeny Injection"
    In der Klasse PersonCsvDeserializer fällt auf, dass die Klasse von einer Datei abhängig ist aber die Daten können vielleicht aus einer Datenbank kommen oder wir schreiben die CSV-Daten direkt in eine grafische Oberfläche. Möchte man jetzt für jeden Anwendungsfall eine eigene Klasse schreiben? Eigentlich nicht. Die Abhängigkeit zur Datei muss also aufgelöst werden. Das .Net-Framework bietet ja die abstrakte Klasse TextReader, die so ziemlich alles darstellen kann. Ein Reader, der eine Datei liest oder aus einem TCP-Stream oder aus einer Datenbank, etc. Anstatt also den Dateinamen reinzureichen, könnte man auch ein TextReader reinreichen.
    Hier mal ein Beispiel, wie so eine Klasse aussehen könnte:
    public class PersonCsvDeserializer {     private TextReader reader;     private bool isHeaderSkipped;     public PersonCsvDeserializer(TextReader reader)     {         this.reader = reader;     }     public IEnumerable<Person> Deserialize()     {         string serializedData;         while ((serializedData = this.ReadNextData()) != null)         {             Person person = this.Parse(serializedData);             yield return person;         }     }     private string ReadNextData()     {         string serializedData = this.reader.ReadLine();         if (!this.isHeaderSkipped)         {             this.isHeaderSkipped = true;             return this.ReadNextData();         }         return serializedData;     }     private Person Parse(string serializedData)     {         string[] elements = serializedData.Split(';');         return new Person         {             Name = elements[0],             Vorname = elements[1],             Geschlecht = (Geschlecht)Enum.Parse(typeof(Geschlecht), elements[2]),             Alter = Convert.ToInt32(elements[3])         };     } } Die Main-Methode sieht dann so aus:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);                          IEnumerable<Person> persons = deserializer.Deserialize();             foreach (Person person in persons)             {                 // ...             }         }       } } Zugegeben, in diesem Beispiel ist die Klasse PersonCsvDeserializer etwas komplizierter geworden aber es ist jetzt egal, woher die Daten stammen, solange wir ein TextReader in den Konstruktor schieben. Das reinrechen der Abhängigkeit in den Konstruktor nennt sich auch "Dependeny Injection". In diesem Beispiel habe ich auch das yield return verwendet. Da wir jetzt nur noch maximal den Speicher für ein Person-Objekt verbrauchen, könnte die Klasse eigentlich nun unendlich viele Daten deserialisieren. Ein Problem stellt aber immer noch die Indexierung des Arrays dar aber das überlasse ich jetzt dir.
    Das Interface
    Das letzte, was ich noch schreiben wollte, wäre ein geeignetes Interface für den Deserializer. Wollen wir jetzt mehrere Deserializer schreiben oder einen Deserializer als Abhängigkeit in eine Klasse reinreichen, ist ein Interface geeignet, damit es später egal ist, um welchen Deserializer es sich handelt. Man könnte sich ja auch vorstellen, dass die Daten nicht in einer CSV-Datei stecken, sondern in einer XML-Datei. Dafür wäre folgendes Interface recht nützlich
    public interface IDeserializer<T> {     IEnumerable<T> Deserialize(); } Mit diesem Interface könnten wir sogar das hässliche using im Hauptprogramm wieder loswerden. Ich finde, das using stört im Lesefluss. Wir haben ja jetzt eine Klasse, die CSV-Daten aus unterschiedlichsten Quellen von Personen deserialisieren kann. Was hindert uns nun daran, einen weiteren Deserializer zu bauen, der aus Dateien deserialisiert? Beispiel:
    public class PersonCsvFileDeserializer : IDeserializer<Person> {     private string fileName;     public PersonCsvFileDeserializer(string fileName)      {         this.fileName = fileName;     }     public IEnumerable<Person> Deserialize()     {         using (TextReader reader = File.OpenText(fileName))         {             PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader);             return deserializer.Deserialize();         }     } } Das using wurde nach PersonCsvFileDeserializer und somit eine ebene tiefer verschoben. Wenn du Dependecy Injection verstanden hast, dann würde dir auffallen, dass die Zeile 
    PersonCsvDeserializer deserializer = new PersonCsvDeserializer(reader); eigentlich böse ist, da es eine Abhängigkeit darstellt, die wiederum in den Konstruktor gehört. Ich habe sie aber erst mal hier drinnengelassen, weil das sonst wieder bedeuten würde, dass das using wieder ins Hauptprogramm rein müsste. Eigentlich müsste man sich eine Fabrik-Methode ausdenken, die den PersonCsvFileDeserializer zusammenbaut. Die habe ich hier aber weggelassen. Die kannst du dir ja ausdenken.
    Das Hauptprogramm würde dann so aussehen:
    static void Main(string[] args) {     string sourcePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\blabla";     IEnumerable<string> fileNames = Directory.GetFiles(rootPath, "*.csv");          foreach(string fileName in fileNames)     {         PersonCsvFileDeserializer deserializer = new PersonCsvFileDeserializer(fileName);                      IEnumerable<Person> persons = deserializer.Deserialize();         foreach (Person person in persons)         {             // ...         }      } } Das wäre doch schon wieder ein Schritt übersichtlicher. 
    Wie du also siehst, haben wir allein nur für das Einlesen von den CSV-Dateien drei Klassen:
    Person PersonCsvDeserializer PersonCsvFileDeserializer und ein Interface:
    IDeserializer<T> geschrieben. Man braucht also kein mega großes Projekt, um mehrere Klassen zu schreiben. Es reicht auch schon was ganz einfaches. Man sollte sich immer bewusst machen, dass Klassen immer nur eine Aufgabe machen sollten und Methoden Teilaspekte dieser Aufgabe sind und sie sollten auch nicht mehr machen, als eine Sache. Es macht auch nichts, wenn man zum Anfang Spagetticode schreibt und diesen später nach und nach einem Refactoring unterzieht. Niemand ist perfekt und niemand schreibt perfekten Code. Man fängt also immer erst mal an und arbeitet sich Schritt für Schritt an eine geeignete und saubere Lösung. Selbst meine Lösung ist mit Sicherheit nicht perfekt und ich habe auch nicht die Weisheit mit Löffeln gefressen. Wenn du mein Beitrag richtig verfolgt haben solltest, hast du vielleicht auch gemerkt, dass ich erst mal eine Lösung geschrieben habe und sie dann nach und nach verfeinert und verbessert habe. Das Wissen kommt erst mit Erfahrung und Erfahrung sammelt man nur, indem man es ausprobiert und darüber mit anderen diskutiert. Also trau dich.
    So, das reicht auch fürs erste. Ich denke, das ist erst mal genug Input.
     
  4. Like
    Whiz-zarD hat eine Reaktion von JimTheLion erhalten in C# Programmabsturz IO-Exception   
    Die *.pdb-Datei erzeugt keine Datei. Sie dient lediglich dazu, mehr Debug-Informationen zu bekommen. Beispiel:
    static void Main(string[] args) { try { throw new NullReferenceException(); } catch (Exception e) { Console.WriteLine(e); } finally { Console.ReadKey(); } } Wenn ich jetzt das Programm ausführe, erhalte ich folgende Nachricht in der Konsole:
    System.NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt. bei ConsoleApplication3.Program.Main(String[] args) in d:\dokumente\visual studio 2015\Projects\ConsoleApplication3\ConsoleApplication3\Program.cs:Zeile 16. Ich kann also sehen, dass die Exception in Zeile 16 in der Datei Program.cs geworfen wurde und die Zeile ist folgende:
    throw new NullReferenceException(); Auch sollte man die Meldung der Exception verstehen. Die obere Zeile ist die Art der Exception mit einer optionalen Nachricht. Der Teil:
    bei ConsoleApplication3.Program.Main(String[] args) Sagt, wo die Exception ausgelöst wurde und der Teil:
    in d:\dokumente\visual studio 2015\Projects\ConsoleApplication3\ConsoleApplication3\Program.cs:Zeile 16. Ist der Stacktrace. Der Stacktrace kann noch deutlich länger werden, je nach dem wie viele Ebenen der Code absteigt und wieder raufklettern muss, um die Exception zu fangen.
    Wenn ich jetzt aber die *.pdb-Datei lösche und das Programm starte, erhalte ich nur folgende Meldung:
    System.NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt. bei ConsoleApplication3.Program.Main(String[] args) Ich sehe also nicht mehr, wo die Exception geworfen wurde, sondern lediglich nur noch die Methode, wo sie ausgelöst wird.
    Für dich ist also erst mal der Stacktrace wichtig, um zu wissen, wo überhaupt die Exception aufgetreten ist. Das spart auch schon vieles an Rätselraten. Wenn man die Stelle dann kennt, kann man sich überlegen, was eigentlich passiert ist. Ohne den Stacktrace zu kennen, wird das Debugging sehr schwer.
  5. Like
    Whiz-zarD hat eine Reaktion von thereisnospace erhalten in C# Programmabsturz IO-Exception   
    Die *.pdb-Datei erzeugt keine Datei. Sie dient lediglich dazu, mehr Debug-Informationen zu bekommen. Beispiel:
    static void Main(string[] args) { try { throw new NullReferenceException(); } catch (Exception e) { Console.WriteLine(e); } finally { Console.ReadKey(); } } Wenn ich jetzt das Programm ausführe, erhalte ich folgende Nachricht in der Konsole:
    System.NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt. bei ConsoleApplication3.Program.Main(String[] args) in d:\dokumente\visual studio 2015\Projects\ConsoleApplication3\ConsoleApplication3\Program.cs:Zeile 16. Ich kann also sehen, dass die Exception in Zeile 16 in der Datei Program.cs geworfen wurde und die Zeile ist folgende:
    throw new NullReferenceException(); Auch sollte man die Meldung der Exception verstehen. Die obere Zeile ist die Art der Exception mit einer optionalen Nachricht. Der Teil:
    bei ConsoleApplication3.Program.Main(String[] args) Sagt, wo die Exception ausgelöst wurde und der Teil:
    in d:\dokumente\visual studio 2015\Projects\ConsoleApplication3\ConsoleApplication3\Program.cs:Zeile 16. Ist der Stacktrace. Der Stacktrace kann noch deutlich länger werden, je nach dem wie viele Ebenen der Code absteigt und wieder raufklettern muss, um die Exception zu fangen.
    Wenn ich jetzt aber die *.pdb-Datei lösche und das Programm starte, erhalte ich nur folgende Meldung:
    System.NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt. bei ConsoleApplication3.Program.Main(String[] args) Ich sehe also nicht mehr, wo die Exception geworfen wurde, sondern lediglich nur noch die Methode, wo sie ausgelöst wird.
    Für dich ist also erst mal der Stacktrace wichtig, um zu wissen, wo überhaupt die Exception aufgetreten ist. Das spart auch schon vieles an Rätselraten. Wenn man die Stelle dann kennt, kann man sich überlegen, was eigentlich passiert ist. Ohne den Stacktrace zu kennen, wird das Debugging sehr schwer.
  6. Like
    Whiz-zarD hat eine Reaktion von lilith2k3 erhalten in C# Programmabsturz IO-Exception   
    Und trotzdem hast du nicht meine Frage beantwortet:
    Aus deinem Code entnehme ich dann, dass die Dateien nicht im Netzwerk-Ordner gelesen und gespeichert werden, sondern im Desktop-Ordner?
    Edit:
    Fürs lesen und Schreiben von CSV-Dateien gibt es auch fertige Bibliotheken. Da würde ich mir auch nicht mehr die Mühe machen und es selbst implementieren. Den CsvHelper finde ich persönlich nicht schlecht.
  7. Like
    Whiz-zarD hat eine Reaktion von lilith2k3 erhalten in C# Programmabsturz IO-Exception   
    Also willst du ein Webcrawler schreiben?
    Warum nimmst du denn nicht einen? z.B. HTTrack?
    Die IOException wird auch nicht vom WebClient ausgelöst. Laut Dokumentation werden folgende Exceptions geworfen:
    Exception Condition ArgumentNullException Der address-Parameter ist null.
    WebException Der URI-Format durch Kombination von BaseAddress und address ist ungültig.
    - oder -
    Fehler beim Herunterladen von Daten.
    NotSupportedException Die Methode wurde in mehreren Threads gleichzeitig aufgerufen.
    Dein Problem muss also beim Lesen oder Schreiben der Dateien zu tun haben. Da dieser Teil in deinem Code fehlt, kann ich dazu nichts sagen. Was genau sagt denn die Exception? Wie sieht denn der Stacktrace aus? Wenn die Symbol-Dateien (*.pdb) dabei liegen, bekommt man sogar die Zeilennummer mit, wo die Exception geworfen worde.
  8. Like
    Whiz-zarD hat eine Reaktion von lilith2k3 erhalten in C# Programmabsturz IO-Exception   
    Ähm, eine Anwendung soll eine Datei lesen und eine Datei schreiben und beide befinden sich dann im selben Ordner, wie die Anwendung. Wozu dann ein WebClient? Was hast du denn überhaupt vor? Wozu die beiden Schleifen?
  9. Like
    Whiz-zarD hat eine Reaktion von stefan.macke erhalten in Berufsschule? Sinnlos!   
    Ein paar Ansätze hatte ich hier schon mal erwähnt. z.B. das Üben von DDD, indem z.B. die Schule bzw. der Lehrer als Kunde auftritt oder auch das Üben von TDD mit sog. Code-Katas. Selbst ich treffe mich ab und zu mit ein paar anderen Entwicklern und bearbeiten eine Kata (über eine Meetup-Gruppe). Man kann da sehr viel von den anderen Entwicklern lernen, weil man andere Sichtweisen und Entwicklungsstile kennenlernt. Wenn man gewisse Regeln definiert, dann hat auch jeder mal die Tastatur vor der Nase und nicht nur die dominanten, die eh alles viel besser können. 
    Auch habe ich hier gelesen, dass ein Azubi OOP beigebracht bekommt, indem er in einer WinForms-Anwendung einen Button zum Springen bringt. Was ist denn das für eine Lernmethode? Wieso eine WinForms-Anwendung? Wenn überhaupt, dann sollte man mit einer Konsolenanwendung anfangen, da bei einer Anwendung mit einer grafischen Oberfläche schon zu viel Magie passiert, die im verborgenen bleibt. Um OOP zu lernen braucht man auch keine grafische Oberfläche. Dann sollte man den Azubis erklären, was (abstrakte) Klassen, Interfaces, Generics, etc. sind und dann auch mal ein bisschen unter die Haube schauen und zeigen, wie z.B. eine Liste funktioniert anstatt sich nur mit Arrays zu befassen. Im selben Atemzug kann man vielleicht auch noch erklären, was eine Referenz bzw. ein Zeiger ist. Vielleicht auch die Problematik mit Geldbeträgen erklären, da ich das Gefühl habe, dass selbst vielen Firmen diese Problematik nicht bewusst ist. Letztes Jahr gabs dazu auf der Developer Week in Nürnberg einen Vortrag dazu und dieser Vortrag war sehr gut besucht und viele waren doch sehr erstaunt. 
    Auch sollte man was sinnvolles entwickeln und keine hüpfenden Buttons. In meiner Assistenten-Ausbildung hatten wir im zweiten Semester zur Übung u.a. einen Sudoku-Löser entwickelt. Dafür hatten wir eine Woche zeit. Zum Testen gab uns der Dozent einen Satz an Unittests, die der Löser bestehen musste. Darüber hinaus gab es noch einen zweiten Satz an Unittests, der erst bei der Abgabe zum Einsatz kam. Der beinhaltete "komplexere" Testszenarien. Das zielte eben halt darauf ab, dass wir uns selbst noch Gedanken machen sollten und selbst Unittests ausdenken und auch untereinander austauschen sollten. Entwickelt wurde dann im Pair-Programming und bei der Abgabe wurden dann Fragen zu unserem Code gestellt, um herauszufinden, ob beide tatsächlich den Code verstanden haben oder ob einer nur den Vortänzer macht und den anderen hinterher schleift. Auch benutzte die Schule ein Analyse-Tool, um herauszufinden, ob vielleicht nicht zwei oder mehrere Gruppen den selben Code abgeliefert haben. Der Code wurde dann in einer Quellcodeverwaltung gepflegt.
    Wie wäre es auch mit einer größeren Projektarbeit bzw. eine Art Hackerspace? Das könnte man sogar mit den FISIs verbinden.
    Wie wäre es auch, wenn man einen Gastredner in die Schule holt, der einen kleinen Vortrag zu einem bestimmten Thema hält? Erst neulich habe ich ein 3 Stunden Vortrag zu Microservice in Verbindung mit RabbitMQ angeschaut. Man muss ja das Thema nicht gleich verstehen und es muss nicht gleich sofort eine Klausur darüber geschrieben werden aber es hilft schon, wenn man schon von bestimmten Themen was gehört hat, damit man im Bilde ist, was es sonst noch so für Technologien gibt. Das Ganze kann auch freiwillig sein.
    Da gibt es eigentlich so viele Ideen, wie man den Berufsschulunterricht gestalten könnte aber das größte Problem ist wohl, dass die Lehrer sich selbst damit überhaupt nicht auskennen. Was ich hier manches mal lese, habe ich das Gefühl, dass sie selbst nicht einmal wissen, wovon sie eigentlich reden und nur ein altes Lehrbuch stur abarbeiten. Man müsste wohl erst mal die Lehrer stetig fortbilden, damit sie auch im Bilde sind, was in der Wirtschaft wirklich abläuft bzw. Lehrer so ausbilden, sodass sie ihr Fach auch selbst verstehen.
    Ich habe ja auch nichts dagegen, wenn die Azubis auch etwas BWL lernen aber ich finde, dass hier einfach die Priorität zu hoch liegt und die eigentliche Arbeit als FIAE/FISI zu kurz kommt. Ja, ich weiß, eigentlich soll der praktische Teil in den Firmen stattfinden aber die Firmen haben oft gar nicht die Möglichkeit die Azubis optimal auszubilden, da sie oft nicht das Know-How besitzen, um einen Azubi ausgiebig zu schulen. Ich finde, Berufsschulen könnten hier echt was bewirken, um einen Blick über den Firmen-Tellerrand zu wagen aber dafür muss das Personal - sprich die Lehrer - fachkundig sein. Wenn man OOP erklären möchte, dann sollte man nicht mit hüpfenden Buttons kommen.
  10. Like
    Whiz-zarD reagierte auf thereisnospace in Berufsschule? Sinnlos!   
    Klasse Beitrag Whiz-zarD, dem ich größtenteils erfahrungsgemäß (welches sich auf ca. 2,5 Jahre Berufsausbildung beschränkt) zustimmen kann.

    Schon am Anfang hatte ich mit meinem Ausbilder ein Gespräch über den Rahmenlehrplan, wo er mir auch schnell mitgeteilt hat, dass er seit Jahren mit dem Bundesministerium für Bildung im Gespräch steht und versucht denen mitzuteilen, dass die Ausbildungsordnung einfach veraltet und nicht mehr zeitgemäß ist. Da ich selbst im öffentlichen Dienst arbeite, weiß ich aber wie lang sich solche Prozesse ziehen können, bis letztendlich etwas verändert wird.
    Berufsschultechnisch denke ich mit gemischten Gefühlen zurück. Wir hatten durchaus einige Lehrer, die es drauf hatten. Einer davon saß schon seit längerer Zeit im Prüfungssausschuss und hat uns optimal auf die Prüfungen vorbereitet und trotzdem hilfreichen und guten Unterricht gemacht.
    Wenn man dann aber in Netzwerktechnik hingesetzt wird und gesagt wird "Öffnet mal Wireshark und analysiert die Pakete" ohne wirkliche Aufgabe ist es auch nicht zielführend.
    Grundsätzlich lässt sich aber als Anwendungsentwickler sagen, dass die im Berufsschulunterricht vermittelten Programmiertechniken nicht den heutigen Standard entsprechen und erst recht nicht sauberer Programmierung. Uns wurde zwar das Mehrschichtenmodell oder UML-Diagramme beigebracht, aber dann wurde im nächsten Zug in die View Programmierlogik eingehakt. Das ist durchaus verbesserungswürdig.
  11. Like
    Whiz-zarD hat eine Reaktion von tegberts erhalten in Berufsschule? Sinnlos!   
    Dein Text klingt aber nicht so wirklich danach, als sei alles super.
    Sorry aber das, was ich von dir in diesem Forum gesehen habe, war keine objektorientierte Programmierung. Nur weil man eine objektorientierte Sprache verwendet, heißt es noch lange nicht, dass man auch Objektorientierung anwendet und Struktogramme braucht man im Alltag auch gar nicht. Struktogramme sind auch nicht einfacher zu lesen, als Code. Von daher kann man es auch gleich weglassen.
    Nicht nur das. Auch die Ausbildung gehört für mich komplett überarbeitet.
    Das ist echt schon abenteuerlich, was ich hier so über die Abschlussarbeiten- und Prüfungen lese.
    Die Azubis lernen veraltete und überholte Methoden, nur um die Prüfung bestehen zu können. Vieles, was sie in der Berufsschule lernen, können sie auch gleich nach der Abschlussprüfung wieder vergessen, weil es entweder in der Praxis gar nicht mehr angewendet wird oder so selten, sodass man sowieso erst mal einen Blick auf Google oder Wikipedia werfen muss. Meiner Meinung nach, setzt man auch die Prioritäten komplett falsch. Anstatt den Azubis drei Jahre lang Pseudo-Code und UML-Diagramme einzutrichtern, sollte man - vor allem den Anwendungsentwicklern - mehr sauberen Code beibringen aber offenbar ist selbst das in der Abschlussprüfung zu viel verlangt. Die offiziellen Lösungen der Programmieraufgaben in der Abschlussarbeit ist so ein schlechtgeschriebener Code, dass man davon Pickel bekommt, wenn man nur ihn ansieht. Von gravierenden Fehlern ganz zu schweigen.
    Was das Abschlussprojekt angeht, bin ich auch kein Freund vom Pflichten- und Lastenheft, was aber offenbar vielfach von der IHK gefordert wird. Das sind auch Fragmente aus einer alten Zeit. Ich habe inzwischen schon viele Projekte gesehen, wo ein Pflichten- und Lastenheft geschrieben wurde (ich habe auch schon sowas das eine oder andere Mal geschrieben) und ständig sind solche Projekte in die Hose gegangen, weil sich die Anforderungen ändern können oder gewisse Dinge sowohl vom Kunden als auch vom Entwickler nicht vollständig durchleuchtet worden waren. Beliebt sind auch Fehlinterpretationen. Sowohl Kunde als auch Entwickler schreiben den selben Wortlaut und dennoch reden sie einander vorbei. Viel wichtiger ist es, mit den Kunden ständig im Kontakt zu bleiben, damit man schnell auf Änderungen reagieren kann. Auch sollte man den Kunden immer mit dem Status informieren und ihm auch den derzeitigen Zwischenstand ungeschönt präsentieren und ihn nicht vor vollendeten Tatsachen stellen, wie es mit dem Pflichten- und Lastenheft der Fall wäre. Wenn man feste Termine mit den Kunden arrangiert, dann braucht man auch kein Lasten- und Pflichtenheft. Änderungswünsche können dann formlos festgehalten und im nächsten Meeting präsentiert werden.
    Ob und wann sich eine Entwicklung amortisiert hat, ist auch gar nicht die Aufgabe eines Entwicklers. Der Entwickler soll nur analysieren, wie aufwendig eine Implementierung ist. Mehr nicht. Auch lässt sich das als Entwickler oft gar nicht so einfach herausfinden, wie es um die Amortisierung steht. Dies ist nur bei In-House-Lösungen möglich. Wenn man eine Software für einen externen Kunden einführt, muss der Kunde diese Analyse vornehmen, weil der Entwickler kein Einblick in die Interna des Kundens hat. Auch eine Ist-Analyse ist hier gar nicht möglich, wenn der Kunde davon nicht redet.
    Wer in einem echten SCRUM-Team arbeitet, wird von solchen Analyse-Kram sogar ferngehalten. Nicht weil man den Entwickler dumm halten möchte, sondern weil jeder seine Kernkompetenzen besitzt und die Kernkompetenz eines Entwicklers ist die Softwareentwicklung und nicht das Ausloten der Wirtschaftlichkeit. Als Beispiel nehme ich mal diese Abschlussarbeit, die mit 100% bewertet wurde. Die Abschnitte "Projektkosten" und "Amortisationsdauer" lesen sich wie ein Standardtext, den man in jeder Abschlussarbeit reinknallen könnte. Diese Texte haben überhaupt keine Aussagekraft. Viel wichtiger wären doch technische Aspekte. In einem Nebensatz wird erwähnt, dass die Daten historisiert abgelegt werden sollen. Da werde ich als Entwickler doch sehr hellhörig, weil die Historisierung gar nicht trivial ist. Schon gar nicht in einer relationalen Datenbank. Da hätte ich schon ein Kapitel erwartet, dass sich mit diesem Thema auseinandersetzt. Was passiert z.B. wenn eine Spalte in einer Tabelle hinzukommt? Ich will ja nicht, dass die Azubis z.B. die Data Vault-Modellierung bis ins Detail verstehen und auch anwenden können aber wenn man schon so ein Thema anreißt, dann erwarte ich auch, dass man sich damit beschäftigt. Sei es auch nur, dass man die Risiken aufzählt. 
    Meiner Meinung nach müsste das gesamte System reformiert werden. Wer nach drei Jahren immer noch nicht verstanden hat, dass es keine if-Schleifen gibt, wird das nie verstehen. Ich finde, eine Berufsschule sollte dafür sein, um das in Firmen angelernte Wissen zu vertiefen und zu erweitern. z.B. mal ein Blick auf Datenstrukturen werfen, um ein Gespür zu bekommen, welche wann geeigneter ist. Der Unterschied zwischen einer verketteten Liste und einer Arrayliste ist wohl offenbar nur sehr wenigen bekannt oder dass in Java und C# Arrays als assoziative Arrays missbraucht werden, indem man den gesuchten Index als Konstante ablegt. Anstatt sinnlose Amortisierungsrechnungen aufzustellen, sollte man vielleicht mit den Azubis DDD üben (z.B. die Schule als Kunde) oder mit Hilfe von Coding Dojos sauberen Code oder TDD in Verbindung mit Continuous Testing üben. Auch wäre ein Blick auf funktionale Programmiersprachen nicht verkehrt, weil diese Sprachen immer mehr in kommen sind. Nicht weil sie trendige Hipster verwenden, sondern weil einfach gewisse Probleme, die man aus der Objektorientierung kennt, dort nicht existieren und daher für viele Aufgaben besser geeignet sind, als objektorientierte Sprachen.
    TL;DR:
    Gerade die IT-Welt ist so extrem wandelbar. Da ist es einfach nicht klug, einen Ausbildungsrahmenplan bis in alle Ewigkeit in Stein zumeißeln. Auch die Berufsschule und die IHK müssen hier wandelbar sein und Trends erkennen. Kein Azubi hat etwas davon, drei Jahre irgendeinen Quatsch zu lernen, was Prüfungsrelevant ist aber später keine Anwendung mehr findet. Eine Berufsschule sollte dafür da sein, ein Azubi auf den Berufsalltag vorzubereiten nicht nur für die Prüfung. Sie ist dafür da, Defizite, die in den Ausbildungsstätten vorkommen können, aufzuarbeiten und den Azubis ein Blick über den Tellerrand zu ermöglichen.
  12. Like
    Whiz-zarD hat eine Reaktion von Albi erhalten in Umschulung - Angst vorm Versagen   
    Das hat man von Bäckern mit Mehlallergie oder von Friseuren mit einer Ammoniak-Allergie, in den 90ern, auch behauptet.
    Und wo ist denn jetzt nun dein Problem? Was möchtest du von der Community? Sollen sie dir helfen oder nicht? Wenn sie dir helfen soll, dann muss man sich auch mal mit der Community beschäftigen und auch mal die Fragen beantworten aber irgendwie redest du nur um den heißen Brei herum anstatt mit konkreten Antworten zu kommen. z.B. warum du dich für die IT entschieden hast? Du machst eine Umschulung also hast du was davor gemacht aber wieso jetzt die IT? Hattest du schon vorher mit der Softwareentwicklung Kontakt? Wo hast du denn deine Schwierigkeiten? Wenn die Community dir aber nicht helfen soll, wieso hast du diesen Thread überhaupt eröffnet? Möchtest du nur Durchhalteparolen hören?
    Ob du jetzt die einzige bist, die Schwierigkeiten bei der Prüfung hatte oder nicht, ist auch vollkommen irrelevant, denn es geht um dich und nicht um die anderen! Anstatt also immer wieder Ausreden parat zu haben, um nicht die Fragen beantworten zu müssen, wäre es hilfreicher, wenn du mal konkrete Beispiele nennst, wo du Schwierigkeiten hast. Nur so kann man Defizite aus der Welt schaffen.
    Was bringt es dir, nächstes Jahr ein Plan zu machen? Was versprichst du dir dadurch? Was soll der Plan überhaupt aufzeigen?
  13. Like
    Whiz-zarD reagierte auf neinal in Umschulung - Angst vorm Versagen   
    Ich weiß im Moment gar nicht was der Thread überhaupt soll.
    Hier wird nur noch diskutiert. Checkaline antwortet gar nicht mehr. Auch nicht auf Fragen. Kann man sich also eigentlich die ganze Mühe auch sparen.
  14. Like
    Whiz-zarD hat eine Reaktion von KampfKatze erhalten in Wenn man 0 Programmierkenntnisse hätte?   
    C# hat in erster Linie nichts mit .Net zu tun. C# ist eine ECMA- und ISO-Standardisierte Sprache.
    .NET ist eine Entwicklungsplattform, die aus mehreren Komponenten besteht. z.B. WinForms, WPF, ASP.NET, ADO.NET, LINQ, Entity Framework, Common Language Runtime (CLR) etc.
    Die neue Compiler-Plattform Roslyn, von Microsoft, kompiliert den C# Code in CIL Code, der wiederrum von der CLR ausgeführt werden kann. Die CLR ist quasi so was ähnliches, wie die Java Virtual Machine. Als Alternative für .NET gibt es Mono und findet man überwiegend in der Linux-Welt, da die .NET-Welt ursprünglich nur auf Windows angesiedelt war. Inzwischen ist das .NET-Framework auch Open Source und mit .NET Core wird eine Plattformunabhängige Version entwickelt, die sowohl auf Windows, als auch auf Linux arbeitet.
    Der Grund, warum C# fast immer mit .NET in Verbindung gebracht wird, ist halt daher geschuldet, dass die C#-Entwicklung hauptsächlich in der Windows-Welt stattfindet und eben das .NET-Framework als Unterbau verwendet wird. Es kann aber jeder sein eigenen Unterbau bauen, wie es ja mit Mono gemacht wurde.
    C# kann man auch nicht mit C oder C++ vergleichen, da die Unterschiede sehr stark sind. Höchstens die Syntax kann man ein wenig miteinander vergleichen, da Microsoft sich an C/C++ orientiert hat. Objective-C ist eine Sprache von Apple, die sich aber sehr stark an der Sprache Smalltalk orientiert und sich ebenfalls von C# sehr unterscheidet. Objective-C wird hauptsächlich in der iOS-App-Entwicklung verwendet. Swift ist ebenfalls eine Programmiersprache von Apple und ist eine Alternative zu Objective-C.
    Die Bedeutung von Multithreading ist auch in allen Sprachen gleich. 
    Objektorientierte Sprachen haben aber aufgrund ihrer Eigenschaft, viele Schwierigkeiten, Multithreading leicht umzusetzen. (Seiteneffekte, Deadlocks, etc.) Darum gewinnen funktionale Sprachen immer mehr an Bedeutung, da dort diese Schwierigkeiten gar nicht existieren, da jeder Wert immutable - d.h. unveränderbar - ist. Da gibt es keine Seiteneffekte.
    Nur, weil viele Sprachen ein C in einem Namen haben, heißt es noch lange nicht, dass es Untermengen von C sind. Es sind lediglich Sprachen, die Konzepte von C übernommen haben aber eigene Konzepte drumherumgebaut haben, die sich sehr stark voneinander unterscheiden.
  15. Like
    Whiz-zarD hat eine Reaktion von KampfKatze erhalten in Wenn man 0 Programmierkenntnisse hätte?   
    Eine Ergänzung:
    Außerdem fehlt Generics komplett.
    Ich habe mir mal das Video "Einführung in Datentypen und was du in diesem Kapitel lernst" angeschaut. Der erzählt viel Stuss. Man merkt, dass er nur Halbwissen verfügt. z.B. weiß er nicht, dass Variablen nur in einem Sichtbarkeitsbereich haben und er meint, dass Variablen so lange im Speicher bleiben, bis wir sie löschen. Das ist falsch. Variablen mit einem primitiven (integrierten) Wertetyp verlieren ihre Gültigkeit, wenn der Programmfluss ihre Sichtbarkeit verliert. Variablen mit Klasseninstanzen räumt der Garbage Collector weg, wenn der Programmfluss ihre Sichtbarkeit verliert.
    Auch behauptet er, ein Integer-Wert könnte man nicht mit einem double-Wert addieren. Das ist falsch. Natürlich geht das. Das Resultat ist dann ein double-Wert. Der Unterschied zwischen double und float ist für ihn auch nur die Speichergröße. Auf die Genauigkeit geht er gar nicht ein.
    Nee, der Kurs ist selbst für 20 € noch zu teuer. Der Originalpreis von 95 € ist Wucher.
  16. Like
    Whiz-zarD hat eine Reaktion von JimTheLion erhalten in Wenn man 0 Programmierkenntnisse hätte?   
    Eine Ergänzung:
    Außerdem fehlt Generics komplett.
    Ich habe mir mal das Video "Einführung in Datentypen und was du in diesem Kapitel lernst" angeschaut. Der erzählt viel Stuss. Man merkt, dass er nur Halbwissen verfügt. z.B. weiß er nicht, dass Variablen nur in einem Sichtbarkeitsbereich haben und er meint, dass Variablen so lange im Speicher bleiben, bis wir sie löschen. Das ist falsch. Variablen mit einem primitiven (integrierten) Wertetyp verlieren ihre Gültigkeit, wenn der Programmfluss ihre Sichtbarkeit verliert. Variablen mit Klasseninstanzen räumt der Garbage Collector weg, wenn der Programmfluss ihre Sichtbarkeit verliert.
    Auch behauptet er, ein Integer-Wert könnte man nicht mit einem double-Wert addieren. Das ist falsch. Natürlich geht das. Das Resultat ist dann ein double-Wert. Der Unterschied zwischen double und float ist für ihn auch nur die Speichergröße. Auf die Genauigkeit geht er gar nicht ein.
    Nee, der Kurs ist selbst für 20 € noch zu teuer. Der Originalpreis von 95 € ist Wucher.
  17. Like
    Whiz-zarD hat eine Reaktion von JimTheLion erhalten in Wenn man 0 Programmierkenntnisse hätte?   
    Natürlich gibt es auch hier Diskussionsspielraum, ansonsten dürften viele Betriebe gar nicht ausbilden.
    Als Beispiel nenne ich meine Mechatroniker-Ausbildung. Einige Schulkollegen aus meiner damaligen Berufsschulklasse hatten in Firmen eine Mechatroniker-Ausbildung gemacht, die gar nicht die Möglichkeiten hatten, den Ausbildungsrahmenplan vollumfänglich einzuhalten. So ist im Ausbilungsrahmenplan z.B. von "Installieren und Testen von Hard- und Softwarekomponenten" die Rede aber sowas gibt es in Firmen nicht, die z.B. Kettensägen mit Verbrennungsmotoren bauen. Solche Firmen haben dann Wartungsverträge mit IT-Firmen. Die Azubis dürfen dann nicht mal in die Nähe der Server.
  18. Like
    Whiz-zarD hat eine Reaktion von KampfKatze erhalten in Wenn man 0 Programmierkenntnisse hätte?   
    Das ist jetzt eine persönliche Vorliebe von mir, aber ich würde empfehlen, mit der Sprache C# anzufangen. Die Community Editon von Visual Studio ist ja kostenlos erhältlich und bietet alles, was man eigentlich so braucht. Java wäre auch möglich aber ich halte die Entwicklungsumgebungen nicht ganz für anfängertauglich.
    Ich hab das Buch zwar nicht gelesen, aber das Inhaltsverzeichnis und die Leseprobe lesen sich ganz vielversprechend und daher würde ich folgendes Buch empfehlen: Schrödinger programmiert C# Mit 750 Seiten ist es schon ein großer Brocken aber dadrinnen befinden sich sehr viele farbige Bilder und recht wenig Text auf den Seiten. Das lockert vielleicht das Lesen ein wenig auf. 
    Einige werden wohl die Sprache Python empfehlen, weil sie sehr anfängerfreundlich sein soll aber ich muss gestehen, dass ich mit dieser Sprache bis jetzt kaum etwas gemacht habe und auch keine Literatur dafür empfehlen kann.
    Bücher, wie Clean Code würde ich auch noch keinen Anfänger in die Hand drücken wollen. Das würde ich erst machen, wenn die Person auch schon gewisse Erfahrungen gesammelt hat, auf die man aufbauen kann.
  19. Like
    Whiz-zarD hat eine Reaktion von mollotoff erhalten in XML Zeichenkette übertragen   
    Als Text soll ein &-Zeichen vorkommen?
    Dann muss der Text mit als CDATA-Abschnitt definiert werden.
    Beispiel:
    <Tag><![CDATA[Clever & Smart]]></Tag> Oder man nimmt gleich ein Framework, was eine XML-Datei generieren kann und spart sich die Mühe, dies selbst zu implementieren.
  20. Like
    Whiz-zarD hat eine Reaktion von SebastianB. erhalten in SQL Abfrage vergleich   
    Und die Antwort lautet nein, weil das SQL-Statement nicht mal ausführbar ist.
  21. Like
    Whiz-zarD hat eine Reaktion von thereisnospace erhalten in Pseudocode in den Lösungen komplett falsch?   
    Leider ist das überhaupt keine Lösung, Da muss ich @Sawfare recht geben. Ich hatte mich auch gefragt, was nun "ausgaben" sein soll? Da haben sie sich wohl verschrieben, denn im Pseudocode ist von "einkommen_miete" die Rede, was plötzlich da ist.
    Dann darf die For-Schleife auch nicht über "AnzahlGruppe" gehen und dann indexiert auf die Mieter-Daten zugreifen. Wenn du mehr Gruppen hast, als Daten, dann fliegt dir die Anwendung um die Ohren und wenn du mehr Mieter-Daten hast, wird er nicht alle berücksichtigen. Die Lösung geht also davon aus, dass man exakt die selbe Anzahl an Gruppen und Mieter-Daten hat.
    Darüber hinaus ist das ein extrem schlechter Programmierstil, was die IHK hier vom Vorschein gibt und wer später so entwickelt, der gehört erschlagen. Indexiert auf Mehrdimensionale Arrays zugreifen ohne die Semantik zu kennen ist so 80er und selbst unter C würde man dafür Structs verwenden. Das ist echt schon peinlich, was die IHK dort produziert ... 
  22. Like
    Whiz-zarD hat eine Reaktion von Sawfare erhalten in SQL Abfrage vergleich   
    Deine Lösung funktioniert nicht, da du kein GROUP BY angegeben hast. SUM und COUNT können nur auf Gruppen ausgeführt werden. Darum wurde in der oberen Lösung jeweils ein Subselect genommen.
  23. Like
    Whiz-zarD hat eine Reaktion von JimTheLion erhalten in Probleme mit Datenbank abfragen   
    Wenn ich mal einen Knoten im Kopf habe, dann nehme ich mir immer dieses Bild als Hilfe:

    Das Zeigt, welche Menge man bei welchem Join bekommt.
    Ein Right Join benutzt man eigentlich auch recht selten. Am häufigsten sind die Inner und Left Joins. Ich selber entwickle ja auch seit fünf Jahren Stored Procedures mit PL/SQL (Eine Sprache von Oracle) und kann mich nicht daran erinnern, jemals ein Right Join gemacht zu haben. Ich versuche es immer mit einem Left Join abzubilden, weil es dann leichter zu verstehen ist. Man bildet eine Grundmenge und schränkt diese Menge mit einem Left oder Inner Join ein.
  24. Like
    Whiz-zarD hat eine Reaktion von sas86ks erhalten in c# InvalidCastException   
    Natürlich kann man die Welt auch nur mit einer Klasse retten aber das ist nicht Sinn und Zweck der Objektorientierung.
    Bei der Objektorientierung kann man eigentlich jedes simple Problem in mehrere Klassen aufteilen. Wenn man es dann dogmatisch betrachtet, kann man es so weit führen, dass in jeder Klasse nur noch eine Methode steckt.
    Wenn du das Single-Responsibility-Prinzip verstanden und dir auch verinnerlicht hast, bekommst du auch ein Gespür dafür, wie Klassen und Methoden gestaltet werden müssen. Ein Beispiel: Wir wollen eine Klasse schreiben, die eine E-Mail verschicken soll. Das Interface sieht im ersten naiven Schritt so aus:
    public interface IEMailSender { public string From { get; set; } public string To { get; set; public string Title { get; set; } public string Content { get; set; } public void Send(); } Wir sehen hier, dass From und To vom Typ String sind. Da kann alles mögliche drinnen stehen. Wir müssen also prüfen, ob es valide E-Mail-Adressen sind. Der naive Gedanke wäre nun, eine private Methode in der Implementierung des Interfaces zu basteln:
    public class EMailSender : IEMailSender { public string From { get; set; } public string To { get; set; public string Title { get; set; } public string Content { get; set; } public void Send() { if(this.IsVaildEMailAddress(this.From) && this.IsVaildEMailAddress(this.To)) { // TODO: Versende E-Mail } } private bool IsVaildEMailAddress(string potentialEMailAddress) { // TODO: Überprüfung auf korrekte Adresse } } Aber ist die Validierung der E-Mail-Adresse wirklich die Aufgabe des E-Mail-Versenders? Nein.
    Muss der Versender den Titel und den Inhalt explizit wissen? Nein.
    Der E-Mail-Versender soll nur die E-Mail versenden. Mehr nicht.
    Also sollte man anfangen, die einzelnen Bestandteile zu zerlegen. Was brauchen wir denn alles für den E-Mail-Versand? Wir brauchen eine E-Mail und ein Versender. Die E-Mail ist noch mal unterteilt in eine Sender- und Empfänger-Adresse, Titel und Inhalt. Also:
    public interface IEMailSender {     void Send(EMail eMail); } public class EMailSender : IEMailSender {     public void Send(EMail email)     {         if(email == null)             throw new NullReferenceException();                      // TODO: Versende E-Mail     } } public class EMail {     public EMailAddress From { get; set; }     public EMailAddesss To { get; set; }     public string Title { get; set; }     public string Content { get; set; } } public class EMailAddress {     public string Address { get; } public EMailAddress(string address) { if(!this.IsValidFormat(address)) throw new FormatException(); this.Address = address; } private bool IsValidFormat(string potentialEMailAddress) { // TODO: Überprüfung auf korrekte Adresse } } Der E-Mail-Sender muss dann nur noch prüfen, ob er auch eine E-Mail zum Versenden bekommen hat. Die Validierung, ob die E-Mail-Adressen korrekt sind, passiert dann schon beim Setzen der E-Mail-Adresse.
    Der Aufruf erfolgt dann so:
    EMail email = new EMail { From = new EMailAddress("foo@bar.de"), To = new EMailAddress("bar@foo.de"), Title = "Hallo Welt", Content = "Ist das Wetter nicht schön?" } IEMailSender sender = new EMailSender(); sender.Send(email); Mit Dependency Injection könnte man dann noch den EMailSender in die Klasse reinreichen, wo er verwendet wird. Dann würde man innerhalb der Klasse nur noch gegen das Interface arbeiten und wäre von der konkreten Implementierung unabhängig.
    Wie du siehst, haben wir bei diesem Beispiel schon drei Klassen und ein Interface geschrieben.
    Ich kenne jetzt eure Beispiele in der Schule nicht aber wenn ich schon sowas lese, wie "Sitzplatz-Reservierungs-Programm", dann kann ich mir nicht vorstellen, dass man euch wirklich die Objektorientierung richtig erklärt, wenn ihr dafür nur eine Klasse geschrieben habt. Alleine wenn ihr schon eine GUI (WinForms oder WPF) verwendet habt, dann fallen mir schon spontan drei Klassen ein, die man dafür bräuchte, um das Problem gekapselt abzubilden. Das Stichwort wäre hier MVC (Model-View-Controller) bzw. MVP (Model-View-Presenter).
  25. Like
    Whiz-zarD hat eine Reaktion von Panawr erhalten in c# InvalidCastException   
    Ich finde es, ehrlich gesagt, schon ein hartes Stück, dass eine Person, die von Programmierung und offenbar auch von UX (User Experience) keine Ahnung hat und dann eine Software für den produktiven Einsatz entwickelt. Auch wenn du dich dafür interessierst, heißt es nicht, dass es funktioniert, was du da tust. Dein gesamter Code ist sehr fehleranfällig und auch unwartbar. Wenn jemand anders die Software weiterentwickeln soll, steht er dort vor einer Wartungshölle, die dazu führt, dass die Person die Software neuschreiben muss. Auch wenn du diese Software weiterentwickeln willst, wird der Aufwand immer weiter steigen, da immer mehr Fehler und Seiteneffekte hinzukommen.
    Mit Aufrufen aus der Win32-API begibst du dich auf sehr dünnen Eis. Da muss man schon wissen, was man da tut. Die Win32-API ist sog. unmanaged Code. Als Anfänger solltest du lieber die Finger von lassen. Wenn du also schon die Anwendung sperren willst (was ich nicht für schlau halte), dann öffne eine zweite Form mit einem Hinweis-Text. Die darunterliegende Form kann man dann nicht in den Fokus holen.
    Also muss das Admin-Tool mit den Clients kommunizieren. Also fungiert das Admin-Tool als Server und die Clients müssen sich am Server registrieren. Irgendwie muss das Admin-Tool ja mit den Clients kommunizieren oder sollen die Clients tatsächlich alle 10 Sekunden eine Abfrage an die Datenbank starten, die den derzeitigen Status überprüft? Also hast du eine Verzögerung von maximal 10 Sekunden, wo der Anwender weiterhin Eingaben tätigen kann und glaub mir, das wird passieren.
    Ich würde dir echt empfehlen, es sein zu lassen, was du da tust. Offenbar nimmst du ja auch nicht mal Ratschläge an oder versuchst, dich weiterzubilden. Stattdessen versuchst du per Copy&Paste eine Client-Server-Software zu bauen. Das kann nur in die Hose gehen. Nichts für ungut, aber ich sehe derzeit keine Chance dir weiterzuhelfen, da schon das nötige Grundwissen fehlt und davon rede ich nicht mal von Client-Server-Architektur, sondern von allgemeinen Dingen.

Fachinformatiker.de, 2024 by SE Internet Services

fidelogo_small.png

Schicke uns eine Nachricht!

Fachinformatiker.de ist die größte IT-Community
rund um Ausbildung, Job, Weiterbildung für IT-Fachkräfte.

Fachinformatiker.de App

Download on the App Store
Get it on Google Play

Kontakt

Hier werben?
Oder sende eine E-Mail an

Social media u. feeds

Jobboard für Fachinformatiker und IT-Fachkräfte

×
×
  • Neu erstellen...