Есть ли способ создать индексированные события в C # (или какой-то обходной путь)? - PullRequest
17 голосов
/ 10 февраля 2010

Заголовок сбивает с толку. Позвольте мне немного уточнить:

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

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

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

Вместо предоставления event EventHandler<PositionChangedEventArgs>, которое вызывается для ЛЮБЫХ позиционных изменений, я хотел бы иметь событие для каждого объекта (доступ по индексу), чтобы наблюдатели могли прослушивать события только для определенного идентификатора. 1026 *

Ответы [ 10 ]

16 голосов
/ 07 марта 2010

Вы можете сделать что-то вроде этого:

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;
8 голосов
/ 10 февраля 2010

Вам необходимо использовать производный от 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, и тогда подписчик может проверить это и решить, что с ним делать.

5 голосов
/ 09 марта 2010

Я думаю Реактивные расширения для .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 голосов
/ 10 февраля 2010

Я только начал использовать Rx Framework, и он великолепен. Я думаю, что это может быть то, что вы ищете.

http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx

Подписка и отмена подписки обрабатываются в рамках. Это называется LINQ для событий. Это «математический двойник» IEnumerable.

Ура, -jc

2 голосов
/ 10 марта 2010

Я подготовил полный пример. Вы можете использовать его следующим образом:

        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, поднятый с сотрудником Дэвидом Райлом
  • Метод 2 с боссом Синтия Джеймсон
  • Метод 2 с сотрудницей Самантой Сан
  • Метод 3, поднятый сотрудником Диком Банши

В следующих строках я вставлю код всех участвующих классов. Приложив немного терпения и скопировав / вставив, вы сможете проверить это = P Надеюсь, это вам поможет.

--- Код ---

Главная

namespace MyExample
{
    public class Program
    {

        static void Main(string[] args)
        {
            SomeExampleClass someExampleInstance = new SomeExampleClass();

            someExampleInstance.SuscribeObjects();            
            someExampleInstance.RaiseAllEvents();            

            Console.ReadLine();
        }


    }
}

Лицо класса

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

Employee

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

    }
}

Класс СобытияПодписки

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());
            }
        }
    }
}
2 голосов
/ 07 марта 2010

Я не знаю, буду ли я использовать события в этом случае, но я не уверен, что это действительно самая большая проблема.

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

Позвольте мне попытаться уточнить, если смогу ... Код для определения, заботится ли получатель A о событии от источника B где-нибудь. Может показаться, что имеет смысл поместить его в B. Однако проблема возникает, когда вы понимаете, что вы должны учитывать получателей C, D и E. У них может быть сложная логика, чтобы определить, что им важно (и это может даже измениться). Размещение всей этой логики в нашем эмиттере (B) приведет к созданию большого, неуклюжего класса, который трудно использовать.

Другой вариант заключается в том, чтобы A имел логику о том, хочет ли он событие внутренне. Это локализует логику на A, сохраняет B чистым и легким для использования всеми остальными. Однако недостатком этого является то, что логика подписки не может использоваться C, если она совпадает.

Но если мы действительно думаем об этом, у нас здесь происходит три вещи: генерируемое событие, фильтрация событий для получателей и получение / реакция на события. Принцип единой ответственности говорит нам, что класс должен нести только ответственность - одна причина для изменения. Включая логику фильтрации либо в A, либо в B, в зависимости от того, кто ее получает, теперь есть две обязанности и две причины для изменения.

Итак, в этом случае я бы хотел создать еще один класс Q, содержащий логику для фильтрации событий. Теперь ни A, ни B не получают дополнительной логики в своем коде. C не нужно заново его реализовывать. И, в качестве бонуса, теперь мы можем легко связать несколько фильтров, чтобы получить сложное поведение фильтра на основе очень простых компонентов.

1 голос
/ 11 февраля 2010

Я нашел в принципе один более или менее элегантный способ решения этой проблемы:

Используйте словарь идентификаторов для событий. Доступ к добавлению / удалению слушателей с помощью методов.

// 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 ();
}
0 голосов
/ 11 марта 2010

Как насчет реализации 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:
    }
}
0 голосов
/ 07 марта 2010

Реализовано как отдельный класс с простым 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 );
            }
        }       
    }
}
0 голосов
/ 10 февраля 2010

Вы имеете в виду что-то вроде

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;

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...