Laden...

Forenbeiträge von Palladin007 Ingesamt 2.079 Beiträge

24.06.2022 - 18:57 Uhr

Jetzt haben wir ein Problem 😉
Im von Abt verlinkten Artikel Service Locator is not an Anti-Pattern ist dein verlinkter Artikel Service Locator is an Anti-Pattern verlinkt 😁

Wer hat jetzt Recht?
Ich persönlich finde, dass im "Nicht-Anti-Pattern"-Artikel kaum Argumente genannt werden, außer am Ende die Performance.
Alles andere klingt nach Faulheit oder "keine Lust, umzudenken".
Sicher gibt es durchaus Situationen, in denen das ServiceLocator-Pattern seine Daseinsberechtigung hat, allerdings denke ich, dass das wirklich große Ausnahmen sind - wie z.B. die Fälle, in denen die Konstruktor-Performance ausschlaggebend ist.

Allerdings würde das Thema den Rahmen hier ziemlich sicher sprengen 😁

24.06.2022 - 10:31 Uhr

Halb Off-Topic:

Die Art und Weise, wie der Service Locator Pattern angewendet ist, ist ein Anti Pattern

Der Satz wirkt, als gäbe es auch einen "richtigen" Weg, das Service Locator Pattern zu verwenden?
Wie sieht denn dieser "richtige" Weg aus?

Für mich ist das generell ein Anti Pattern 😠

23.06.2022 - 13:16 Uhr

Ob es ein besser geeignetes System zur direkten Kommunikation zwischen Remote und Client gibt, weiß ich nicht.
Ich würde einfach eine klassische Web-API daraus machen, das ist vermutlich am einfachsten.

Ob nun der Remote-Computer eine Verbindung mit dem Client aufbaut oder umgekehrt, musst Du entscheiden.
Letzteres wäre vermutlich einfacher, da der Client dann keine eingehenden Nachrichten abhören und demnach auch keine Berechtigungen dafür haben muss.

23.06.2022 - 10:47 Uhr

Ich glaube nicht, dass das geht.
Du könntest aber ein zweites Programm auf dem Client installieren, dessen einzige Aufgabe es ist, die ProcessStartInfo-Daten vom "Server"-Programm entgegen zu nehmen und den Prozess zu starten.

20.06.2022 - 11:00 Uhr

Klingt als hättest Du Abts Antwort nicht verstanden 😉

Prinzipiell: gar nicht.

Es gibt kein Beispiel für "gar nicht".
Wenn deine Anwendung aktuell direkt mit der DB telefoniert (deswegen fragst Du ja), dann hast ein Problem.
Um das zu ändern kannst Du nicht mal eben so ein Code-Snippet rein pasten und es läuft sicher(er).

Dein Programm darf nicht mit der DB arbeiten (=SQL, EFCore, etc. mit ConnectionString), Du musst also umdenken und - je nachdem, wie dein Programm aufgebaut ist - auch großflächig umbauen.
Die Möglichkeiten, die Du hast, hat Abt dir genannt, dazu musst Du dich jetzt einlesen und überlegen, was Du brauchst und welche der Möglichkeiten für dich und dein Ziel am besten geeignet ist.

Oder Du lässt es so.
Für privat mag das ausreichen, im professionellen Umfeld sollte man das aber nicht machen - hängt davon ab, was für Daten Du hast.
Wobei es auch Firmen gibt, die das so wollen, obwohl es unsicher ist (weil weniger Arbeit), aber dann ist es nicht mehr dein Bier.

14.06.2022 - 14:31 Uhr

Die Application hat eine ShutdownMode-Property, darüber kannst Du definieren, wann er automatisch die Anwendung beenden soll.
Eventuell hilft das?

12.06.2022 - 13:03 Uhr

Ich würde aber nicht auf LINQ2DB setzen, einfach nur weil man denkt, dass es um Performance geht.

Ich traue Microsoft zu, dass sie das sehr gut können und ich traue Abt zu, dass seine Aussage (fast impactlose Performance) auf Erfahrungen beruht, die eine solche Aussage rechtfertigen.
Vielleicht ist LINQ2DB ein wenig schneller, aber rechtfertigt dieser Vorteil, dass man auf EFCore mit allen seinen Vorteilen und Change Tracking (was man auch abschalten kann) verzichten muss?
Ganz ehrlich: Dieses Framework bildet den Kern der Anwendung, der sollte sitzen und da setze ich lieber auf EFCore, das weit weit umfangreicher getestet ist, weil es ungefähr überall genutzt wird.

Wenn die Performance wirklich derart wichtig ist, dann würde ich lieber direkt auf Dapper setzen, aber auch da: Lohnt sich der Aufwand wirklich?

11.06.2022 - 11:57 Uhr

Tja, willkommen in der Wirklichkeit 😁
Je länger man Updates vor sich her schiebt, desto aufwändiger/schwieriger wird es, bis es kaum noch tragbar ist.
Aber ganz ehrlich: Wenn Du "mal eben so" die komplette Daten-Schicht samt Entity Framework neu selber basteln kannst, dann kannst Du auch die Versionen hoch ziehen, das ist weniger riskant.

Aber einfach auf eine neuere VS Version upgraden geht ja leider nicht so einfach.

Doch - warum nicht?

Dat hat auch bei Abfragen zu einer deutichen Verbesserung der Performance geführt

Das alte Entity Framework war nie sonderlich performant.
Das neue Entity Framework Core ist eine Neuentwicklung, immer noch nicht auf dem Level von plain SQL, aber deutlich schneller, als das alte.

