Good to know / Must have



  • Gibt es eigentlich in C# Dinge die man als Entwickler in der Sprache unbedingt beherzigen sollte? Ich meine sowas wie const correctness in C++?

    Ich meine, ich bekomme mittlerweile so ziemlich alles entwickelt was ich mir wünsche, aber was sind so Sachen die man verinnerlicht haben sollte; was unterscheidet den Hobby-Entwickler vom Profi (mal abgesehen möglicherweise von einem Arbeitsvertrag)?



  • Wieso nicht? schrieb:

    was unterscheidet den Hobby-Entwickler vom Profi (mal abgesehen möglicherweise von einem Arbeitsvertrag)?

    Die Frage macht so für mich keinen Sinn, denn der Unterschied ist per Definition nur dass der eine es beruflich macht und der andere nicht.
    Davon abgesehen gibt es leider auch in der Praxis wenig Unterschiede.

    Vielleicht solltest du die Frage anders formulieren 😉



  • In C# kannst du dich nur schwer von anderen Programmierern abgrenzen. Es läuft eigentlich immer auf die Verwendung der aus dem .NET-Framework stammenden Klassen zurück. Den Speicher kann man außen vor lassen, weil der GC alles erledigt und die Codestruktur ergibt sich weitestgehend von selbst.
    Einen vollständig funktionstüchtigen Webbrowser entwickeln heute schon Schüler der 8. Klasse mit C# (und der Klasse WebBrowser aus dem .NET Framework).

    Das schlimme ist eben, selbst mit umfangreichen internen Kenntnissen über Protokollschichten und Netzwerkprogrammierung könntest du dabei nichts anfangen.



  • Naja, es gibt halt wirklich viele Programmierer in C++. Die meisten können auch das umsetzen, was sie gerne machen möchten.
    Viele können es aber nicht gut genug um sich damit in einem Job zu behaupten. Wenn ich das bisher richtig gesehen habe, sollte man in C++ const correctness nicht nur gehört haben, sondern es auch umsetzen können, wenn man einen Job als C++ Entwickler sucht.
    Ähnliches suche ich für C#. Wollte ich C++ Entwickler werden, würde ich mich jetzt über const correctness informieren und versuchen das in jedem meiner Codes umzusetzen.
    Was wären solche Sachen in C#?



  • 3eeretette schrieb:

    In C# kannst du dich nur schwer von anderen Programmierern abgrenzen. Es läuft eigentlich immer auf die Verwendung der aus dem .NET-Framework stammenden Klassen zurück. Den Speicher kann man außen vor lassen, weil der GC alles erledigt und die Codestruktur ergibt sich weitestgehend von selbst.

    Und das ist leider ganz schlimmer Bullshit.

    Ich sag' ja die Frage gehört anders formuliert.
    Natürlich gibt es in C# etliche Dinge die man wissen und beachten sollte wenn man sauber Programmieren will.



  • Natürlich gibt es in C# etliche Dinge die man wissen und beachten sollte wenn man sauber Programmieren will.

    Und genau solche suche ich. Worauf kann ich achten, um in meinem Handwerk besser zu werden.



  • Die Sache die mir dabei immer als erstes einfällt ist IDisposable .

    Also das Verständnis worum es bei der Sache geht (deterministische Verwaltung von Resourcen ausser "managed Speicher"), und die konsequente Anwendung von IDisposable . Sowohl aktiv als auch passiv.

    Aktiv heisst: An allen Stellen wo es nötig ist IDisposable.Dispose aufrufen. Idealerweise indirekt, also mittels using . Bzw. wenn das nicht geht dann halt "manuell".
    Passiv heisst: In allen Klassen wo es nötig ist IDisposable korrekt implementieren.

    Das setzt natürlich voraus dass man überhaupt weiss dass Objekte einer bestimmten Klasse disposed werden müssen. Dazu muss man "nen Riecher" entwickeln - alles auswendig lernen und bei jeder Klasse die man irgendwo verwendet grundsätzlich immer nachgucken ob sie IDisposable implementiert wäre auch etwas übertrieben. Im Zweifel aber lieber nachgucken.

    Das machen leider sehr viele C# Programmierer nicht, und der Code den sie fabrizieren ist dann oft dementsprechend problematisch in grösseren und/oder lang laufenden Anwendungen.

    Wer das nicht macht, geht bei mir nicht als "professioneller" (im übertragenen Sinn) C# Programmierer durch.
    (Im wörtlichen Sinn gibt's leider wie schon angedeutet viele "professionelle" C# Programmierer, also welche die es beruflich machen, die komplett auf IDisposable scheissen bzw. nichtmal wissen worum es dabei überhaupt geht.)



  • ps:
    Ich bin kein Fan des sog. Dispose Patterns.

    Wenn ein Programmierer meint das so machen zu müssen, dann ist das "OK". Wenn er es anders macht, und dieses "anders" Sinn macht, dann ist das für mich auch OK - ich mache es ja selbst anders. Wichtig ist nur dass er es macht, also dass er IDisposable verwendet wo es verwendet werden sollte und implementiert wo es implementiert werden sollte.



  • Wieso nicht? schrieb:

    was unterscheidet den Hobby-Entwickler vom Profi (mal abgesehen möglicherweise von einem Arbeitsvertrag)

    Für mich waren am Anfang die größten Unterschide die Arbeit in einem Team und die Arbeit an einem Produkt. Dadurch sind die Prioritäten anders definiert als bei Hobbyprojekten. Wartbarkeit und Lesbarkeit waren plötzlich viel wichtiger. Bei meinen Hobbyprojekten ging es mir oft um sehr konkrete Detailfragen. Sozusagen um das "wie". Und da ich alleine an den Projekten gearbeitet habe, habe ich viele seltsame Konstrukte verwendet, weil ich mich selber mit meinem Code ausgekannt habe. In der Arbeit musste ich dann sehr viel stärker auf die Architektur achten. Da wars z.B. wichtig, dass ein Kollege, der nicht viel mit dem Projekt zu tun hat, schnell reinschauen und was fixen kann.



  • Was du schreibst stimmt irgendwie. Ist für mich aber nix woran man Hobbyisten und Profis unterscheiden könnte.
    Ich kennen Hobbyisten die super-sauber-wartbar programmieren und Profis die den grässlichsten, unwartbaren Code schreiben den man sich vorstellen kann.
    Daher meinte ich ja auch die Frage ist schlecht formuliert.

    Und: Es ist nicht C#-spezifisch. Und da der OP im C#-Forum gefragt hat... hab ich einfach angenommen dass es ihm nicht um solche Dinge geht.



  • Vielen dank schon mal für den Tip mit IDisposable.
    Bei den Klassikern wie DB und Streams nutze ich das schon, entweder direkt über "using" oder den destructor.
    Aber bei anderen Sachen, wie beispielsweise Graphics hab ich garnicht gewusst, dass die disposable ist.
    Werde da auf jedenfall drauf achten.

    Genau sowas meinte ich schon. Sowas wie:
    Ein guter C# entwickler achtet auf "dispose".
    etc...

    Wording achte ich schon sehr drauf.
    Allerdings scheine ich da noch potential zu haben.
    War aber schon eher C# spezifisch gedacht.
    Ein bisschen wie für C++:
    Ein guter C++-Programmierer gibt allokierten Speicher frei.
    Ein guter C++-Programmierer nutzt Const-Correctness.

    Da war die Sache mit IDisposable schon mal ein guter Hinweis



  • Ein riesen Thema ist dann mMn. auch noch das was sich aus dem fehlenden const ergibt: darauf zu achten wann Objekte kopiert bzw. mit einem "read only Adapter" (sowas wie ReadOnlyCollection ) versehen werden müssen.

    BTW:

    Bei den Klassikern wie DB und Streams nutze ich das schon, entweder direkt über "using" oder den destructor.

    Was meinst du mit Destruktor? Doch wohl hoffentlich nicht die Funktion die in C# mit ~Klassenname geschrieben wird?
    Das wäre nämlich nicht der richtige Platz für member.Dispose() Aufrufe.

    member.Dispose() Aufrufe gehören in die eigene Dispose Funktion.



  • Was meinst du mit Destruktor? Doch wohl hoffentlich nicht die Funktion die in C# mit ~Klassenname geschrieben wird?
    Das wäre nämlich nicht der richtige Platz für member.Dispose() Aufrufe.

    Tatsächlich mache ich es öfter mal so. Zum Beispiel für reportings. Der Benutzer drückt auf nen Knopf, es wird einen Reporting Objekt erzeugt, im Kontruktor Quasi die DB-Verbindung aufgebaut, dann werden 10-20 funktionen aufgerufen die dann queries ausführen, und danach wird das Objekt nicht mehr benötigt, heißt der GC sollte aufräumen und die Verbindung geschlossen werden.
    Ich vermute mal das funktioniert.

    Was spricht denn dagegen? Wieso sollte ich das nicht tun?



  • Nur wann wird jener Destruktor aufgerufen? Das ist ja genau das Problem, dass es willkürlich passiert. Deswegen lob ich mir mein RAII 🙂



  • @Wieso nicht?
    Bin mir nicht sicher was du jetzt meinst. Zeig mal wie das in Code aussehen würde.

    ps: Meinst du

    var con = new SomeDbConnection(); // <--- no "using" here
    // Use "con"
    // Use "con" some more
    // <--- nothing here
    

    ?

    Falls ja, dann würde ich das nicht mehr als "unschön" sondern wirklich als Fehler bezeichnen.
    Grund: du gibst die Connection nicht frei.
    Und so eine Connection belegt Resourcen lokal (Speicher, Network-Connection, u.U. Temp-Files) UND auf dem Server (auch hier wieder Speicher, Network-Connection, ...)!
    Das nennt man "unmanaged resources", und diese "leakst" du.

    Dass das Connection-Objekt vielleicht irgendwann mal vom GC weggeräumt wird ist dabei keine Entschuldigung und macht die Sache nicht wirklich besser.
    Denn der GC weiss nix von diesen "unmanaged resources". Nicht von den lokalen und schon gar nicht davon was der DB Server evtl. benötigt um die Connection aufrechtzuerhalten.

    Wenn jetzt die SomeDbConnection Klasse keine dreckigen Workarounds für Schmutzfinken wie dich eingebaut hat, dann kannst du damit ganz schnell deine lokale Maschine dicht machen und auch den Server arg belasten. Einfach indem du Code wie oben in einer Schleife wieder und wieder ausführst. "Idealerweise" ohne dabei viel "managed" Mist zu produzieren, so dass es schön lange dauert bis der GC mal anspringt.



  • ps: Ich finde den Begriff "Destruktor" im Zusammenhang mit C# bzw. .NET im allgemeinen problematisch. Der C# Standard verwendet den zwar IIRC (für den Finalizer), aber das macht es nicht besser.
    Destruktor ist einfach durch C++ zu "vorbelastet". Und dann kommt noch dazu dass der Destruktor in C++/CLI nochmal was anderes ist, nämlich IDisposable.Dispose .

    "Finalizer" ist klar.
    "Dispose" ist klar.
    "Destruktor" ist problematisch.



  • Bei Reports ist es oft so:

    class Report {
    	var con; 
    	public Report()
    	{
    		this.con = new Con(...);
    	}
    
    	//A few functions
    
    	~Report()
    	{
    		this.con.dispose();
    	}
    }
    

    Und der Aufruf dann:

    public void doReport() 
    {
    	Report r = new Report()
    	for(...)
    	{
    		r.aFewFunctions();
    	}
    }
    

    Bei kleineren sachen sieht es so aus:

    using (SqlConnection con = new SqlConnection())
    

    Du meinst, die Report-Klasse sollte besser IDisposable implementieren und beim aufruf dann

    using(Report r = ...)
    

    ?



  • Ja, in dem Fall macht ganz klar IDisposable.Dispose mehr Sinn.
    Mehr noch: IDisposable.Dispose ist das Werkzeug das genau dafür gemacht wurde.
    Dass die DB-Connection irgendwann weggeräumt wird ist wie gesagt lange nicht ausreichend.

    Im Vergleich dazu dein Finalizer bringt hier genau nichts.
    Denn wenn der Report finalisiert wird, dann wird - vorausgesetzt es gibt keine anderen Objekte die auf die selbe DB-Connection verweisen - die DB-Connection im selben "Finalizer-Durchlauf" auch finalisiert*. Die hat nämlich ihren eigenen Finalizer, eben um sicherzustellen dass es keine "ewigen" Leaks gibt wenn man Close/Dispose vergisst.

    Einen eigenen Finalizer schreiben macht in den wenigsten Fällen Sinn. Wenn man selbst mit Interop rummacht (speziell PInvoke, manchmal auch bei COM), ja. Zum Testen ist es auch eine gute Sache - z.B. kann man mit Finalizern schön prüfen ob ein Programm alle Objekte (von eigenen Klassen) disposed die es auch disposen muss.
    Davon abgesehen fällt mir schon nichts mehr ein.

    *: Die DB-Connection kann sogar vor deinem Report finalisiert werden, d.h. du darfst dich im Finalizer nicht darauf verlassen dass Objekte auf die deine Membervariablen verweisen noch "verwendbar" wären. Sie existieren zwar noch, aber es kann eben sein dass sie schon finalisiert wurden.



  • *: Die DB-Connection kann sogar vor deinem Report finalisiert werden, d.h. du darfst dich im Finalizer nicht darauf verlassen dass Objekte auf die deine Membervariablen verweisen noch "verwendbar" wären. Sie existieren zwar noch, aber es kann eben sein dass sie schon finalisiert wurden.

    Das prüfe ich vorher. Aber ich verstehe deine Argumentation und werde das in zukunft auch so umsetzen.



  • BTW, weils mir gerade wieder einfällt...
    Ein reales Beispiel für was passiert wenn man Dinge nicht deterministisch freigibt.
    Die Klasse System.Windows.Interop.D3DImage verwendet intern Objekte vom Typ System.Windows.Interop.InteropBitmap (bzw. davon abgeleitete).
    Jedes mal wenn man D3DImage.SetBackBuffer mit einem anderen BackBuffer aufruft erzeugt D3DImage ein neues Objekt das von InteropBitmap abgeleitet ist.
    Diese Objekte halten unmanaged Resourcen - ich glaube mich zu erinnern dass es um file mappings geht (Shared Memory halt).
    D3DImage gibt diese InteropBitmap Objekte aber nie frei. (EDIT: Also "unreachable" werden die natürlich, aber D3DImage räumt sie eben nicht deterministisch ala IDisposable auf. /EDIT) Und es gibt auch sonst keine Klasse im ganzen WPF Framework die sich darum kümmern würde die InteropBitmap Objekte so bald wie möglich freizugeben.

    Der Effekt: wenn man oft D3DImage.SetBackBuffer aufruft, weil man halt blöderweise muss (z.B. weil sich das Format oft ändert, oder weil der User dauernd Fenster wo ein D3DImage drinnen ist auf und zu macht), dann kackt das Programm irgendwann einfach ab. Fliegt ne Exception irgendwo in den Eingeweiden von WPF - gefangen bekommt man die nicht (weil WPF-interner Thread) und man könnte sie sowieso nicht sinnvoll behandeln. Bzw. davor wird es erstmal noch gröbstens langsam - so schlimm dass die ganze GUI einfriert und quasi unverwendbar wird.

    Einziger Workaround der mir eingefallen ist: Nach allen 2-3 D3DImage die man erzeugt bzw. allen 2-3 SetBackBuffer Aufrufen ein GC.Collect ausführen. 👎

    Ich will damit nur verdeutlichen: Dispose-Disziplin ist keine unnötige Fleissaufgabe - es können in der Praxis wirklich böse Dinge passieren wenn man da schludert.

    EDIT: Oops, zu früh Absenden gedrückt. Jetzt sollte es passen.


Anmelden zum Antworten