Подтвердить что ты не робот

Есть ли способ создать индексированные события в С# (или некоторое обходное решение)?

Подпись озадачивает. Позвольте мне немного пояснить:

Я хотел бы предоставить события, которые зависят от параметра, поэтому наблюдатель может принять решение о получении событий, если что-то произойдет с определенным "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>, созданного для ЛЮБЫХ позиционных изменений, я хотел бы иметь событие для каждого объекта (доступ к нему по индексу), поэтому наблюдатели могут прослушивать события только для определенного идентификатора.

4b9b3361

Ответ 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:
    }
}