Announcement

Collapse
No announcement yet.

C# Form-Elemente in Klasse benutzen?

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • #16
    1. Validierung ist immer ein blödes Thema und es ist schwer diese an einem Ort zum Bündeln. Typischerweise macht man das aber als erstes bevor man in irgendeine andere Logik wie z.B. das Speicher in einer Datenbank geht. Analog zum EntryService könntest Du auch einen EntryValidator haben. z.B. in der Art:

    Code:
    public class EntryValidator
    {
      public bool IsValid(Entry entry)
      {
        if(String.IsNullOrEmpty(entry.FirstName))
        {
          return false;
        }
      }
    }
    Je nach Anforderungen kann man hier aber statt dem bool auch eine Liste von Validierungsergebnissen zurückgeben. Im einfachsten Fall z.B. Text:

    Code:
    public class EntryValidator
    {
      public IEnumerable<string> FindValidationErros(Entry entry)
      {
        var errors = new List<string>();
        if(String.IsNullOrEmpty(entry.FirstName))
        {
          errors.Add("Vorname fehlt");
        }
    
        return errors;
      }
    }
    
    //In der Form
    public class MyForm: Form
    {
      private readonly _entryValidator = new EntryValidator();
      private readonly _entryService = new EntryService();
    
      ...
    
      public void OnSaveButtonClick(...)
      {
         var entry = // entry aus UI holen - DataBinding oder per Hand
         var errors = _entryValidator.FindValidationErros(entry);
         if(errors.Any())
         {
           // gib Fehler an UI weiter
           return;
         }
       
         _entryService.Save(entry);
      }
    }
    Sowas in der Art könnte ich mir vorstellen. Je nachdem wie komplex Deine UI ist kannst Du den Rückgabewert der Validierung natürlich auch noch erweitern z.B. um zu erfahren welche Property genau nicht funktioniert hat um z.B. das direkt am dazugehörigen Steuerlement eine Fehlernachricht anzuzeigen.

    2. Das sind automatic Properties. Diese werden standardmäßig mit dem default Wert des Typs gesetzt (in diesem Fall null). Das ist übrigens ein typisches Beispiel für Datencontainer/POCO/Entity. Die sehen in der Regel immer so aus.

    3. Sie sollten sich so wenig wie möglich kennen. Das ist erstmal die goldene Regel. Aber Du solltest nicht auf biegen und brechen versuchen sie getrennt zu halten. Deswegen besser lieber die Logik immer erstmal runter tippen und schauen welche Teile man davon auseinander trennen kann. Als krampfhaft von vornherein zu versuchen alles auseinander zu ziehen und dann Code zu schreiben wo man Würgereiz bekommt wenn man das nächste mal rein schaut.

    4. Ich würde niemals Integers die eine implizite Bedeutung haben als Rückgabewert verwenden. Dafür gibt es enums. Generell gilt meiner Meinung nach: Das Interface grundsätzlich immer so klein wie möglich halten. Das gilt für Eingabeparameter wie auch für Ausgabeparameter. Ich glaube das ist aber eine Geschmacksfrage wie jetzt genau welcher Rückgabewert auszusehen hat. Es ist natürlich auch möglich eine eigene Klasse nur für den Rückgabewert zu erstellen. In komplexeren Szenarien kann auch das eine Lösung sein. (Ich hoffe das war die Antwort auf die Frage, ich konnte nicht genau erkennen worauf Du Dich mit der Frage beziehst)

    Comment


    • #17
      1. Also lagert man das alles komplett so weit wie möglich aus und auch die Überprüfung der Inhalte der Eingabefelder lagert man aus oder macht man das nur, wenn es nicht anders geht wie in meinem Fall?
      Da steckt ein Denkproblem in der Fragestellung. Du prüfst keine Eingabefelder. Du prüfst eine Modelklasse auf ihre Validität. Das die Modelklasse zufällig aus einer UI erstellt/geändert/befüllt wurde sollte keine Relevanz haben. Darum geht es ja oft in der Entwicklung Dinge so voneinander zu trennen das die Dinge die sich voraussichtlich ändern können so isoliert sind das sie keinen Einfluß auf die anderen haben. Spiele während des Entwickeln das "was wäre wenn"-Spiel durch. Wenn ich die UI von Winforms auf Webforms(oder was auch immer) umstelle was muss ich ändern. Wenn ich die Datenbank umstelle, auf eine anderes Modell oder auf JSON,XML oder was auch immer, was muss ich ändern. Wenn ich die Logik ändere was muß ich ändern. Bei der Strukturierung in UI, Logik, Persistenz sollte dann auch jeweils nur die entsprechende Schicht betroffen sein und die anderen unverändert weiterfunktionieren. Wenn das passt bist du auf dem richtigen Weg.

      2. Muss man nicht eigentlich in dem ersten Codestück die Klasse Entry instanzieren oder wieso kennt der da die Propertys?
      In button_AddEntry_Click wird Entry instanziiert. Und der andere Teil ist die Klassendefinition und da muss man nix instanziieren sondern nur benennen. Der Hinweis auf automatische Properties ist ja bereits gefallen.

      3. Also dürfen sich die Logikklassen gegenseitig kennen oder sollte man das auch da soweit wie möglich trennen?
      Es gilt immer das sich die Dinge möglichst wenig kennen sollten. Um da das richtige Maß zu treffen ist aber Erfahrung nötig. Ich könnte dir keine Faustformel an die Hand geben die immer funktioniert.
      Bezogen auf deine Logikklassen würde ich sagen wenn sich bestimmte Datenklassen kennen zum Beispiel eine Personen Klasse die eine Adresse enthält in Form eine Adressen Klasse dann wäre es natürlich wenn sich die entsprechenden beiden Logik Klassen auch kennen.
      Also wenn Person Adresse kennt sollte auch die Personen Logik die Adressen Logik kennen (jeweils aber nicht umgekehrt)

      4. Wie ist das mit dem komplexeren Rückgabewert gemeint? Wie geht man sowas eigentlich an, wenn man einen konkreteren Rückgabewert haben will und von welchem Typ sollte der Rückgabewert dann sein?
      Das kommt jetzt auf die konkreten Anforderungen an. Ein Bool (geht/geht nicht) ist natürlich meist zuwenig. Fanderlf hat ein Beispiel gegeben wie man Fehlermeldungen zurückgeben kann. Wenn die Anforderung lautet was präsentierbares für einen Anwender zu haben reicht das. Aber wenn da noch andere Anforderungen kommen zum Beispiel Lokalisierung von Fehlermeldung (was es schwer macht auf bestimmte Fehler in der UI zu reagieren) oder Fehlerarten die verschieden UI nahe Logik auslösen sollen zum Beispiel eine Fehler,Warnung oder Info Unterscheidung, die sowas wie eine Rückfrage beim User auslösen sollen, aka "es gibt ein kleiner Problem mit deinen Daten wollen sie trotzdem?" dann braucht man eben einen expliziten Response Typ der von dem Benutzer der Logik klassen auswertbar ist.

      1 wäre dann ok, 0 irgendeiner Fehler und -1 Exception?
      Nimm einen Enum auch wenn der am Ende aerstmal nicht mehr als ok /nicht ok enthalten würde. a.) Das kann noch wachsen und b.) ein andere Entwickler (und du selbst mit ein wenig zeitlichem Anstand) müßte irgendwo nachgucken was 17 bedeutet ein "ValidationResult.Incomplete" ist aber sprechend ohne das man wo nachgucken muss. Exceptions solltest du eher nicht abfangen und auf irgendeine Fehler Enum Typ mappen. Nur wenn du besondere Sicherheitsanforderungen hast und bestimmte Interna verheimlichen must solltest du das in Betracht ziehen. Ansonsten hängen an Exceptions Daten (zum Beispiel der Stacktrace) die eminent wichtig sind für die Fehlersuche die sollte man nicht unter den Tich fallen lassen.

      Zum Beispiel dein Code hier

      catch(Exception ex)
      {
      MessageBox.Show("Fehler!\n\n" + ex.Message, "Fehler!", MessageBoxButtons.OK, MessageBoxIcon.Error);
      }
      hat sicherlich bei jedem der halbwegs erfahrenen Entwickler der das gelesen hat Augenverdrehen ausgelöst
      Wenn sich ein Benutzer bei dir meldet und sagt "Ich weiß nicht was ich gerade gemacht habe aber dein Programm hat was von 'Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.' gefasselt. Bring das in Ordnung". Kannst du nur noch mit den Schultern zuckern weil du die wirklich wichtigen Informationen der Exception unter den Tisch fallen lässt.
      Zuletzt editiert von Ralf Jansen; 08.03.2014, 15:21.

      Comment


      • #18
        Das ist doch sogar ne Enum oder?

        Originally posted by fanderlf View Post
        Code:
        public class EntryValidator
        {
          public IEnumerable<string> FindValidationErros(Entry entry)
          {
            var errors = new List<string>();
            if(String.IsNullOrEmpty(entry.FirstName))
            {
              errors.Add("Vorname fehlt");
            }
        
            return errors;
          }
        }
        4. Ich würde niemals Integers die eine implizite Bedeutung haben als Rückgabewert verwenden. Dafür gibt es enums. Generell gilt meiner Meinung nach: Das Interface grundsätzlich immer so klein wie möglich halten. Das gilt für Eingabeparameter wie auch für Ausgabeparameter. Ich glaube das ist aber eine Geschmacksfrage wie jetzt genau welcher Rückgabewert auszusehen hat. Es ist natürlich auch möglich eine eigene Klasse nur für den Rückgabewert zu erstellen. In komplexeren Szenarien kann auch das eine Lösung sein. (Ich hoffe das war die Antwort auf die Frage, ich konnte nicht genau erkennen worauf Du Dich mit der Frage beziehst)
        Naja, ich glaube das ist in meinem Fall eher nicht nötig, bzw. überdimensioniert.
        Ich würde nur gerne auch die Exceptions mit ausgeben, wenn diese denn auftreten.

        Man gibt ja wie oben im Beispiel dann den String an den Anwender zurück oder? Dann kann man diesen ja mit in die MessageBox geben aber wie kriege ich nachher da noch die Exception mit?

        Originally posted by Ralf Jansen View Post
        Nimm einen Enum auch wenn der am Ende aerstmal nicht mehr als ok /nicht ok enthalten würde. a.) Das kann noch wachsen und b.) ein andere Entwickler (und du selbst mit ein wenig zeitlichem Anstand) müßte irgendwo nachgucken was 17 bedeutet ein "ValidationResult.Incomplete" ist aber sprechend ohne das man wo nachgucken muss. Exceptions solltest du eher nicht abfangen und auf irgendeine Fehler Enum Typ mappen. Nur wenn du besondere Sicherheitsanforderungen hast und bestimmte Interna verheimlichen must solltest du das in Betracht ziehen. Ansonsten hängen an Exceptions Daten (zum Beispiel der Stacktrace) die eminent wichtig sind für die Fehlersuche die sollte man nicht unter den Tich fallen lassen.

        Zum Beispiel dein Code hier

        hat sicherlich bei jedem der halbwegs erfahrenen Entwickler der das gelesen hat Augenverdrehen ausgelöst
        Wenn sich ein Benutzer bei dir meldet und sagt "Ich weiß nicht was ich gerade gemacht habe aber dein Programm hat was von 'Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.' gefasselt. Bring das in Ordnung". Kannst du nur noch mit den Schultern zuckern weil du die wirklich wichtigen Informationen der Exception unter den Tisch fallen lässt.
        Also sollte man die ex.Message und den ex.Stacktrace und eventuell auch ex.Data mitgeben, wäre das dann genug?
        Aber du hast auf jeden Fall recht.
        Nur, warum sollte man keine Exceptions abfangen?! Das muss man doch sogar oder nicht? Ansonsten verstehe ich nicht, wie du das meinst.

        Noch 2 Fragen dazu:

        Wenn ich jetzt aber einzelne Einträge prüfen will, dann kann ich das ja so nicht mit isValid machen oder?
        Es geht ja nachher auch um die einzelnen Einträge, dann bräuchte ich ja noch ne extra Methode, also so?

        Code:
        public bool isValidSingle(string entry)
        {
            return (entry != null && !string.IsNullOrWhiteSpace(entry));
        }
        denn einzelne Einträge kann ich ja so nicht prüfen?

        Code:
        if(checkEntry.isValid(entry.street)
        {
        
        }
        Was mir grad noch eingefallen ist ... wenn eine Exception auftritt wird doch nur noch der catch-Block ausgeführt oder? Also auch wenn der Funktionsblock noch weiter geht, dann wird nach dem Catch schluß gemacht oder?
        Das heißt, wenn eine Connection offen ist, dann muss man diese noch im Catch-Block schließen oder?
        Also so würde die Connection noch offen bleiben, wenn ein Fehler auftritt: (Nur ein Beispiel auch ohne CommandText)

        Code:
        public bool createSomething()
        {
            try
            {
                sql_connection.Open();
                sql_command.ExecuteNonQuery();
                MessageBox.Show("Die Operation wurde erfolgreich ausgefühtrt!", "Erfolg",
                                    MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
            }
            catch(Exception ex)
            {
                MessageBox.Show("Es ist ein Fehler auftgetreten!\n\n" + ex.Message, "Fehler",
                                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            sql_connection.Close();
            return true;
        }
        Dann würde die Funktion ja auch nichts zurückgeben oder wäre der Rückgabewert dann automatisch false? und die Connection wäre wie gesagt noch offen.?
        Zuletzt editiert von Threin; 09.03.2014, 01:41. Reason: Ergänzung

        Comment


        • #19
          Das ist doch sogar ne Enum oder?
          Nein. enum != IEnumerable

          Man gibt ja wie oben im Beispiel dann den String an den Anwender zurück oder? Dann kann man diesen ja mit in die MessageBox geben aber wie kriege ich nachher da noch die Exception mit?
          Gar nicht. Wie der Name Exception schon andeutet sind die ja explicit für Ausnahmefälle gedacht also lass die einfach per normalen Exceptionverhalten zum Aufrufer zurücklaufen.

          Also sollte man die ex.Message und den ex.Stacktrace und eventuell auch ex.Data mitgeben, wäre das dann genug?
          Warum nicht einfach die Exception? Zum anzeigen Exception.Message und/oder Exception.ToString zum wegloggen Exception.ToString.

          Nur, warum sollte man keine Exceptions abfangen?! Das muss man doch sogar oder nicht? Ansonsten verstehe ich nicht, wie du das meinst.
          Natürlich. Aber nur wenn man auch weiß was man dann damit anstellt. Einfach anzeigen ist kein behandeln sondern ein alleine lassen des Users mit dem Problem der damit noch viel weniger anfangen kann als du. In deinem ersten Code hast du einfach jede Exception angezeigt und dann das Programm weiterlaufen lassen. Bei einem Timeout auf der DB vielleicht noch ok. Bei einem OutOfMemory, Stackoverflow bestimmt nicht dann wäre ein einfach weitermachen vermutlich sogar gefährlich.
          Die Regel ist üblicherweise nur Exceptions zu fangen und zu behandeln von denen man weiß das sie ungefährlich sind oder von denen du weißt was dann zu tun ist der Rest sollte in erster Näherung zum beenden der Anwendung führen.

          Wenn ich jetzt aber einzelne Einträge prüfen will, dann kann ich das ja so nicht mit isValid machen oder?
          Verschieden Test verschieden Methoden.

          Das heißt, wenn eine Connection offen ist, dann muss man diese noch im Catch-Block schließen oder?
          Neben try ... catch gibt es auch try ... finally wenn man bestimmte Dinge imemr ausgeführt haben will egal ob Fehler oder nicht.
          Das was man üblicherweise tut ist die Kombination aus der IDisposable.Dispose Methode und dem using Block.

          Und in erster Näherung recycelt man SqlConnections nicht selbst sondern erzeugt sich eine wenn man eine braucht (im Hintergrund poolt das System die Connections eh schon)
          und läßt sie automatisch disposen (freigeben was schließen beinhaltet).
          Das sieht dann so aus.

          [HIGHLIGHT=C#]using(SqlConnection con = new SqlConnection(meinLieberConnectionstring))
          {
          con.Open();
          using (SqlCommand cmd = con.CreateCommand())
          {
          cmd.CommandText = meinLieberCommandText;
          cmd.ExecuteNonQuery(); // oder ExecuteScalar, ExecuteReader oder was auch immer
          }
          }
          [/HIGHLIGHT]

          Um das ganze kannst du noch einen Exception Block packen wenn da etwas passiert von den du weißt das du das sinnvoll behandeln muß. Schließen und aufräumen ist dann schon durch den using Block erfolgt.

          Dann würde die Funktion ja auch nichts zurückgeben oder wäre der Rückgabewert dann automatisch false?
          Nach dem catch ist die Exception weg das schließen und zurückgeben von true wird also ausgeführt. Wenn du eine Exception behandeln willst aber dann doch die Exceptionweiter werfen möchtest dann setzte ein throw; ans ende des Exceptionblocks dann wird die gefangene Exception nochmal geworfen (inklusive originalem Stacktracte etc.). Hier solltest du aber eher wie oben gezeigt einen using Block verwenden.

          Comment


          • #20
            Hmm ... wie kriege ich das denn hin, wenn die Exception von der Klasse Database geworfen wird?
            Das sah bzw. sieht ja so aus:

            Code:
            try
            {
                // Was auch immer
            }
            catch(Exception ex)
            {
                MessageBox.Show("Fehler!\n\n" + ex.Message, "Fehler!",
                                 MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            wie kriege ich denn dann die ex, also die Exception an den Aufrufer zurück? Das geht doch so nicht oder?
            Dann werde ich das jetzt einfach immer so machen, wie du das gesagt hast, also mit ex.ToString() und das reicht dann ja auch?

            Zu dem using:
            Also benutzt man das immer, wenn etwas mit new erzeugt wird und dann wird auch bei einem Fehler automatisch die Connection geschlossen und auch bei einem normalen Verlauf ebendso?

            Zu dem catch:
            Also läuft der Block dann trotzdem noch weiter zuende?
            Ich will ja nicht in dem Block weiter machen, sondern er soll dann ja auch weiter zuende laufen, was ja auch der normale Verlauf ist oder liege ich da falsch?

            Comment


            • #21
              wie kriege ich denn dann die ex, also die Exception an den Aufrufer zurück? Das geht doch so nicht oder?
              einfach keinen try ... catch Block machen sondern es dem Aufrufer überlassen? Oder wie gesagt catchen und tun was immer zu tun ist und dann throw aufrufen damit wird die Exception weitergeworfen so als wäre sie nicht gefangen worden.
              Ich wiederhole try ... catch Block aber nur machen wenn der auch was sinnvolles zum bereinigen des Problem veranstalten kann. Wenn nicht soll sich dich nächst höhere Ebene darum kümmern. Und wenn keinem was sinnvolles einfällt dann muß sich die Anwendung halt beenden da ein potentiell gefährliches Problem vorliegt. Du must dich um die potentiellen Problem der Anwendungen kümmern wenn möglich nicht der User in dem du im einfach jedes per Exception gemeldetes Problem vor die Füsse wirfst (per MessageBox.Show).

              Also benutzt man das immer, wenn etwas mit new erzeugt wird und dann wird auch bei einem Fehler automatisch die Connection geschlossen und auch bei einem normalen Verlauf ebendso?
              Nein bei allen Klassen die IDisposable implementieren (DBConnection Klassen gehören z.B. dazu). Das sind üblicherweise Klassen die mit Resourcen arbeiten die nicht direkt zu .Net gehören (Datenbank Connections, Netzwerk Verbindungen, Files etc.) die zu einem steuerbaren Zeitpunkt freigeben werden müssen (der Garbage Collector von .Net Klassen läuft ja irgendwann und nicht gezielt dann wenn man es braucht).

              Also läuft der Block dann trotzdem noch weiter zuende?
              Ich will ja nicht in dem Block weiter machen, sondern er soll dann ja auch weiter zuende laufen, was ja auch der normale Verlauf ist oder liege ich da falsch?
              welcher Block der try-Block? Nein an der Stelle des Auftretens der Exception wird der Block beendet und es wird zum ersten catch Block gesprungen der diese Sorte von Exceptions behandelt.

              Comment


              • #22
                Theoretisch schließt .Net die Datenbankverbindung wenn Du sie in einem using benutzt und dort eine Exception fliegt. Wir hatten schon ab und an das Problem dass das nicht passiert ist, aber das lag wohl eher an schlechten DB Treibern als am using Statement.

                Exceptions müssen ja nicht dort gefangen werden wo sie entstehen. Ausser Du kannst damit etwas sinnvolles tun wie Ralf schon geschrieben. Im Prinzip kannst Du auch in der Form um die Funktion in der anschließend der Datenbank Aufruf stattfindet ein try ... catch hinbauen und dort auf Exceptions reagieren. Im Prinzip würde ich allerdings so wie es Ralf auch schon geschrieben hat die Exception loggen und das Programm sofort beenden. Du musst bei unbehandelten Exceptions immer davon ausgehen dass ein Programm in einem korrupten Zustand ist. Deswegen ist es eigentlich immer angebracht die Exception zu loggen und das Programm dann zu schließen.
                Wenn das allerdings nur ein kleines Programm für Dich zum Spielen ist was andere Endbenutzer niemals sehen werden ist die Lösung mit der Textbox meiner Meinung nach vollkommen ausreichend.

                Zum Thema DB Connections: Du solltest auf jeden Fall connection Pooling verwenden. Das heisst Du dass das .Net Framework ständig einige Verbindungen offen hält und sich aus dem Pool bei Bedarf welche nimmt. Eine Connection mit einer DB aufzubauen dauert für Computerverhältnisse relativ lange. Wenn Du connection pooling allerdings aktiviert hast (ich glaube das ist sogar standardmäßig an), dann dauert der Aufbau der Verbindung nicht sehr lange, es ist ja eigentlich auch nur das Zuweisen einer bereits bestehenden Verbindung, und Du solltest für jede Transaktion eine "neue" Verbindung aufbauen.

                Comment


                • #23
                  Neues Thema, neuer Thread

                  http://entwickler-forum.de/showthrea...xen-zu-sperren
                  Christian

                  Comment

                  Working...
                  X