Hab ich sonst noch eine Möglichkeit, unter meinen gegeben Umständen ein Objekt <T>
und/oder eine List<T> zurückgeben zu lassen?

Ja:

Schau dir die IntelliSense-Hilfe an, da zeigt dir Visual Studio an, welche Überladungen es gibt - dann nimm eine davon.

Aber Du solltest lieber aktualisieren, als auf dem selben altbackenem Kram herum zu kriechen.
Wenn Du dir das selber bastelst, bastelst Du dir nur das nächste Problem.

11.06.2022 - 01:19 Uhr

Die Methode mit zwei Parametern gibt's seit .NET 4.5 - was verwendest Du?
Schau dir die IntelliSense-Hilfe an, da zeigt dir Visual Studio an, welche Überladungen es gibt - dann nimm eine davon.
Oder Du verwendest eine aktuelle Version, das wäre am klügsten und Du hast das Problem nicht mehr.

Und was Du da tust, gibt's schon, man nennt es OR-Mapper.
Und von Microsoft gibt's das Entity Framework.
Oder Du verwendest Dapper, wenn es etwas leichtgewichtiges sein soll.

09.06.2022 - 20:10 Uhr

string strCurrent = Marshal.PtrToStringAnsi(new IntPtr(iStartAddress));

if (fileEncoding == Encoding.UTF8)
{
    Encoding enc = new UTF8Encoding(true);

    byte[] utf8bytes = enc.GetBytes(strCurrent);
    byte[] win1252Bytes = Encoding.Convert(enc, Encoding.ASCII, utf8bytes);

    strCurrent = Encoding.ASCII.GetString(win1252Bytes);
}

Was genau ist der Sinn hinter diesem Code?
Du hast UTF16, holst per UTF8-BOM die Bytes, konvertierst sie in ASCII und holst von den ASCII-Bytes den dann ASCII-String?
Das klingt ziemlich unsinnig 😉

Und was der Code drum herum soll, ist mir auch nicht ganz klar

By the way:
Zum Lesen von INI gibt's Frameworks.

03.06.2022 - 19:38 Uhr

Was ich damit v.a. ausdrücken will: die zitierte Aussage ist pauschal falsch -- es kommt vielmehr auf den konkreten Fall darauf an.

Mir ging es mit der Aussage mehr um eine allgemeine Faustregel, die man sich für alle nicht kritischen Fälle angewöhnen kann.
Vielleicht ist Enumerable.Empty<T> nicht in jeder Situation das performanteste, aber wenn man IEnumerable<T> hat, ist es performanter, als Array.Empty<T>.

Einfach pauschal null nehmen, würde ich aber nicht, da der Kontext ggf. gar kein null erlaubt und man somit den Nutzer des Codes dazu zwingen würde, immer auf null zu prüfen.
Da kann ein Enumerable.Empty<T> zwar langsamer sein, aber man muss die null-Checks nicht schreiben und nicht überall ein Verhalten im null-Fall definieren.
Wenn nun der Code, der ein IEnumerable<T> zurück liefert, nie null zurück liefert, ist das Verhalten im Fall, dass es keine Ergebnismenge gibt, automatisch klar definiert.
Aber das bezieht sich natürlich nicht auf die Fälle, in denen man auf diesem Level optimieren muss 😉

Ist aber dennoch gut zu wissen - besonders das Detail, wie performant der null-Check ist

03.06.2022 - 16:04 Uhr

Der Time-Punch beim Array kommt (wahrscheinlich) durch das Cast von Array auf IEnumerable zustande.

Immer wieder erstaunlich, wo sich Details verstecken können, das hätte ich nicht erwartet 😁
Also in Zukunft lieber Enumerable.Empty nutzen - tut nicht weh und ist schneller.

03.06.2022 - 11:10 Uhr

Ich nutze immer: Array.Empty<string>()
Dabei wird nur einmal ein leeres Array erzeugt, danach wird es statisch vorgehalten
Müsste ja also ungefähr das gleiche sein, wie Enumerable.Empty<string>(), da wird ja auch ein leeres Objekt erzeugt und dann statisch vorgehalten.
Ein Benchmark habe ich aber noch nicht gemacht.

30.05.2022 - 14:40 Uhr

Tatsache, da wird vorher geprüft - gut, ich hab nix gesagt 🙂

30.05.2022 - 14:10 Uhr

ThrowIfCancellationRequested reicht auch direkt, wirft ja nur, wenn IsCancellationRequested = true ist, man spart sich also ein if.

Heißt:


void MyMethod(CancellationToken cancellationToken)
{
    while (/* while condition */)
    {
        cancellationToken.ThrowIfCancellationRequested();

        // Do work ...
    }
}

23.05.2022 - 16:35 Uhr

Nur, um das Thema hier nicht so halb beendet stehen zu lassen:

Das GC.Collect scheint nur manchmal zu helfen (war ja irgendwie zu erwarten), weiter einzusteigen ist bei dem Zeitdruck aber keine Option.
Der Kunde sagt, es ist "in Ordnung", müsste aber nach dem Release als Erstes angegangen werden.
So ist jetzt auch der Plan, daher lasse ich das erst einmal liegen.

Dein Angebot:

Ich würde das sehr gerne machen, nicht zuletzt, um wieder was dazu zu lernen 😁
Aber wie Du schon vermutet hast, ist das ein sensibles Thema und ich darf dir nichts schicken.
Eventuell ändert sich das nach dem Release nochmal, aber das ist mehr Wunschdenken von mir, als realistisch.
Ich danke dir trotzdem für das Angebot und melde mich, falls ich irgendwann doch darf 🙂

