【发布时间】:2009-10-15 02:13:50
【问题描述】:
我一直在尝试了解事件/代表,但对两者之间的关系感到困惑。我知道委托允许您调用不同的函数,而无需知道正在调用的特定函数。 (例如:一个绘图函数需要接受不同函数的输入)。
但我看不到事件中如何使用委托。
有人可以构建一个简单的示例(使用伪代码、C# 或 Java)来说明与事件相关的委托的工作原理吗?
谢谢!
【问题讨论】:
标签: c# java event-handling delegates
我一直在尝试了解事件/代表,但对两者之间的关系感到困惑。我知道委托允许您调用不同的函数,而无需知道正在调用的特定函数。 (例如:一个绘图函数需要接受不同函数的输入)。
但我看不到事件中如何使用委托。
有人可以构建一个简单的示例(使用伪代码、C# 或 Java)来说明与事件相关的委托的工作原理吗?
谢谢!
【问题讨论】:
标签: c# java event-handling delegates
(这都是从 C# 的角度来看的。)
我有一个article about the differences between events and delegates。这更详细地涵盖了下面提到的所有内容。
基本上,我喜欢将事件视为一个属性——它是一对方法,仅此而已。一个事件不是 get/set,而是 add/remove——意思是“添加这个事件处理程序”和“删除这个事件处理程序”。从本质上讲,这就是一个事件。
C# 也有 field-like events 这是一个快捷方式:
public event EventHandler Foo;
声明一个字段 和 一个事件,具有几乎微不足道的添加/删除实现。在类中,引用Foo 指的是字段。课外,指Foo指事件。
基本思想是事件允许其他代码订阅和取消订阅,方法是传入一个委托(事件处理程序)。通常,订阅是通过创建一个新的多播委托来实现的,该委托包含上一个事件处理程序列表和新的。因此,如果您将事件处理程序存储在名为 myEventHandlers 的字段中,订阅实现可能是:
myEventHandlers += value;
类似地取消订阅通常涉及创建一个新的多播委托没有指定的处理程序:
myEventHandlers -= value;
然后,当您想要引发/触发事件时,只需调用该多播委托 - 通常使用无效检查以避免在没有人订阅时引发异常:
EventHandler handler = myEventHandlers;
if (handler != null)
{
// You could pass in a different "sender" and "args" of course
handler(this, EventArgs.Empty);
}
使用事件,订阅者不知道彼此,也不能自己引发事件(通常)。换句话说,它是一种封装模式,在语言和平台中都被赋予了地位。
【讨论】:
您需要具体说明所需的语言。据我所知,Java 没有委托的概念(尽管我可能完全错了);它倾向于遵循观察者模式进行事件处理。
但是,C# 可以。 C# 中的event 与委托的关系与属性与其支持字段的关系相同。委托本身存储了指向处理事件的函数的指针(或者更准确地说,是附加到事件的指针列表;我在这里松散地使用术语“指针”)。
如果我在 C# 中声明:
public event EventHandler MyEvent;
并像这样调用事件:
MyEvent(this, EventArgs.Empty);
这实际上只是完整事件实现的一些简写:
private EventHandler myEventHandler;
public event EventHandler MyEvent
{
add { myEventHandler += value; }
remove { myEventHandler -= value; }
}
并称它为...
myEventHandler(this, EventArgs.Empty);
所有这一切都是说实际的event 公开了两个操作:add 和remove,消费代码使用它们将其事件处理程序附加到事件。在默认(速记)表示法中,编译器创建委托类型的私有实例成员,并以我上面描述的方式使用它。当您“调用”事件时,编译器实际上会用事件名称替换它创建的私有支持委托的名称。这就是为什么不能从子类调用event 的原因——如果事件是用简写形式创建的,那么支持成员就是private。
【讨论】:
区别很简单。
delegate 是一个有两个字段的类 - object 和 MethodInfo。
event 是 delegate 类型的私有字段和两个公共方法 add 和 remove。
通常在事件 MulticastDelegate 的底层使用 - 它是一个继承自 Delegate 的类并包含代表列表。这允许事件有多个订阅者。
【讨论】:
你可以看看: http://msdn.microsoft.com/en-us/library/17sde2xt.aspx
示例在此继续: http://msdn.microsoft.com/en-us/library/xwbwks95.aspx
如前所述,基本上,事件只是委托的特殊情况,但随着 .NET 3.5 中的更改,您可以在不使用委托的情况下编写事件,尽管在底层仍会编写委托。
如果你看这篇文章,他们会展示如何使用 lambda 表达式和匿名函数来处理事件: http://msdn.microsoft.com/en-us/library/ms366768.aspx
【讨论】:
.Net 事件只是底层的委托:它们在编译器中提供了一些语法糖。
您可以设置/重置委托,但您只能添加或删除事件处理程序。理由是您不会关心还有谁订阅了事件,而普通委托更多地用于“回调”场景。
但归根结底,它们非常相似。
一些资源:
【讨论】:
我是 Java 世界的新手,但我不得不承认我很高兴,但是我仍然想念一些 C# 的东西,所以设计这个给我带来了很好结果的模式,Java 专家看到了使用它的一些缺点图案?它只支持java 8:
@FunctionalInterface
public interface IEvent<TEventArgs extends Object> {
void invoke(TEventArgs eventArgs);
}
public class EventHandler<TEventArgs>
{
private ArrayList<IEvent<TEventArgs>> eventDelegateArray = new ArrayList<>();
public void subscribe(IEvent<TEventArgs> methodReference)
{
eventDelegateArray.add(methodReference);
}
public void unSubscribe(IEvent<TEventArgs> methodReference)
{
eventDelegateArray.remove(methodReference);
}
public void invoke(TEventArgs eventArgs)
{
if (eventDelegateArray.size()>0)
eventDelegateArray.forEach(p -> p.invoke(eventArgs));
}
}
public class DummyEventProducer
{
// The event
public EventHandler<String> myEvent = new EventHandler<>();
public void onMyEvent(String A)
{
myEvent.invoke(A);
}
}
public class DummySubscriber {
// The method will be subscribed to the event
public void methodCallWhenEventGetTriggered(String eventArgs)
{
System.out.println("event fired with eventargs: " + eventArgs);
}
}
public class Main {
public static void main(String[] args)
{
// A dummy producer
DummyEventProducer producer = new DummyEventProducer();
// A dummy subscribers
DummySubscriber testingInstanceA = new DummySubscriber();
DummySubscriber testingInstanceB = new DummySubscriber();
DummySubscriber testingInstanceC = new DummySubscriber();
// We create decoupled event links because we want to un-subscribe later
IEvent<String> EventSink1 = testingInstanceA::methodCallWhenEventGetTriggered;
IEvent<String> EventSink2 = testingInstanceB::methodCallWhenEventGetTriggered;
IEvent<String> EventSink3 = testingInstanceC::methodCallWhenEventGetTriggered;
// subscribe to the event on dummy producer
producer.myEvent.subscribe(EventSink1);
producer.myEvent.subscribe(EventSink2);
producer.myEvent.subscribe(EventSink3);
// fire the event on producer
producer.onMyEvent("Hola MUNDO with decoupled subscriptions!");
// unsubscribe to the event on dummy producer
producer.myEvent.unSubscribe(EventSink1);
producer.myEvent.unSubscribe(EventSink2);
producer.myEvent.unSubscribe(EventSink3);
// fire the event on producer again
producer.onMyEvent("Hola MUNDO! with no events subscriptions :(");
// IF YOU DON CARE ABOUT UNSUBSCRIBE YOU CAN LINK EVENTS DIRECTLY TO THE SUBSCRIBER
producer.myEvent.subscribe(testingInstanceA::methodCallWhenEventGetTriggered);
producer.myEvent.subscribe(testingInstanceB::methodCallWhenEventGetTriggered);
producer.myEvent.subscribe(testingInstanceC::methodCallWhenEventGetTriggered);
// fire the event on producer again
producer.onMyEvent("Hola MUNDO! with strong link subscriptions (cannot be un-subscribed");
}
}
随时提问、更正、建议 =) 最好的问候!
【讨论】: