C# - статические события в нестатических классах

бывают ситуации, когда я очень люблю статические события, но тот факт, что я редко вижу их в коде других людей, заставляет меня задуматься, не упускаю ли я что-то важное. Я нашел много дискуссий о статических событиях на этом сайте, но большинство из них касаются ситуаций, которые меня не интересуют (например, на статических классах) или где я бы не подумал использовать их в первую очередь.

Что Я am заинтересованы в ситуациях, когда у меня может быть много экземпляры чего-то и единственный экземпляр долгоживущего объекта "менеджер", который реагирует на что-то в этих экземплярах. Очень простой пример, чтобы проиллюстрировать, что я имею в виду:

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");
        }
    }
}

для меня это выглядит гораздо более чистым и простым решением, чем иметь событие экземпляра, и мне также не нужно отслеживать изменения в коллекции believers. В случаях, когда класс Believer может "видеть" класс типа Бога, я мог бы иногда использовать NotifyGodOfPrayer () - метод вместо (который был предпочтительный ответ в нескольких подобных вопросах), но часто класс типа Believer находится в"моделях" -сборке, где я не могу или не хочу напрямую обращаться к классу God.

существуют ли какие-либо фактические недостатки этого подхода?

Edit: спасибо всем, кто уже нашел время ответить. Мой пример может быть плохим, поэтому я хотел бы уточнить свой вопрос:

Если я использую такие статические события в ситуациях, где

  • я уверен, что будет только один экземпляр подписчика-object
  • это гарантированно существует до тех пор, пока приложение работает
  • и количество экземпляров, которые я смотрю, огромно

тогда есть ли потенциальные проблемы с этим подходом, о которых я не знаю?

Если ответ на этот вопрос не "да", я действительно не ищу альтернативные реализации, хотя я действительно ценю все, кто пытается быть полезным. Я не ищу самое красивое решение (я должен был бы дать этот приз моей собственной версии просто за то, что она короткая и простая в чтении и обслуживании:)

2 ответов


одна важная вещь, которую нужно знать о событиях, - это то, что они вызывают объекты, которые подключены к событию, чтобы не быть собранным мусором, пока владелец события не собран мусор или пока обработчик событий не отцеплен.

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

боги останутся в вашем баране, пока они привязаны к Believer.Prayed. Это может привести к утечке боги.


я также прокомментирую дизайнерское решение, но я понимаю, что пример, который вы сделали, возможно, не лучшая копия вашего реального сценария.

мне кажется более разумным не создавать зависимость от God to Believer, и использовать события. Хорошим подходом было бы создать агрегатор событий, который стоял бы между верующими и богами. Например:

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!");

на Pray вызываемый метод, агрегатор событий вызывает соответствующий метод из God класса, в свою очередь. Чтобы управлять ссылками и избегать утечек памяти, вы можете создать UnregisterGod метод или держать богов в коллекции слабые ссылки например

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));
    }
}

Быстрый совет: временно храните делегат события, поскольку слушатели могут отцепить свои обработчики событий

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

редактировать

имея в виду детали, которые вы добавили о своем сценарии (огромное количество вызывающих событий, постоянных и одиночных event watcher) я думаю, что вы выбрали правильный сценарий, исключительно из соображений эффективности. Это создает наименьшие затраты памяти и процессора. Я бы не стал использовать этот подход в целом, но для сценария, который вы описали, статическое событие-очень прагматичное решение, которое я мог бы принять.

один недостаток, который я вижу-это поток управления. Если ваш прослушиватель событий создан в одном экземпляре, как вы говорите, Я бы использовал singleton (anti)pattern и вызвал метод God С Believer непосредственно.

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

почему? Потому что тогда вы получите более детальный контроль над выполнением действия молитвы. Если вы решите, что вам нужно подкласс Believer к особому виду, который не молится в определенные дни, тогда у вас будет контроль над этим.


думаю, что having an instance even было бы чище и вызывающе более читаемым.
Гораздо проще рассматривать его как, экземпляр охотится, так что его молиться событие получает триггер. И я не вижу в этом никаких недостатков. Я так не думаю!--2--> не больше шума, чем мониторинг статическое событие. но это правильный путь...

мониторинг списка:
Измените список на ObservableCollection (и взгляните на NotifyCollectionChangedEventArgs ).
Контролируйте его:

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
    }
}