Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
Abmelden von EventHandlern in einem komplex Vernetzten Program
Myrkjartan
myCSharp.de - Member



Dabei seit:
Beiträge: 6

Themenstarter:

Abmelden von EventHandlern in einem komplex Vernetzten Program

beantworten | zitieren | melden

Hallo Comunity,
ich arbeite an einem sehr komplexen Program, das EventHandler benutzt um zwischen Klassen zu komunizieren. Vor mir waren schon dutzende von Programmieren an diesem Projekt zu Gange. Mir ist an einem Punkt aufgefallen, dass es sehr weitreichende MemoryLeaks gibt, was ich erst einmal darauf zurück geführt habe, dass Eventhandler miteinander Verknüpft werden und diee verknüpfungen dafür sorgen, dass der GC die Elemente mit diesen EventHandlern nicht wegräumt, da es noch Verweise gibt.
Da es aufgrund der Programstruktur kompliziert bis unmöglich ist die EventHandler zu deregistrieren, habe ich folgende Idee gehabt:
Ich habe eine Etension Methode geschrieben, die alle registrierten EventHandler entfernen soll. Diese sieht so aus:

public static void ClearAllRegisteredHandler(this EventHandler handler)
        {
            foreach (EventHandler eh in handler?.GetInvocationList() ?? new Delegate[0])
            {
                handler -= eh;
            }
        }

Da sich mein Verständnis für die inneren Zusammenhänge von EventHandlern doch eher in Grenzen hält, ist meine Frage schlicht diese:
Funktioniert das?
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1446
Herkunft: Düsseldorf

beantworten | zitieren | melden

Bist Du dir sicher, was Du da tust?
Das schreit nach Fehlern, einfach alle EventHandler zu entfernen.

Soweit ich weiß, geht das nur umständlich per Reflection.

Die einfachste variante ist denke ich, dass Du den add- bzw. remove-Accessor vom Event veränderst und dann die EventHandler einer Liste hinzufügst:

public event EventHandler MyEvent
{
    get => _myEventHandlers.Add(value);
    remove => _myEventHandlers.Remove(value);
}

Das Ausführen der EventHandler musst Du dann allerdings auch manuell machen, indem Du die Liste durchläufst und alle einzeln ausführst.
Ist ziemlich überflüssig und fast immer eine blöde Idee, aber dafür kannst Du sehr simpel alle EventHandler entfernen, indem Du einfach die Liste leerst.
Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von Palladin007 am .
private Nachricht | Beiträge des Benutzers
Myrkjartan
myCSharp.de - Member



Dabei seit:
Beiträge: 6

Themenstarter:

beantworten | zitieren | melden

Ich bin mir nicht sicher was ich da mache....
Es ist aber so gedacht, dass immer alle EventHandler entfernt werden. Ich benutze diese Funktion nur dann, wenn ich das Fenster schließe, an welcher Stelle alle entfernt werden müssen.
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1446
Herkunft: Düsseldorf

beantworten | zitieren | melden

Naja, kommt drauf an, um welche Events es da geht.

Wenn Du das Fenster schließt und das Objekt dann auch verloren gehen darf, werden auch alle EventHandler zu den Events dieses Fensters verworfen.
Außer es wurden irgendwo welche gespeichert, aber davon gehe ich Mal nicht aus.

Wenn es um die Events von anderen Klassen geht und "im" Fenster wurden EventHandler registriert, dann wird das schon spannender, denn Du kannst die EventHandler nicht unterscheiden. Wenn Du alle Handler entfernst sind wirklich alle weg, nicht nur die, die vom Fenster registriert wurden.

Ich vermute Mal, irgendwelche Fenster-Methoden dienen als EventHandler von anderen Business-Klassen? In dem Fall wäre es das klügste, dass das Fenster alle diese EventHandler beim Schließen wieder entfernt. Das ist der einzige Weg, wie Du erreichen kannst, dass nur die EventHandler verschwinden, die auch weg können.
private Nachricht | Beiträge des Benutzers
Myrkjartan
myCSharp.de - Member



