How to pass a delegate as an argument to subscribe

2019-07-07 09:45发布

问题:

I have an external application that provides an event StkQuit.

I subscribe to this event in a static class that handles all communication between my application and [the] external application. I would like to subscribe to the StkQuit event using another handler that sits on my form class.

This handler will inform the user that the external application has been closed. I would like to have a generic method in the static class called SubscribeToStkQuit that accepts a delegate as a parameter and subscribes that delegate (referring to the handler on my form class) to the StkQuit event.

Is this possible? Is this the most elegant/simplistic way to achieve such functionality?

My example code:

Form Class

public delegate void dForceClose();
public void SubscribeToStkQuit(dForceClose forceClose)
{
    UtilStk.SubscribeToStkQuit(forceClose = new dForceClose(ForceClose));
}
private void ForceClose()
{
    MessageBox.Show("TEST");
}

Static Class

private static AgUiApplication _stkUiApplication;
public static void SubscribeToStkQuit(Delegate subscribeHandler)
{
    _stkUiApplication.OnQuit += subscribeHandler;          
}

[Update]

As per comments I have updated the code like so:

public delegate void dForceClose(object sender, EventArgs e);
public void SubscribeToStkQuit(dForceClose forceClose)
{
    UtilStk.SubscribeToStkQuit(forceClose = new dForceClose(ForceClose));
}
private void ForceClose(object sender, Eventargs e)
{
    MessageBox.Show("TEST");
}

I am still getting the cast exception. Any ideas ??

[Update #2]

I'm still having problems with this. In my static class I have a handler already for when OnQuit fires:

private static AgUiApplication _stkUiApplication;
public static bool CheckThatStkIsAvailable()
{
    object stkApplication = null;
    try
    {
        stkApplication = Marshal.GetActiveObject(_stkProgId);
        _stkUiApplication = stkApplication as AgUiApplication;
        _stkUiApplication.OnQuit += StkQuit;
        return true;
    }
    catch (Exception)
    {
        // handle this
    }
}

private static void StkQuit()
{
    _stkUiApplication.OnQuit -= StkQuit;
    Marshal.FinalReleaseComObject(_stkUiApplication);
    Marshal.FinalReleaseComObject(_stkRoot);
}

This works fine. So I thought I would create a public property for _stkUiApplication and subscribe from the form class in the same manner:

Static Class

public static AgUiApplication StkUiApplication
{
    get { return _stkUiApplication; }
}

Form Class

private void SubscribeToStkQuit()
{
    UtilStk.StkUiApplication.OnQuit += StkQuit;
}

private void StkQuit()
{
    MessageBox("TEST");
}

回答1:

If you subscribe to a static event with an instance method, then the instance will not be garbage collected until the static event is disposed (unless you unsubscribe).

This will cause a memory leak.

Beyond that, the problem with your code is that the signature of ForceClose() does not match _stkUiApplication.OnQuit it needs to be ForceClose(object sender, SomeKindOfEventArgs e)

It should be UtilStk.SubscribeToStkQuit(forceClose => (s, e){ForceClose();});

EDIT:

The reference to your external app is static:

private static AgUiApplication _stkUiApplication;

so it will live for the duration of your application. Adding an event handler to

 _stkUiApplication.OnQuit

passes a reference to a method on the instance of your form class. This reference will now be held for the life of your application, so it cannot be garbage collected.

To handle for this situation, either explicitly de-register (-=) the handler when the listening object is disposed, or handle static events with static handlers.

I mistakenly thought that you were describing web forms at first, which changes things a bit (you may only ever instantiate one Form), but the above holds true regardless. And it is good practice.

To solve your current problem: You need to type the parameter passed to:

public static void SubscribeToStkQuit(Delegate subscribeHandler)
{
    _stkUiApplication.OnQuit += subscribeHandler;          
}

to be the type of _stkUiApplication.OnQuit

Something like

public static void SubscribeToStkQuit(EventHandler<EventArgs> subscribeHandler)
{
    _stkUiApplication.OnQuit += subscribeHandler;          
}

Then you can do this:

public void SubscribeToStkQuit()
{
    UtilStk.SubscribeToStkQuit((sender, args) => ForceClose(sender, args));
}


回答2:

Yes, you can!

But it's better to use actions and functions, which are flexible in terms of parametrization (all of them are in System namespace).

Anyway, I've another suggestion: why don't you use event accessors? You can create one in your static class:

public event EventHandler Quit { add { _stkUiApplication.OnQuit += value; } remove { _stkUiApplication.OnQuit -= value; } }

About actions and functions, these are delegates too, so you can use them everywhere as input parameters for any delegation.

Perhaps you can use the event accessor and do it in a .NET way, can't you?