19.05.2022 - 12:59 Uhr

Was ich nicht konnte, obwohl es lt. allen hier leicht funktionieren sollte, war es mehrere unabhängige Sprachen/Formatierungen in einer Ansicht zu haben.
Vl. hat ja jemand die Geduld, mir zu zeigen, wie mein kleines Programm mit den Board mitteln, leicht zu lösen geht.

Du meinst, Du suchst eine Übersicht aller Texte für alle Sprachen?

Das habe ich schon genannt: RESX-Manager
ResXManager - Visual Studio Marketplace

18.05.2022 - 23:30 Uhr

Ich schließe mich dem Feedback dazu an: Wo ist der Sinn?

Ich will die .NET-Lokalisierung nicht aufs Blut verteidigen, aber das Konzept hinter den RESX-Dateien, wie die Sprachen gesucht werden und die Tools dazu (RESX-Manager) sind schon sehr gut und ich wüsste nicht, was man da noch besser machen kann. Gerade der RESX-Manager macht das erst richtig wertvoll, da ich mit wenigen Klicks einfach die gesamte Lokalisierung als XLSX exportieren, zum Kunden/Übersetzer schicken und danach wieder importieren kann - quasi keine zusätzliche Arbeit für mich.

Also wenn Du etwas in die Richtung entwickeln möchtest und auch möchtest, dass andere davon profitieren (können), dann sollte ein wichtiges Ziel sein, genau diese bestehenden Tools möglichst gut zu unterstützen. Oder Du baust direkt darauf auf, z.B. indem Du einen eigenen Code-Generator schreibst, der das Problem, um das es dir hier geht, auf Basis der RESX-Dateien lösen kann.

Ich sehe hier also nur eines:
Ein Konzept, das extrem vom aktuellen bewähren Konzept abweicht und eigentlich nur genutzt wird, um eine klapprige Brücke zum bestehenden Konzept zu bauen.
Also auch, wenn deine Library ein reales Problem löst (ich sehe es auch noch nicht wirklich), dann hast Du immer noch das Problem, dass Du eine von Anfang an erzwungene Basis mit schleppst, die eigentlich niemand will.

Sorry, aber wenn ich ein konkretes Problem damit habe, dann baue ich die Lösung lieber direkt auf der erprobten Basis auf 🙂

17.05.2022 - 10:41 Uhr

By the way:


if (level != null)
    myLogger.SetLoggingLevel = level.Value;

Das solltest Du nicht machen 😉
Generell nicht - das LogLevel gehört an die Methode übergeben.
Oder zumindest dieses Beispiel sollte das LogLevel immer manuell angeben, um Seiteneffekte zu vermeiden.

Oder was denkst Du passiert, wenn jemand in der Methode ein anderen LogLevel definiert?
Damit veränderst Du plötzlich auch alle Log-Aufrufe danach.

17.05.2022 - 10:20 Uhr

Das Problem, was Du hast, ist ein typisches Problem von dem Ansatz mit Contracts-Projekten: Du versuchst es überall zu verfolgen.
Ich würde es nicht überall erzwingen, sondern nur bei den Komponenten, die auch tatsächlich unabhängig vom Rest existieren können und für die es ggf. Erweiterungen oder andere Implementierungen geben kann oder soll. Beim Rest würde ich es einfacher halten und Abstraktion und Implementierung zusammen in einem Projekt lassen - letzteres als internal.
Logging wäre ein gutes Beispiel, wo sich Contracts anbietet, allerdings enthält das vermutlich kaum Logik (Du nutzt ja hoffentlich ein vorhandenes Framework), was diese Aufteilung wieder ziemlich überflüssig macht - deshalb würde ich das auch lassen.

In deinem Fall sehe ich zwei Wege, um deinen Code anzubieten:* Eigenes Logging-Interface, das eine entsprechende LogMethodExecution-Methode anbietet - da Du das Interface schon hast, bietet sich das ja an.

  • Eine Erweiterungsmethode, die die Methode an ein bestehendes Logging-Interface ergänzt.
    Das würde ich aber nur machen, wenn der Code darin einfach ist und vollständig auf Basis der Abstraktion (dem Logging-Interface) funktioniert - was bei dir auch der Fall ist.
    Und vor allem solltest Du es nicht übertreiben 😉

  • Dein Weg - allerdings sehe ich da das Problem, dass es unauffällig ist, man kann es leichter vergessen. Eine Methode am Interface, das man sowieso nutzt, fällt mehr auf, als eine Klasse.

Und die Klasse würde ich einfach zur Abstraktion legen, sie ist ja auch Teil der Abstraktion, da sie eigentlich nur eine bestehende Abstraktion ohne eigene Logik aufruft.
Du hast also Logging-Interface oder Erweiterungsmethode in einem Projekt, die Methode Methode gibt deine Klasse zurück und die liegt ebenfalls im selben Projekt, wie die Abstraktion.

By the way: ggf. ist ein Struct besser, da das ja überall in jeder Methode genutzt wird, recht klein ist (enthält nur drei Referenzen, die sowieso auf den Stack geladen wurden) und nie länger lebt, als die Methode ausgeführt wird. So sparst Du dir ein Objekt pro Methode, das der GC aufräumen muss.
Und ich würde die Klasse/das Struct möglichst dumm halten, also kein Log im Konstruktor. Die LogMethodExecution-Methode ruft das EnterMethode auf und übergibt an die Klasse/das Struct nur alles nötige, um LeaveMethod aufrufen zu können.

16.05.2022 - 19:17 Uhr

Process (Docs)