Dabei seit:
Beiträge: 6

Themenstarter:

beantworten | zitieren | melden

Also, wenn ich deine Vermutung richtig verstanden habe, dann ist es genau so.
Ich versuchwe mal, das verständlich zu beschreiben:
Klasse A (wpf) hat einen EventHandler X.
Klasse B,C,D und E benutzen Klasse A und registrieren eigene EventHandler auf X. Diese sind teils tatschlich deklarierte EventHandler, teils auch nur Lambda Ausdrücke.
Wenn das Fenster geschlossen wird, müssen alle auf X registrierten EventHandler gelöscht werden, da sonst Verweiße auf diese vorhanden sind, weshalb der GC A nicht wegräumt und dieses Element im Speicher bleibt bis das Program komplett geschlossen wird.
Deshalb war mein Plan dann beim Schließen A.ClearAllRegisteredHandler() auszuführen, was meines Verständnisses nach, genau das machst was du vorgeschlagen hast.
Zitat
In dem Fall wäre es das klügste, dass das Fenster alle diese EventHandler beim Schließen wieder entfernt

Habe ich das richtig Verstanden?
Dieser Beitrag wurde 3 mal editiert, zum letzten Mal von Myrkjartan am .
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1446
Herkunft: Düsseldorf

beantworten | zitieren | melden

Wenn ich dich jetzt wiederum richtig verstanden habe, ist das der Fall, wo es tatsächlich funktionieren könnte.

Ergo:

private readonly ICollection<EventHandler> _myEventHandlers;

public event EventHandler MyEvent
{
    get => _myEventHandlers.Add(value);
    remove => _myEventHandlers.Remove(value);
}

private void ClearMyEvent()
{
    _myEventHandlers.Clear();
}
private void InvokeMyEvent(EventArgs e)
{
    foreach (var handler in _myEventHandlers)
        handler(this, e);
}
private Nachricht | Beiträge des Benutzers
Myrkjartan
myCSharp.de - Member



Dabei seit:
Beiträge: 6

Themenstarter:

beantworten | zitieren | melden

OK, dann bedanke ich mich erst einmal für die Hilfe und werde mich zunächst durch die 10.000 Klassen arbeiten die das Programm enthält und die MemoryLeaks auf diese Art stopfen. Mal sehen ob's hilft.
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1446
Herkunft: Düsseldorf

beantworten | zitieren | melden

Ach warte - das sind vermutlich vordefinierte Events?

private void Clear()
{
    var myEventInfo = typeof(Program).GetEvent(nameof(MyEvent));

    foreach (var handlerDelegate in MyEvent.GetInvocationList())
    {
        myEventInfo.RemoveEventHandler(this, handlerDelegate);
    }
}

Grausig, aber bitte

PS:
Man könnte es in eine eigene Klasse ausgliedern, indem man als Parameter eine LambdaExpression entgegen nimmt:

ClearEvent(() => MyEvent);

public static void ClearEvent(Expression<Func<MulticastDelegate>> getEventExpression)
{
    var (eventInfo, owner) = AnalyzeEventInfoExpression(getEventExpression);
    var eventMulticastDelegate = getEventExpression.Compile()();

    foreach (var handlerDelegate in eventMulticastDelegate.GetInvocationList())
    {
        eventInfo.RemoveEventHandler(owner, handlerDelegate);
    }
}
private static (EventInfo eventInfo, object owner) AnalyzeEventInfoExpression(Expression<Func<MulticastDelegate>> getEventExpression)
{
    // ...
}

Das geht - man kann anhand der LambdaExpression die Instanz mit dem Event und die EventInfo dazu herausfinden.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Palladin007 am .
private Nachricht | Beiträge des Benutzers
FZelle
myCSharp.de - Experte



Dabei seit:
Beiträge: 10070

beantworten | zitieren | melden

