Making method in another class call an event in ca

2019-04-15 02:53发布

问题:

I need to start a download of some html so I call void GetHTML in another class. When it's done I want to pass along what Event it should raise in the calling class. How could I do this?

So it would look something like this:

public class Stuff
{
    public void GetHTML(string url, event to raise here)
    {
       //Do stuff then raise event
    }
}
public class Other
{
    public Other()
    {
        Stuff stuff = new Stuff();
        stuff.GetHTML(someUrl, somehow sending info that HTML_Done should be called);
    }
    void HTML_Done(string result, Event e)
    {
         //Do stuff with the result since it's done
    }
}

I realize I'm not super clear with what I want to do, I'd be glad to fill in any missing parts.

Thanks for any suggestions!

回答1:

Event Subscription and Notification

public class Stuff
{
    // Public Event to allow other classes to subscribe to.
    public event EventHandler GetHtmlDone = delegate { };

    public void GetHTML(string url)
    {
        //Do stuff

        // Raise Event, which triggers all method subscribed to it!
        this.GetHtmlDone(this, new EventArgs());
    }
}

public class Other
{
    public Other()
    {
        Stuff stuff = new Stuff();

        // Subscribe to the event.
        stuff.GetHtmlDone += new EventHandler(OnGetHtmlDone);

        // Execute
        stuff.GetHTML("someUrl");
    }

    void OnGetHtmlDone(object sender, EventArgs e)
    {
        //Do stuff with the result since it's done
    }
}

Using this pattern allows many more subscribers.

You also do not tie the notifier, the Stuff class, to the caller, the Other class.

You either have subscribers or not, no difference to the Stuff class.

The Stuff class should not know about the subscriber, it should merely raise an event it exposes for subscription.

EDIT
As ctacke pointed out correctly in the comments, raising the event using this.GetHtmlDone(this, new EventArgs()); will cause an exception if nobody has subscribed.
I changed my code above to ensure the event can be raised safely at all times by initialising my eventhandler.
As I'm always using it (by raising it) I'm sure it is only good practise to always initialise what you are using.

I could have added a null check on the event handler but in my personal oppinion I do not agree with that having to be the responsibility of the stuffclass. I feel the event should always be raised as it is the "responsible" thing to do.

I found this thread on SO which kind of confirmed to me that it does not seem wrong to do so.

In addition I also run Code Analysis on that code to ensure I'm not breaking the CA1805 rule by initialising the EventHandler. CA1805 was not raised and no rules have been broken.

Using my car analogy from the comments, I believe not initializing the eventhandler and raising it all the time would be the same than saying "When turning a corner in your car only use your indicator if someone is watching and if not don't bother". You never know if anyone is watching, so you might as well make sure you always do it.

This is simply my personal preference. Anyone else, please do add the != null check at all times if that is how you prefere to do it.

Thank you so much for the comments ctacke and for pointing this out. I learned quite a lot from this.
I will have to go back to some of my projects now and update some code to make sure my libraries are not crashing if nobody subscribes to my events. I feel quite silly not ever having caught that in any of my Tests.



回答2:

I'm not sure if this is what you're after, but you can use the built in Action delegate type:

public class Stuff
{
  public void GetHTML(string url, Action<string, Event> callback)
  {
     //Do stuff 

     //raise event
     callback("result here", new Event());       
  }
}

stuff.GetHTML(someUrl, HTML_Done);

Alternatively, use the standard event pattern and the EventHandler<T> delegate type. You'll need to create your own EventArgs type.



回答3:

You want a callback.

public class Stuff
{
    public void GetHTML(string url, Action callback)
    {
       // Do stuff

       // signal we're done 
       callback();
    }
}

public class Other
{
    public Other()
    {
        Stuff stuff = new Stuff();

        // using a lambda callback
        stuff.GetHTML(someUrl, ()=> Console.WriteLine("Done!") );
        // passing a function directly
        stuff.GetHTML(someUrl, MyCallback);
    }

    public void MyCallback() 
    {
        Console.WriteLine("Done!");
    }
}

If you want to pass arguments, define the Action appropriately, ie; Action<string>, and then the callback becomes callback("some string")

The other option you have, is by using Events. It looks like that was what you were aiming for when you asked the question. I wouldn't recommend it, the callback option is nicer IMO. Though, for future reference, this is how you'd use an Event in this circumstance.

public delegate void DoneEventHandler(object sender, string result);

public class Stuff
{
    public event DoneEventHandler DoneEvent = delegate {}; // avoid null check later

    public void GetHtml(string url)
    {
        // do stuff
        DoneEvent(this, "result");
    }
}

public class Other
{
    public void SomeMethod()
    {
        Stuff stuff = new Stuff();
        stuff.DoneEvent += OnDone;
        stuff.GetHtml(someUrl)
    }

    public void OnDone(objects sender, string result)
    {
        Console.WriteLine(result);
    }
}

Info on how to use Events in C#.



回答4:

If I ve not understood you wrong.I think you should try this way

public class Stuff 
{
     public void GetHTML(string url, event to raise here)
     {
        //Do stuff  and instead of raising event here
     }
 } 
public class Other
 {
     public Other()
     {
         Stuff stuff = new Stuff();
         stuff.GetHTML(someUrl, somehow sending info that HTML_Done should be called);
         //Raise event here once that method is finished
     }
     void HTML_Done(string result, Event e)
     {
          //Do stuff with the result since it's done
     }
 } 


回答5:


You can try this approach


public class Stuff
{
     public delegate void DownloadComplete("you can pass whatever info you like here");
     public event DownloadComplete OnDownloadComplete;
     public void GetHtml(string UrlToDownload)
     {
         //Download the data here
         //After data download complete
         if(OnDownloadComplete)
            OnDownloadComplete("arguments to be passed");
     }

} public class Other { public Other() { Stuff myStuff=new Stuff(); myStuff.OnDownloadComplete+=new EventHandler(myStuff_OnDownloadComplete); }

 void myStuff_OnDownloadComplete("arguments will be passed here")
 {
    //Do your stuff
 }

}