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