Damit kanst Du die Anzahl Prozesse abrufen, nach TeamViewer suchen und wenn Du einen passenden Prozess findest, kannst Du Dinge tun.

16.05.2022 - 10:36 Uhr

The ItemsControl - The complete WPF tutorial

Wobei das auch kein richtiges MVVM ist, weil das ViewModel fehlt.
Wie das richtig geht, steht in meinem letzten Link.

16.05.2022 - 09:34 Uhr

Joa - Du bastelst dir alles selber, ohne DataBinding wirst Du keine Bindung hin bekommen.

Oder anders gesagt: Deinen Code kannst Du komplett entsorgen - so mies das auch klingen mag.
Bis auf das LINQ am Anfang (wobei das da auch nicht hin gehört, aber das ist ein anderes Thema) ist der komplette Rest ein MVVM-Verstoß und kann/sollte vollständig mit DataBinding, einem ItemsControl (oder ListBox) und ItemTemplate im XAML gelöst werden.

[Artikel] MVVM und DataBinding

16.05.2022 - 09:30 Uhr

Die Software hat eine Test-Version - probiere sie doch einfach aus?

13.05.2022 - 19:25 Uhr

Oder die Klasse bekommt ein Interface mit einer Methode, die Bool zurück gibt, dann klappt's auch ohne Reflection.

13.05.2022 - 18:01 Uhr

... oder Du schränkst den Typ entsprechend ein:


T Create<T>()
    where T: new()
{
    return new T();
}

Damit erschlägst Du dann auch gleich abstrakte Klassen, die Du so ja nicht nutzen kannst.

Übrigens klingt dein Vorhaben nach etwas, was man lieber anders lösen sollte 😉

Z.B. würde ich sowas NIEMALS vom Namens abhängig machen, entweder der Name ist fix vorgegeben und dann kann ich auch ein Interface schreiben, oder ich versuche es möglichst zu vermeiden.
Und wenn es unbedingt doch machen muss, würde ich eher ein Attribut erstellen, nach dem ich dann suchen kann, dann ist der Name auch egal und im Attribut können noch ein paar Daten ergänzt werden.

Und MrSparkle war schneller 🙂

13.05.2022 - 15:08 Uhr
GC.Collect  

bitte nur als Notlösung betrachten, da dies die interne Arbeit und Tuning vom GC durcheinander bringt.

Leider sehe ich aktuell keine andere Option (außer das manuell alles auf null setzen), der Zeitplan ist schon lange überschritten und der Kunde hat auch nur noch diesen Monat 😠
Da sind mir Notlösungen lieber, als 2,5 GB im RAM versauern zu lassen und es soll im Anschluss sowieso weiter gehen, dann aber mit ruhigerem Zeitplan.

PerfView ist mächtig, aber nicht trivial und intuitiv.
Wenn du willst und es gestattet ist, so kann ich mir die Dumps einmal anschauen und dann eine grobe Art Anleitung dazu erstellen.
Aber ich muss bei PerfView auch immer wieder "reinkommen", da ich es recht schnell wieder vergesse 😉

Danke für das Angebot, aber das kann ich nicht alleine entscheiden 🙂
Ich spreche das bei nächster Gelegenheit mal an.

Kann aber gut sein, dass sich das um ein paar Wochen (oder sogar Monate) verschiebt, wenn die Notlösung soweit ausreicht, auch da kann ich nur meine Meinung zu sagen, aber nichts entscheiden.

13.05.2022 - 12:14 Uhr

private static async void TriggerDelayedFullGC()
{
    await Task.Delay(100, default).ConfigureAwait(false);

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
}

Das tut's auch.
So baue ich es erst einmal ein und schau später nochmal.

13.05.2022 - 11:07 Uhr

GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();
GC.Collect(GC.MaxGeneration);

Warum GC.MaxGeneration? Einfach weil viel hilft viel 😁
Ich will erst einmal sehen, ob es überhaupt etwas bringt, danach schaue ich mir an, ob GC.MaxGeneration zu viel ist oder nicht.