Bei der größe des Progams, würde ich als aller erstes einen Memoryprofiler nehmen und schauen, was tatsächlich so los ist.

Bei einem Program das ich mal in die Finger bekommen habe, war es das falsche DB handling.
Die SW hat ihre Commands nie disposed und das hat dann irgendwann zu Problemen geführt.
private Nachricht | Beiträge des Benutzers
Myrkjartan
myCSharp.de - Member



Dabei seit:
Beiträge: 6

Themenstarter:

beantworten | zitieren | melden

Ich bin bereits mit einem MemoryProfiler drüber gegangen, was mich am Ende auf die EventHandler gebracht hat.
Was meinst du mit 'vordefinierte Events' ?
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1446
Herkunft: Düsseldorf

beantworten | zitieren | melden

Damit meine ich die Events, die Du nicht bearbeiten kannst, also z.B. Click-Events.

Und ich hab oben noch ein Konzept ergänzt, wie man die Reflection-Varianta ausgliedern kann.
Ist nicht einfach, aber soweit ich mich erinnere, geht das.
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1446
Herkunft: Düsseldorf

beantworten | zitieren | melden

Ich hatte Langeweile:

private static (EventInfo eventInfo, object owner) AnalyzeEventInfoExpression(Expression<Func<MulticastDelegate>> getEventExpression)
{
    if (getEventExpression.Body is MemberExpression bodyExp)
    {
        object owner;

        if (bodyExp.Expression is ConstantExpression thisExp)
        {
            owner = thisExp.Value;
        }
        else if (bodyExp.Expression is MemberExpression varExp && varExp.Expression is ConstantExpression varOwnerExp)
        {
            owner = (varExp.Member as FieldInfo).GetValue(varOwnerExp.Value);
        }
        else
            throw new Exception("Wrong lambda expression");

        var eventFieldInfo = bodyExp.Member;
        var eventInfo = eventFieldInfo.DeclaringType.GetEvent(eventFieldInfo.Name);

        return (eventInfo, owner);
    }

    throw new Exception("Wrong lambda expression");
}

Aus der Instanz aufrufen: ClearEvent(() => MyEvent);
Mit einer Variable aufrufen: ClearEvent(() => myVar.MyEvent);

Aber mach im Code deutlich, dass das absolut der falsche Weg ist.
Ich seh's sonst noch kommen, dass die Methode überall genutzt wird, weil es eben einfacher ist.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Palladin007 am .
private Nachricht | Beiträge des Benutzers
MarsStein
myCSharp.de - Experte

Avatar #avatar-3191.gif


Dabei seit:
Beiträge: 3429
Herkunft: Trier -> München

beantworten | zitieren | melden

Hallo,

eine wichtige Anmerkung zu der Extension-Methode im Startbeitrag, da hier offenbar ein massives Verständnisproblem bezüglich Delegates/Events vorliegt: Diese Methode bringt gar nichts!
Sobald ein EventHandler einer Variable zugewiesen oder als Parameter übergeben wird, arbeitest Du nur noch auf einer Kopie des EventHandlers.
Aus dieser Kopie werden zwar mit der Extensionmethode alle Handler entfernt, das interssiert aber den ursprünglich an die Methode übergebenen EventHandler nicht die Bohne - da bleibt alles weiter registriert.

Gruß, MarsStein
Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca
private Nachricht | Beiträge des Benutzers
witte
myCSharp.de - Member



Dabei seit:
Beiträge: 966

beantworten | zitieren | melden

Du könntest dich auch mal in das Thema weak events einlesen.
private Nachricht | Beiträge des Benutzers
Myrkjartan
myCSharp.de - Member



Dabei seit:
Beiträge: 6

Themenstarter:

beantworten | zitieren | melden

@MarsStein
Das habe ich tatsächlich nicht gewusst. Wenn ich das ganze so abändere, dass die (dann leere) Kopie des EventHandlers zurückgegeben wird, würde das funktionieren?
private Nachricht | Beiträge des Benutzers