简体   繁体   中英

C# - Static events on non-static classes

There are situations where I'm quite fond of static events, but the fact that I rarely see them in other people's code makes me wonder if I'm missing something important. I found a lot of discussions about static events on this site, but most of them deal with situations that I'm not interested in (like on static classes) or where I wouldn't think of using them in the first place.

What I am interested in are situations where I might have many instances of something and a single instance of a long-living "manager" object that reacts to something on those instances. A very simple example to illustrate what I mean:

public class God {

    //the list of followers is really big and changes all the time, 
    //it seems like a waste of time to
    //register/unregister events for each and every one...  
    readonly List<Believer> Believers = new List<Believer>();

    God() {
        //...so instead let's have a static event and listen to that
        Believer.Prayed += this.Believer_Prayed;
    }

    void Believer_Prayed(Believer believer, string prayer) {
        //whatever
    }
}

public class Believer {

    public static event Action<Believer, string> Prayed;

    void Pray() {
        if (Prayed != null) {
            Prayed(this, "can i have stuff, please");
        }
    }
}

To me, this looks like a much cleaner and simpler solution than having an instance event and I don't have to monitor changes in the believers collection either. In cases where the Believer class can "see" the God-type class, I might sometimes use a NotifyGodOfPrayer()-method instead (which was the preferred answer in a few similar questions), but often the Believer-type class is in a "Models"-assembly where I can't or don't want to access the God class directly.

Are there any actual downsides to this approach?

Edit: Thanks to everyone who has already taken the time to answer. My example may be bad, so I would like to clarify my question:

If I use this kind of static events in situations, where

  • I'm sure there will only ever be one instance of the subscriber-object
  • that is guaranteed to exist as long as the application is running
  • and the number of instances I'm watching is huge

then are there potential problems with this approach that I'm not aware of?

Unless the answer to that question is "yes", I'm not really looking for alternative implementations, though I really appreciate everyone trying to be helpful. I'm not looking for the most pretty solution (I'd have to give that prize to my own version simply for being short and easy to read and maintain :)

One important thing to know about events is that they cause objects which are hooked to an event not to be garbage collected until event owner is garbage collected, or until event handler is unhooked.

To put it into your example, if you had a polytheistic pantheon with many gods, where you promoted and demoted gods such as

new God("Svarog");
new God("Svantevit");
new God("Perun");

gods would remain in your RAM while they are attached to Believer.Prayed . This would cause your application to leak gods.


I'll comment on design decision also, but I understand that example you made is maybe not best copy of your real scenario.

It seems more reasonable to me not to create dependency from God to Believer , and to use events. Good approach would be to create an event aggregator which would stand between believers and gods. For example:

public interface IPrayerAggregator
{
    void Pray(Believer believer, string prayer);
    void RegisterGod(God god);
}

// god does
prayerAggregator.RegisterGod(this);
// believer does
prayerAggregator.Pray(this, "For the victory!");

Upon Pray method being called, event aggregator calls appropriate method of God class in turn. To manage references and avoid memory leaks, you could create UnregisterGod method or hold gods in collection of weak references such as

public class Priest : IPrayerAggregator
{
    private List<WeakReference> _gods;

    public void Pray(Believer believer, string prayer)
    {
        foreach (WeakReference godRef in _gods) {
            God god = godRef.Target as God;
            if (god != null)
                god.SomeonePrayed(believer, prayer);
            else
                _gods.Remove(godRef);
        }
    }

    public void RegisterGod(God god)
    {
        _gods.Add(new WeakReference(god, false));
    }
}

Quick tip: Temporarily store event delegate as listeners might unhook their event handlers

void Pray() {
    var handler = Prayed;
    if (handler != null) {
        handler(this, "can i have stuff, please");
    }
}

Edit

Having in mind details you added about your scenario (huge number of event invokers, constant and single event watcher) I think you chose right scenario, purely for efficiency reasons. It creates least memory and cpu overhead. I wouldn't take this approach generally, but for scenario you described static event is very pragmatic solution that I might take.

One downside I see is flow of control. If your event listener is created in single instance as you say, then I would leverage singleton (anti)pattern and invoke method of God from Believer directly.

God.Instance.Pray(this, "For the victory!");
//or
godInstance.Pray(this, "For the victory!");

Why? Because then you get more granular control over performing action of praying. If you decide down the line that you need to subclass Believer to a special kind that doesn't pray on certain days, then you would have control over this.

I actually think that having an instance even would be cleaner and defiantly more readable.
It is much more simple to view it as, an instance is preying, so his pray event gets trigger. And I don't see ant downsides for that. I don't think that monitor changes is not more of a hustle than monitoring the static event. but is the correct way to go...

Monitoring the list:
Change the list to be an ObservableCollection (and take a look at NotifyCollectionChangedEventArgs ).
Monitor it by:

public class God {

    readonly ObservableCollection<Believer> Believers = new ObservableCollection<Believer>();

    public God() {
        Believers  = new ObservableCollection<T>();
        Believers.CollectionChanged += BelieversListChanged;
    }

    private void BelieversListChanged(object sender, NotifyCollectionChangedEventArgs args) {

        if ((e.Action == NotifyCollectionChangedAction.Remove || e.Action ==     NotifyCollectionChangedAction.Replace) && e.OldItems != null)
        {
            foreach (var oldItem in e.OldItems)
            {
                var bel= (Believer)e.oldItem;               
                bel.Prayed -= Believer_Prayed; 
            }
        }

        if((e.Action==NotifyCollectionChangedAction.Add ||               e.Action==NotifyCollectionChangedAction.Replace) && e.NewItems!=null)
            {
                foreach(var newItem in e.NewItems)
                {
                   foreach (var oldItem in e.OldItems)
                {
                    var bel= (Believer)e.newItem;               
                    bel.Prayed += Believer_Prayed; 
                }
            }
        }
    }

    void Believer_Prayed(Believer believer, string prayer) {
        //whatever
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM