Есть ли способ создать индексированные события в С# (или некоторое обходное решение)?
Подпись озадачивает. Позвольте мне немного пояснить:
Я хотел бы предоставить события, которые зависят от параметра, поэтому наблюдатель может принять решение о получении событий, если что-то произойдет с определенным "id". Это может выглядеть так:
public event EventHandler Foo (string id);
Я знаю, что этот синтаксис неверен в .NET 3.5, и я также знаю, что эта идея вводит дополнительную проблему (например, как мы можем отменить подписку?).
Как мне обойти эту проблему? Я подумал об использовании чего-то вроде:
public EventHandler Foo (string id);
который, по крайней мере, является законным синтаксисом и может работать, но он по-прежнему не очень велик для меня.
Изменить: Я не, прося о передаче аргументов функции обратного вызова. Моя идея больше похожа на это:
class Bleh
{
public event EventHandler Foo (string index);
private void RaiseEvents() // this is called by a timer or whatever
{
Foo["asdf"] (this, EventArgs.Empty); // raises event for all subscribers of Foo with a parameter of "asdf"
Foo["97"] (this, EventArgs.Empty); // same for all "97"-subscribers
// above syntax is pure fiction, obviously
}
}
// subscribe for asdf events via:
Bleh x = new Bleh ();
x.Foo["asdf"] += (s, e) => {};
Объяснение
Поскольку вы, вероятно, задаетесь вопросом, почему я пытаюсь это сделать, я объясню свою ситуацию. У меня есть класс, который предоставляет позиции определенных объектов (каждый из них идентифицируется некоторой строкой ID).
Вместо предоставления event EventHandler<PositionChangedEventArgs>
, созданного для ЛЮБЫХ позиционных изменений, я хотел бы иметь событие для каждого объекта (доступ к нему по индексу), поэтому наблюдатели могут прослушивать события только для определенного идентификатора.
Ответы
Ответ 1
Вы можете сделать что-то вроде этого:
public class Foo
{
public class Bar
{
public event EventHandler PositionChanged;
internal void RaisePositionChanged()
{
var handler = PositionChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
private Dictionary<string, Bar> m_objects;
public Bar this[string id]
{
get
{
if (!m_objects.ContainsKey(id))
m_objects.Add(id, new Bar());
return m_objects[id];
}
}
private void RaisePositionChanged(string id)
{
Bar bar;
if (m_objects.TryGetValue(id, out bar))
bar.RaisePositionChanged();
}
}
Затем, чтобы подписаться на событие, это будет так просто:
Foo foo = new Foo();
foo["anId"].PositionChanged += YourHandler;
Ответ 2
Вам нужно использовать класс, созданный EventArgs, который включает идентификатор, а затем использовать EventHandler<IdEventArgs>
или что-то еще:
public class IdEventArgs : EventArgs
{
private readonly string id;
public string Id { get { return id; } }
public IdEventArgs(string id)
{
this.id = id;
}
}
public event Eventhandler<IdEventArgs> Foo;
Когда вы поднимаете событие, вам нужно создать экземпляр IdEventArgs
, а затем абонент может проверить это и решить, что с ним делать.
Ответ 3
Я только начал использовать Rx Framework, и это блестяще. Я думаю, это может быть то, что вы ищете.
http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx
Подписка и не-подписка обрабатываются в рамках. Он назывался LINQ для событий. Это "математическое двойное" IEnumerable.
Cheers,
-jc
Ответ 4
Я думаю, что Reactive Extensions for.NET - именно то, что вы ищете.
Идея * такова:
Сначала определите класс, который происходит от EventArgs
и включает нужную вам информацию (в частности, любой "указатель", который вы имели в виду). Что-то вроде этого:
public class IndexedEventArgs : EventArgs {
public string Index { get; private set; }
public IndexedEventArgs(string index) {
Index = index;
}
// ...
}
Далее, для класса, который будет поднимать события, реализуйте одно событие с использованием EventHandler<TEventArgs>
и этого класса, который вы только что определили. В рамках этого определения класса создайте объект, реализующий IObservable
следующим образом:
public class ClassWithIndexedEvents {
public event EventHandler<IndexedEventArgs> IndexedEvent;
public IObservable Events { get; private set; }
public ClassWithIndexedEvents() {
// yeah, this feels a little weird, but it works
Events = Observable.FromEvent<IndexedEventArgs>(this, "IndexedEvent");
}
// ...
}
Теперь в вашем коде, где вы хотите подписаться только на события, соответствующие определенному индексу, вы можете отфильтровать свойство Events
так же, как и IEnumerable
:
// code mangled to fit without scrolling
public IDisposable CreateSubscription(
string eventIndex,
Action<IEvent<IndexedEventArgs>> handler) {
return Events.Where(e => e.Index == eventIndex).Subscribe(handler);
}
Обратите внимание, что метод Subscribe
возвращает объект IDisposable
; это ваш ключ к последующей отмене подписки на отфильтрованное событие, на которое вы только что подписались. Код довольно очевиден:
var fooSubscription = objectWithIndexedEvents.CreateSubscription(
"foo",
e => DoSomething(e)
);
// elsewhere in your code
fooSubscription.Dispose();
* Отказ от ответственности: я пишу весь этот код более или менее из памяти о том, как работает Rx; Я не тестировал его, так как у меня нет Rx, установленного на машине, которую я сейчас использую. Я могу проверить завтра на другой машине, чтобы убедиться, что все написано правильно; на данный момент это должно по крайней мере служить иллюстрацией, чтобы дать вам представление о том, как работает Rx. Чтобы узнать больше, вы всегда можете найти учебники Rx онлайн.
Ответ 5
Я не знаю, буду ли я использовать события в этом случае, но я не уверен, что это самая большая проблема.
Если вы пытаетесь контролировать подписчиков, я думаю, вам лучше позволить подписчикам обработать фильтрацию. Только они знают, на что они действительно хотят отфильтровать, поэтому размещение кода фильтрации в классе, который испускает события, кажется субоптимальным.
Позвольте мне попытаться немного уточнить, если я могу... Код, чтобы определить, будет ли получатель A
заботиться о событии от эмиттера B
, живет где-то. Может показаться, что имеет смысл поставить его в B
. Однако проблема возникает, когда вы понимаете, что вам нужно учитывать получателей C
, D
и E
. У них может быть сложная логика, чтобы определить, что им небезразлично (и это может даже измениться через itme). Полагая всю эту логику в нашем эмиттере (B
), мы сделаем большой, неуклюжий класс, который трудно использовать.
Другой вариант - иметь A
логику того, хочет ли он событие внутри. Это локализует логику до A
, позволяет B
чистить и легко потреблять все остальные. Однако недостатком этого является то, что логика подписки не может использоваться C
, если она будет одинаковой.
Но если мы действительно думаем об этом, мы имеем три вещи, происходящих здесь... событие, испускаемое, фильтрация событий получателям и получение/реакция на события. Принцип единой ответственности говорит нам, что класс должен иметь только ответственность - одна из причин изменения. Включая логику фильтрации как в A
, так и в B
, в зависимости от того, что получилось, теперь есть две обязанности и две причины для изменения.
Итак, я бы попытался сделать в этом случае создание другого класса Q
, который содержит логику фильтрации событий. Теперь ни A
, ни B
не получают дополнительную логику в своем коде. C
не требует повторной реализации. И в качестве бонуса мы теперь можем легко связать несколько фильтров, чтобы получить сложное поведение фильтра, основанное на очень простых компонентах.
Ответ 6
Я подготовил полный пример. Вы можете использовать его следующим образом:
eventsSubscriptions["1"].EventHandler = new EventHandler(this.Method1);
eventsSubscriptions["2"].EventHandler = new EventHandler(this.Method2);
eventsSubscriptions["3"].EventHandler = new EventHandler(this.Method3);
Boss Boss1 = new Boss("John Smith");
Boss Boss2 = new Boss("Cynthia Jameson");
Employed Employed1 = new Employed("David Ryle");
Employed Employed2 = new Employed("Samantha Sun");
Employed Employed3 = new Employed("Dick Banshee");
// Subscribe objects to Method 1
eventsSubscriptions["1"].Subscribe(Boss1);
eventsSubscriptions["1"].Subscribe(Employed1);
// Subscribe objects to Method 2
eventsSubscriptions["2"].Subscribe(Boss2);
eventsSubscriptions["2"].Subscribe(Employed2);
// Subscribe objects to Method 3
eventsSubscriptions["3"].Subscribe(Employed3);
Затем вы можете вызвать методы RaiseAllEvents(), и это консольный вывод:
- Метод 1, поднятый Боссом Джоном Смитом
- Метод 1, поднятый сотрудником David Ryle
- Метод 2, поднятый с Боссом Синтия Джеймсон
- Метод 2, поднятый сотрудником Samantha Sun
- Метод 3, поднятый сотрудником Диком Банши
В следующих строках, я буду вставлять код всех участвующих классов. С небольшим терпением и копией/вставкой вы сможете проверить его = P Надеюсь, он вам поможет.
--- Код ---
Главная
namespace MyExample
{
public class Program
{
static void Main(string[] args)
{
SomeExampleClass someExampleInstance = new SomeExampleClass();
someExampleInstance.SuscribeObjects();
someExampleInstance.RaiseAllEvents();
Console.ReadLine();
}
}
}
Класs >
namespace MyExample
{
public abstract class Person
{
protected string name;
public Person(string name)
{
this.name = name;
}
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public override string ToString()
{
return (this.GetType().Name + " " + name);
}
}
}
Класс Босс
namespace MyExample
{
public class Boss : Person
{
public Boss(string name)
: base(name)
{ }
}
}
Сотрудник
namespace MyExample
{
public class Employee : Person
{
public Employee(string name)
: base(name)
{ }
}
}
Класс SomeExampleClass
namespace MyExample
{
public class SomeExampleClass
{
private EventsSubscriptions eventsSubscriptions = new EventsSubscriptions();
private void Method1(object sender, System.EventArgs e)
{
Console.WriteLine("Method 1 raised with " + sender.ToString());
}
private void Method2(object sender, System.EventArgs e)
{
Console.WriteLine("Method 2 raised with " + sender.ToString());
}
private void Method3(object sender, System.EventArgs e)
{
Console.WriteLine("Method 3 raised with " + sender.ToString());
}
public void SuscribeObjects()
{
eventsSubscriptions["1"].EventHandler = new EventHandler(this.Method1);
eventsSubscriptions["2"].EventHandler = new EventHandler(this.Method2);
eventsSubscriptions["3"].EventHandler = new EventHandler(this.Method3);
Boss Boss1 = new Boss("John Smith");
Boss Boss2 = new Boss("Cynthia Jameson");
Employee Employee1 = new Employee("David Ryle");
Employee Employee2 = new Employee("Samantha Sun");
Employee Employee3 = new Employee("Dick Banshee");
// Method 1
eventsSubscriptions["1"].Subscribe(Boss1);
eventsSubscriptions["1"].Subscribe(Employee1);
//// Method 2
eventsSubscriptions["2"].Subscribe(Boss2);
eventsSubscriptions["2"].Subscribe(Employee2);
//// Method 3
eventsSubscriptions["3"].Subscribe(Employee3);
}
public void RaiseAllEvents()
{
eventsSubscriptions.RaiseAllEvents();
}
}
}
Класс EventsSubscriptions
namespace MyExample
{
public class EventsSubscriptions
{
private Dictionary<string, Subscription> subscriptions = new Dictionary<string, Subscription>();
public Subscription this[string id]
{
get
{
Subscription subscription = null;
subscriptions.TryGetValue(id, out subscription);
if (subscription == null)
{
subscription = new Subscription();
subscriptions.Add(id, subscription);
}
return subscription;
}
}
public void RaiseAllEvents()
{
foreach (Subscription subscription in subscriptions.Values)
{
Subscription iterator = subscription;
while (iterator != null)
{
iterator.RaiseEvent();
iterator = iterator.NextSubscription;
}
}
}
}
}
Подписка на класс
namespace MyExample
{
public class Subscription
{
private object suscribedObject;
private EventHandler eventHandler;
private Subscription nextSubscription;
public object SuscribedObject
{
set
{
suscribedObject = value;
}
}
public EventHandler EventHandler
{
set
{
eventHandler = value;
}
}
public Subscription NextSubscription
{
get
{
return nextSubscription;
}
set
{
nextSubscription = value;
}
}
public void Subscribe(object obj)
{
if (suscribedObject == null)
{
suscribedObject = obj;
}
else
{
if (nextSubscription != null)
{
nextSubscription.Subscribe(obj);
}
else
{
Subscription newSubscription = new Subscription();
newSubscription.eventHandler = this.eventHandler;
nextSubscription = newSubscription;
newSubscription.Subscribe(obj);
}
}
}
public void RaiseEvent()
{
if (eventHandler != null)
{
eventHandler(suscribedObject, new System.EventArgs());
}
}
}
}
Ответ 7
В основном я нашел более или менее элегантный способ решить эту проблему:
Используйте словарь идентификаторов для событий. Доступ к добавлению/удалению слушателей с помощью методов.
// ignore threadsafety and performance issues for now.
private Dictionary<string, EventHandler> _Events = new Dictionary<string, EventHandler> ();
private void AddId (string id)
{
_Events[id] = delegate {
};
}
public void Subscribe (string id, EventHandler handler)
{
_Events[id] += handler;
}
public void Unsubscribe (string id, EventHandler handler)
{
_Events[id] -= handler;
}
private void Raise (string id)
{
_Events[id] (this, new EventArgs ());
}
static void Main (string[] args)
{
var p = new Program ();
p.AddId ("foo");
p.Subscribe ("foo", (s, e) => Console.WriteLine ("foo"));
p.Raise ("foo");
p.AddId ("bar");
p.Subscribe ("bar", (s, e) => Console.WriteLine ("bar 1"));
p.Subscribe ("bar", (s, e) => Console.WriteLine ("bar 2"));
p.Raise ("bar");
Console.ReadKey ();
}
Ответ 8
Вы имеете в виду что-то вроде
public class EventArgs<T> : EventArgs
{
private T _value;
public T Value
{
get { return this._value; }
protected set { this._value = value; }
}
public EventArgs(T value)
{
this.Value = value;
}
}
// ...
public event EventHandler<EventArgs<string>> Foo;
?
Ответ 9
Реализован как один класс с простым API.
// subscribe to an event
eventsource.AddHandler( "foo", MyEventHandler );
// unsubscribe
eventsource.RemoveHandler( "foo", MyEventHandler );
// raise event for id
eventsource.RaiseEvent( "foo" );
public class EventSource
{
Dictionary<string,List<EventHandler>> handlers = new Dictionary<string,List<EventHandler>>();
public void AddHandler( string id, EventHandler handler )
{
if (!handlers.ContainsKey( id )) {
handlers[id] = new List<EventHandler>();
}
handlers[id].Add( handler );
}
public void RemoveHandler( string id, EventHandler handler )
{
if (handlers.ContainsKey( id )) {
handlers[id].Remove( handler );
}
}
public void RaiseEvent( string id )
{
if (handlers.ContainsKey( id )) {
foreach( var h in handlers[id] ) {
h( this, EventArgs.Empty );
}
}
}
}
Ответ 10
Как насчет реализации INotifyPropertyChanged вместо?
И затем...
protected void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void OnSourcePropertyChanged(Object sender, PropertyChangedEventArgs eventArgs)
{
if (eventArgs.PropertyName == "InterestingName")
{
// TODO:
}
}