Aber tatsächlich hilft es, aber anders als erwartet.
Ich beobachte jetzt zwei Dinge:* Die RAM-Auslastung geht ca. eine Sekunde (ich hab's mit dem Delay eingebaut) nach dem Verlassen des Dialogs wieder runter

  • Die Kurve ist nach den ersten ein/zwei Mal Dialog anzeigen und verlassen sehr viel gleichmäßiger, wie als würden Objekte gecacht werden, was ich aber nicht mache

Gerader der zweite Punkt verwundert mich - hast Du dazu eine Erklärung?
Das GC gibt bei jedem Mal Verlassen des Dialogs sehr viel weniger frei, als ich erwarten würde, aber beim erneuten anzeigen, wird auch sehr viel weniger benötigt.
Könnte das mit SQLite zusammenhängen, dass der - ähnlich dem SQLServer - anfängt, die Daten im RAM vorzuhalten, um den Zugriff zu optimieren?

Aber so wie das aussieht, übernehme ich das für alle Dialoge, dass nach jedem Dialog verlassen erst einmal aufgeräumt wird.
Ich teste nur noch, ob auch ohne GC.MaxGeneration ausreicht und das Delay kann vermutlich auch raus.
Ob das eine gute Lösung ist, weiß ich nicht, aber sie hilft und da ich etwas unter Druck stehe, ist es zumindest eine vorübergehend geeignete Lösung - denke ich.

PS:
Mit PerfView hab ich ein paar Dumbs erstellt, nur werde ich daraus nicht schlau.
Bisher kannte ich das Tool nicht und es scheint auch einiges an Einarbeitung zu benötigen, doch die Zeit habe ich leider nicht.
Ich würde daher eine schnelle Lösung bevorzugen, auch wenn es nur ein Flickenteppich ist, aber dann kann ich es später nochmal angehen.

13.05.2022 - 10:23 Uhr

GC.MaxGeneration = 2
GCSettings.LatencyMode = Interactive

GC.Collect(GC.MaxGeneration) hat auch nichts gebracht.
Manchmal gibt er es auch von alleine frei, ich weiß allerdings nicht, wann und ob das die Daten vom aktuellen Dialog sind, den ich gerade verlasse, oder die von irgendwann vorher.
Wenn ich einen Button verwendet, um GC.Collect(GC.MaxGeneration) aufzurufen, gibt es meistens ungefähr die Menge frei, die ich erwarten würde.

Aber warum?

Ich hab jetzt folgendes eingebaut:


_ = Task.Delay(1000, default)
    .ContinueWith(_ => GC.Collect(GC.MaxGeneration), default(CancellationToken));

Das funktioniert so halb, es gibt ab dem zweiten Dialog-Verlassen wieder frei.

Ich schau mir jetzt PerfView an, vielleicht kann mir das ja etwas mehr verraten, vermutlich habe ich doch irgendwo ein gemeines Problem und ich jage hier unnötig dem GC hinterher.

12.05.2022 - 20:39 Uhr

Die meisten dieser Profiler führen eine kompletten GC durch (also inkl. Gen-2 und LOH) und haken sich in den Informationsstrom vom GC rein um so an die Ojekte zu kommen.
Genau dieses Verhalten hast du beobachtet.

Versuch dich einmal an PerfView, das bietet i.d.R. mehr Infos / Möglichkeiten. Aber die UX ist sehr bescheiden...

Das erklärt's ein bisschen, ich hab testweise nur ein einfaches GC.Collect gemacht, das geht vermutlich nicht weit genug?

Und danke für den Tipp - schau ich mir morgen an.

Wenn durch den .NET Memory Profiler Speicher freigegeben wird, dann teste mal, ob auch ein GC.Collect (z.B. durch einen expliziten Button-Click) diesen freigibt.

Das hatte ich mir auch schon gedacht und GC.Collect eingefügt - allerdings nicht durch einen Button-Klick, sondern direkt nachdem alles weg geworfen wurde.
Gebracht hat es nichts, aber ich teste Morgen mal mit einem eigenen Button, vielleicht macht das einen Unterschied.

erst wenn der Speicher an die Grenzen stößt, wird mehr freigegeben

Ich hätte nicht gedacht, dass er (in meinem Fall) knapp 3GB einfach so liegen lässt.
Aber gut, ich hab auch (noch) keine Ahnung, wie der genau arbeitet - das Buch liegt hier, aber die Zeit fehlt noch ^^

12.05.2022 - 15:08 Uhr

Kannst du nicht bei den "Detail"-Objekten die Referenz für das Hauptobjekt auf null setzen, sobald du das Hauptobjekt nicht mehr benötigst?

Ergebnis: Es liegt sehr viel weniger im RAM, weil ich jetzt alles auf null gesetzt habe, was ich nur finden konnte, durch alle Klassen hindurch.
Die Instanzen, die nicht da sein sollten, gibt's allerdings immer noch und ein Snapshot vom .NET Memory Profiler "beseitigt" die Instanzen immer noch.

Meine zweite Theorie war scheinbar erfolgreicher, aber auch nur manchmal und ein Snapshot vom .NET Memory Profiler "behebt" das Problem immer noch.
Aber gut, das heißt wohl, dass das Problem doch irgendwo in meinem Code liegt 😁

12.05.2022 - 13:39 Uhr

Da bin ich gerade dran, ist aber nicht "mal eben so" gemacht, weil der Kreis etwas größer ist.

Ich hatte gehofft, man kann dem GC einen ganzen Objekt-Baum mitgeben, zumindest erinnere ich mich, mal etwas davon gelesen zu haben.
Oder das war ein Issue, wo jemand nach genau so einem Feature gefragt hat.
Oder ich werfe irgendetwas anderes ganz durcheinander 😁

Ich teste das Mal und eine andere Theorie, dann melde ich mich wieder.

12.05.2022 - 13:20 Uhr

Guten Mittag,

es geht um eine WPF-Anwendung.
Ich suche gerade ein MemoryLeak, der scheinbar nur auftritt, wenn es um sehr viele Daten geht, also auch die Auswirkungen sehr groß sind.

Dabei ist mir aufgefallen, dass ich mit Hilfe der Diagnostic Tools von Visual Studio den Leak problemlos reproduzieren kann (mehrere Instanzen einer Klasse, wo nur eine sein sollte), doch sobald ich mit dem .NET Memory Profiler einen Snapshot erstelle, sind die überflüssigen Instanzen weg und der Speicher freigegeben.
Eventuell hängt das mit großen zirkulären Referenzen zusammen? Heißt: Ein "Haupt"-Objekt referenziert sehr viele "Detail"-Objekte, die auf das "Haupt"-Objekt verweisen.

Ich kann nicht ganz sagen, ob es wirklich keine Referenzen mehr gibt, daher die Frage:
Kann es sein, dass in einem Fall mit sehr vielen Objekte im "Referenz-Kreis" der GC nicht hinterher kommt und einfach zu lange braucht?
Und wenn ja, wie gehe ich damit am besten um, kann ich den Fall prüfen und ggf. ausschließen?

Und ist es möglich, dem GC die Objekte, die ich definitiv nicht mehr brauche, mitzugeben und sie so "löschen" zu lassen?

04.05.2022 - 12:54 Uhr

var window = new MyWindow();
window.Show();

[...] Haupt Form

[...] über ein Click Event [...]

Was Du schreibst, schreit danach, dass Du versuchst, WPF so zu benutzen, wie man es bei WinForms gemacht hat.
Lass das bleiben, Du wirst nur Probleme damit haben.

Bei WPF arbeitet man mit MVVM: [Artikel] MVVM und DataBinding
Ich weiß aber auch, dass das beim Öffnen von einem Window nicht ganz so einfach ist - ist also nur als Hinweis gemeint.

01.05.2022 - 16:26 Uhr

PS: string statt String ist die sicherere Schreibweise (gilt auch für die anderen Varianten wie Int32 etc..).
Im Gegensatz zu Früher ist die klare Empfehlung nur noch die aliase (also die type keywords) zu verwenden.

Warum sicherer?
Ist doch beides äquivalent zueinander?

29.04.2022 - 19:04 Uhr

System.Text.Encoding.Default passt meist viel besser.

... was seit .NET Core auch UTF8 ist:
https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding.default?view=net-6.0#the-default-property-on-net-core

26.04.2022 - 13:47 Uhr

Ich fasse Mal zusammen:

Du hast eine Klasse mit einem Konstruktor mit zwei Name-Parametern
In diesem Konstruktor erzeugst Du zwei weitere Instanzen von dieser Klasse ohne Parameter
Diesen Instanzen (je Cast auf ITest1 und ITest2) setzt Du die Namen aus dem jeweiligen Parameter
Der aktuellen Instanz setzt Du die zwei neuen Instanz-Variablen nameAlpha und nameBeta (das sind keine Properties) anhand der zuvor gesetzten Name-Properties
Die zuvor erstellten Instanzen verwirfst Du ungenutzt
Die beiden Name-Properties der aktuellen Instanz bleiben leer

Klingt das sinnvoll? 😉
Für mich sieht das so aus, als versuchst Du Trial&Error irgendetwas zu erreichen, aber so funktioniert das nicht.
Du solltest dir entweder ein gutes Buch oder die Anleitungen von Microsoft dazu durchlesen, dann hast Du mehr Freude daran.

Ich vermute Mal, Du meinst folgendes:


class TestClass : ITest1, ITest2
{
    // DAS sind Properties - und halte dich an Namenskonventionen
    public string NameAlpha { get; set; }
    public string NameBeta { get; set; }

    string ITest1.Name
    {
        get { return NameAlpha; }
        set { NameAlpha = value; }
    }
    string ITest2.Name
    {
        get { return NameBeta; }
        set { NameBeta = value; }
    }

    public TestClass(string nameAlpha, string nameBeta)
    {
        NameAlpha = nameAlpha;
        NameBeta = nameBeta;
    }
}

26.04.2022 - 13:15 Uhr

Stimmt 😁
Ich schreibe meistens so:


var test1 = (ITest1)test;
var test2 = (ITest2)test;

Da braucht man das natürlich, aber das habe ich dann - der Klarheit wegen - nachträglich nochmal ergänzt.

Danke für die Korrektur

PS: Ich hab's oben aktualisiert.

26.04.2022 - 13:00 Uhr

TestClass test = new TestClass();
Console.WriteLine(((ITest1)test).Name);
Console.WriteLine(((ITest2)test).Name);

// ITest1 test1 = (ITest1)test;
// ITest2 test2 = (ITest2)test;
// Hier brauchst Du den Cast nicht, das passiert automatisch (danke Abt, für die Korrektur)
ITest1 test1 = test;
ITest2 test2 = test;
Console.WriteLine(test1.Name);
Console.WriteLine(test2.Name);

Andere Wege gibt es nicht, zumindest keine, die nicht weit über jeden vernünftigen Nutzen hinaus gehen würden.
Und das Features heißt übrigens Explicit Interface Implementation

25.04.2022 - 19:30 Uhr

Hm - wusste ich nicht.

Deshalb sollte man es aber dennoch nicht so machen.
Das sollte wenn dann nur eine Not-Lösung für alle Projekte sein, für die es nicht anders geht.
Und ich bin mir ziemlich sicher, hier gibt es noch andere Optionen 😉

25.04.2022 - 14:27 Uhr

Console arbeitet auf Net 6.0 und die dll mit Framework 4.8.

Das ist dein Fehler.
Das mag funktionieren, ist aber mehr Zufall, weil dahinter der gleiche Byte-Code (IL) steht, aber beides sind verschiedene Frameworks und Du hast keine Garantie, dass es auch in Zukunft noch funktioniert.

Und nein, nur weil das eine Version 4.8 und das andere Version 6 ist, ist das nicht abwärtskompatibel, wie es bisher sonst immer war.
.NET 4.8 ist die letzte Version der "alten" Welt, alles danach ist von Grund auf erneuert und nicht abwärtskompatibel.

Wenn die DLL auch in einem anderen Projekt, das .NET 4.x sein muss (z.B. nicht Abhängigkeiten, die nicht aktualisiert werden können), dann setze die DLL mit .NET Standard 2.0 um, das wird von .NET 4.x und .NET 6 unterstützt.

Abt war so freundlich, das zusammenzufassen (hätte ich von Microsoft erwartet, aber naja):
[FAQ] Das .NET Ökosystem - .NET, .NET Core, .NET Standard, NuGet und Co

Und die Versionen-Tabelle von Microsoft:
.NET Standard-Versionen

25.04.2022 - 14:05 Uhr

Eigentlich sollte Visual Studio deine DLL automatisch kompilieren, wenn dein Konsolenprojekt sie braucht und sich darin auch etwas geändert hat.
Und das funktioniert auch ganz sicher, immerhin hast Du nur eine DLL, große Projekte haben 100(e) DLLs in einer Projektmappe 😉 Wenn das nicht funktionieren würde, wäre beim Visual Studio Team von Microsoft die Hölle los...

Du hast also irgendein anderes Problem, z.B. falsche Projekt-Referenzen.
Ich habe z.B. schon häufiger Referenzen gesehen, die die DLL im Output-Verzeichnis direkt referenzieren.
Das funktioniert, aber für Visual Studio ist das dann nur eine x-beliebige DLL ohne jede Projekt-Zugehörigkeit und kompiliert sie natürlich auch nicht automatisch.

Aber abgesehen davon:
Du kannst auch einen Rechtsklick auf jedes Projekt oder die Projektmappe machen und dort jedes Projekt oder die Projektmappe direkt kompilieren.

25.04.2022 - 12:37 Uhr

Naja, meine eigentliche Grundidee (und auch der Anwendungsfall, aus dem sie entstanden ist) sieht so aus, dass es mehrere Klassen gibt, die auf eine Ressource zugreifen.
Das Lock existiert also klassenübergreifend, z.B. statisch oder in einem Singleton.
Die einzelnen arbeitenden Klassen haben dann ihr Ticket jeweils pro Instanz und können somit erzwingen, dass nur diese eine Instanz etwas tun darf, bis sie selber fertig sind.

Nun kann es aber sein, dass die arbeitende Instanz aus irgendeinem Grund (das wäre definitiv ein Bug) nicht mehr existiert, das Lock vorher aber nicht korrekt freigegeben wurde.
In diesem Fall würde das Lock dann auf ewig gesperrt bleiben, weil niemand mehr eine Referenz auf das Ticket hat - also ein Deadlock.
Durch die WeakReference würde dieser Deadlock irgendwann (indirekt durch den GC) wieder freigegeben werden.

25.04.2022 - 12:18 Uhr

Diesen Fall würde ich als "undefiniert" für die Synchronisation sehen und daher eine NullReferenceException werfen. "Fail early" ist ein gutes Paradigma, denn so kann unterschieden werden ob tatsächlich null übergeben wurde od. ob der lock einfach nicht betreten werden kann.

Stimmt - ja, ich versuche möglichst ohne Exceptions zu arbeiten, wo es nicht notwendig ist (z.B. bei Try-Methoden), aber hier ist es ja inhaltlich und dank nullable reference types offensichtlich, dass kein null übergeben werden darf.
Außerdem behandle ich das Disposed genauso: Es fliegt eine Exception für einen Fall, der "eigentlich" nicht vorkommen darf.

25.04.2022 - 03:10 Uhr

Das konkrete Problem hatte ich nicht mehr gelöst - zumindest soweit ich mich erinnere.
Ich weiß jedenfalls noch, dass ich nach einem ClickOnce-Problem (nicht das erste) mich dazu entschlossen habe, ClickOnce als Problem-Zeit-Fresser ganz abzuschaffen und habe dann auf Basis von AutoUpdater.NET eine Alternative eingeführt.

Ob das für dich auch der richtige Weg ist, kann ich dir nicht sagen, dazu bin ich zu lange aus dem Thema raus.
Du solltest dir aber in jedem Fall gut überlegen, bevor Du ganz umstellst, denn das bedeutet zwar weniger Probleme, allerdings machst Du gleichzeitig einiges an anderen Aufwänden und möglichen Problemen auf, die Du vermutlich gar nicht auf dem Schirm hast.

Bei uns war es eine Folge von mehreren Problemen, die jedes Mal viel Zeit gekostet haben und dass wir generell (auch aus anderen Gründen) nie ganz zufrieden mit ClickOnce waren.

24.04.2022 - 18:36 Uhr

Die ist laut den Docs abstrakt. Sie hat aber trotzdem einen Konstruktor. Das verstehe ich schon nicht, denn eine abstrakte Klasse soll ja eigentlich gar keine Instanzen haben.

Sie kann doch trotzdem Initialisierungen benötigen?
Die Ableitung muss diesen Konstruktor dann eben aufrufen.
Außerdem: Jede Klasse hat einen Konstruktor, Du siehst ihn nur nicht immer, da er - wenn nicht vorhanden - automatisch ergänzt wird.

Davon abgesehen gibt es dann aber noch eine Create() Methode, die eine Instanz erstellt und zurückgibt.

Das ist das Factory-Pattern.
Ich vermute, das hat den Hintergrund, dass sie die Implementierungen dahinter ändern können, ohne dass dadurch BreakingChanges entstehen.
Einen konkreten Grund, warum sie das Factory-Pattern verwendet haben, weiß ich aber nicht.

Die Create Methode liefert eine Instanz von SHA256Managed.

Das stimmt nur so fast.
Tatsächlich liefert sie eine nested class namens "Implementation", deren Implementierung aber identisch zu SHA256Managed ist.

Was der Grund für dieses ungewöhnliche Vorgehen ist, würde mich aber auch interessieren.

24.04.2022 - 16:06 Uhr

PS:


public ValueTask<Releaser> EnterAsync(object ticket, TimeSpan timeout, CancellationToken cancellationToken = default)
{
    return timeout == TimeSpan.Zero
        ? EnterAsync(ticket, cancellationToken)
        : WithTimeout(ticket, timeout, cancellationToken);

    async ValueTask<Releaser> WithTimeout(object ticket, TimeSpan timeout, CancellationToken cancellationToken)
    {
        using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

        timeoutCts.CancelAfter(timeout);

        try
        {
            return await EnterAsync(ticket, timeoutCts.Token);
        }
        catch (OperationCanceledException)
            when (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
        {
            throw new TimeoutException();
        }
    }
}

Hätte den Vorteil, dass eine besser Verständliche TimeoutException geworfen wird, anstelle einer OperationCanceledException.
Was meinst Du dazu?
Nachteil ist, dass dadurch wieder eine StateMachine nötig wird.

24.04.2022 - 15:26 Uhr

public struct AsyncTicketLockOptions
{
    public bool WeakTicketReferencing { get; set; }
}

public sealed class AsyncTicketLock
{
    private readonly Channel<StateBase> _channel = Channel.CreateBounded<StateBase>(1);
    private readonly StateBase _state;

    public AsyncTicketLock()
        : this(default)
    {
    }
    public AsyncTicketLock(AsyncTicketLockOptions options)
    {
        _state = options.WeakTicketReferencing
            ? new WeakRefState()
            : new StrongRefState();
    }

    public ValueTask<Releaser> EnterAsync(object ticket, TimeSpan timeout, CancellationToken cancellationToken = default)
    {
        return timeout == TimeSpan.Zero
            ? EnterAsync(ticket, cancellationToken)
            : WithTimeout(ticket, timeout, cancellationToken);

        ValueTask<Releaser> WithTimeout(object ticket, TimeSpan timeout, CancellationToken cancellationToken)
        {
            using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

            timeoutCts.CancelAfter(timeout);

            return EnterAsync(ticket, timeoutCts.Token);
        }
    }

    public async ValueTask<Releaser> EnterAsync(object ticket, CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(ticket, nameof(ticket));

        while (true)
        {
            if (TryEnter(ticket, out Releaser releaser))
                return releaser;

            await _channel.Writer
                .WaitToWriteAsync(cancellationToken)
                .ConfigureAwait(false);
        }
    }

    public bool TryEnter(object ticket, out Releaser releaser)
    {
        var success = TryEnter(ticket);

        releaser = success
            ? new Releaser(this, ticket)
            : default;

        return success;
    }

    public bool TryEnter(object ticket)
    {
        if (ticket is null)
            return false;

        lock (_state)
        {
            if (_channel.Reader.TryPeek(out StateBase? state) && state.TryGetTicket(out object? stateTicket))
            {
                if (ReferenceEquals(ticket, stateTicket))
                {
                    state.IncrementEnteredCount();

                    return true;
                }

                return false;
            }
            else
            {
                var hasWritten = _channel.Writer.TryWrite(_state);

                Debug.Assert(hasWritten);

                _state.Reset(ticket);

                return true;
            }
        }
    }

    public bool Release(object ticket)
        => Release(ticket, all: false);

    public bool ReleaseAll(object ticket)
        => Release(ticket, all: true);

    private bool Release(object ticket, bool all)
    {
        if (ticket is null)
            return false;

        if (!_channel.Reader.TryPeek(out _))
            return false;

        lock (_state)
        {
            if (!_channel.Reader.TryPeek(out StateBase? state))
                return false;

            if (!state.TryGetTicket(out object? stateTicket))
                return false;

            if (!ReferenceEquals(ticket, stateTicket))
                return false;

            var count = state.DecrementEnteredCount();

            Debug.Assert(count >= 0);

            if (all || count <= 0)
            {
                var hasFreedChannel = _channel.Reader.TryRead(out StateBase? readState);

                Debug.Assert(hasFreedChannel);

                if (readState is not null)
                {
                    readState.TryGetTicket(out object? readStateTicket);

                    Debug.Assert(ReferenceEquals(readStateTicket, stateTicket));
                }
            }

            return true;
        }
    }

    public readonly struct Releaser : IDisposable
    {
        private readonly AsyncTicketLock _parent;
        private readonly object _ticket;

        internal Releaser(AsyncTicketLock parent, object ticket)
        {
            _parent = parent;
            _ticket = ticket;
        }

        public void Dispose()
            => _parent?.Release(_ticket);
    }

    private abstract class StateBase
    {
        private int _enteredCount;

        public int EnteredCount => _enteredCount;

        public void Reset(object ticket)
        {
            _enteredCount = 1;

            SetTicket(ticket);
        }

        public abstract bool TryGetTicket([NotNullWhen(true)] out object? ticket);
        protected abstract void SetTicket(object ticket);

        public int IncrementEnteredCount() => ++_enteredCount;
        public int DecrementEnteredCount() => --_enteredCount;
    }

    [DebuggerDisplay($"EnteredCount: {{{nameof(EnteredCount)},nq}}, UserState: {{{nameof(_ticket)},nq}}")]
    private sealed class StrongRefState : StateBase
    {
        private object? _ticket;

        public override bool TryGetTicket([NotNullWhen(true)] out object? ticket)
        {
            ticket = _ticket;
            return ticket is not null;
        }

        protected override void SetTicket(object ticket)
        {
            _ticket = ticket;
        }
    }

    [DebuggerDisplay($"EnteredCount: {{{nameof(EnteredCount)},nq}}")]
    private sealed class WeakRefState : StateBase
    {
        private WeakReference<object>? _ticketRef;

        public override bool TryGetTicket([NotNullWhen(true)] out object? ticket)
        {
            if (_ticketRef is null)
            {
                ticket = null;
                return false;
            }

            return _ticketRef.TryGetTarget(out ticket);
        }

        protected override void SetTicket(object ticket)
        {
            _ticketRef = new WeakReference<object>(ticket);
        }
    }
}