有没有办法在C#(或一些变通方法)中创建索引事件?

时间:2010-02-10 15:25:02

标签: c# events

标题令人困惑。让我澄清一下:

我想提供依赖于参数的事件,以便观察者可以决定在特定“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>,而是希望每个对象都有一个事件(由索引访问),因此观察者只能侦听特定ID的事件

10 个答案:

答案 0 :(得分:16)

你可以这样做:

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;

答案 1 :(得分:8)

您需要使用包含ID的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的实例,然后订阅者可以检查它并决定如何处理它。

答案 2 :(得分:5)

我刚刚开始使用Rx框架,它很棒。我想这可能就是你要找的东西。

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

订阅和取消订阅在框架中处理。它被称为LINQ to events。它是IEnumerable的“数学对偶”。

干杯, -jc

答案 3 :(得分:5)

我认为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教程。

答案 4 :(得分:2)

我不知道在这种情况下我是否会使用事件,但我不确定这是否是最大的问题。

如果您正在尝试控制订阅者,我认为您最好让订阅者处理过滤。只有他们知道他们真正想要过滤的内容,所以将过滤代码放在发布事件的类中似乎是次优的。

让我尝试澄清一下,如果可以的话...确定收件人A是否关心来自发射器B的事件的代码。将它放在B中似乎是有意义的。但是,当您意识到必须考虑收件人CDE时,问题才出现。他们可能有复杂的逻辑来确定他们关心的东西(甚至可能会改变它)。将所有这些逻辑放在我们的发射器(B)中将会产生一个难以使用的大型,笨拙的类。

另一种选择是让A具有关于它是否想要内部事件的逻辑。这将逻辑本地化为A,使B保持清洁,并且易于被其他人使用。但是,这样做的缺点是如果C恰好相同,则订阅逻辑不可用。

但是如果我们真的想到它,我们在这里发生了三件事......发出的事件,向接收者过滤事件,以及接收/反应事件。单一责任原则告诉我们,一个班级应该只有责任 - 一个改变的理由。通过在AB中包含过滤逻辑,无论哪个获取它现在都有两个职责,并且两个原因需要更改。

因此,在这种情况下,我想要做的是创建另一个类Q,它包含事件过滤的逻辑。现在,AB都没有在代码中获得额外的逻辑。 C无需重新实施。而且,作为奖励,我们现在可以轻松地将多个过滤器绑定在一起,以便根据非常简单的组件获得复杂的过滤器行为。

答案 5 :(得分:2)

我准备了一个完整的例子。您可以这样使用它:

        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()方法,这是控制台输出:

  • 与Boss John Smith一起提出的方法1
  • 使用员工David Ryle提出的方法1
  • 方法2由Boss Cynthia Jameson提出
  • 方法2由员工Samantha Sun提出
  • 使用员工Dick Banshee提出的方法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)
        { }
    }
}

<强>员工

namespace MyExample
{
    public class Employee : Person
    {
        public Employee(string name)
            : base(name)
        { }
    }
}

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

答案 6 :(得分:1)

我基本上找到了解决这个问题的优雅方法:

使用ids字典来表示事件。访问通过方法添加/删除侦听器。

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

答案 7 :(得分:0)

你的意思是

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;

答案 8 :(得分:0)

使用简单的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 );
            }
        }       
    }
}

答案 9 :(得分:0)

如何实施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:
    }
}