Mar*_*ari 5 c# weak-events weakeventmanager
我不喜欢不合标准的模式,但我正在对我的应用程序进行快速测试,并且遇到了这种奇怪的行为。
考虑一个公开事件的普通类,这里是非常常见的 PropertyChanged,但我认为可以是任何其他类。
订阅者选择通过 WeakEventManager 助手订阅事件。现在,“奇怪”的事情是实际的发件人引用:只要实例与订阅中使用的实例相同,一切都会顺利。但是,当您使用另一个对象时,不会发出通知。
同样,这不是一个好的模式,但我想知道这种限制是否有任何充分的理由,或者说这是一种错误。与其说是真正的需要,不如说是一种好奇心。
class Class1
{
static void Main(string[] args)
{
var c = new MyClass();
WeakEventManager<INotifyPropertyChanged, PropertyChangedEventArgs>.AddHandler(
c,
"PropertyChanged",
Handler
);
c.ActualSender = c;
c.Number = 123; //will raise
c.ActualSender = new Class1();
c.Number = 456; //won't raise
Console.ReadKey();
}
static void Handler(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine("Handled!");
}
}
class MyClass : INotifyPropertyChanged
{
public object ActualSender { get; set; }
private int _number;
public int Number
{
get { return this._number; }
set
{
if (this._number != value)
{
this._number = value;
this.OnPropertyChanged("Number");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(
string name
)
{
this.PropertyChanged(
this.ActualSender,
new PropertyChangedEventArgs(name)
);
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:这是实现预期行为的粗略方法(为简单起见,硬链接)。
class Class1
{
static void Main(string[] args)
{
var cx = new MyClass();
var cy = new MyClass();
Manager.AddHandler(cx, Handler1);
Manager.AddHandler(cx, Handler2);
Manager.AddHandler(cy, Handler1);
Manager.AddHandler(cy, Handler2);
cx.ActualSender = cx;
cx.Number = 123;
cx.ActualSender = new Class1();
cx.Number = 456;
cy.ActualSender = cy;
cy.Number = 789;
cy.ActualSender = new Class1();
cy.Number = 555;
Console.ReadKey();
}
static void Handler1(object sender, PropertyChangedEventArgs e)
{
var sb = new StringBuilder();
sb.AppendFormat("Handled1: {0}", sender);
var c = sender as MyClass;
if (c != null) sb.AppendFormat("; N={0}", c.Number);
Console.WriteLine(sb.ToString());
}
static void Handler2(object sender, PropertyChangedEventArgs e)
{
var sb = new StringBuilder();
sb.AppendFormat("Handled2: {0}", sender);
var c = sender as MyClass;
if (c != null) sb.AppendFormat("; N={0}", c.Number);
Console.WriteLine(sb.ToString());
}
}
static class Manager
{
private static Dictionary<object, Proxy> _table = new Dictionary<object, Proxy>();
public static void AddHandler(
INotifyPropertyChanged source,
PropertyChangedEventHandler handler
)
{
var p = new Proxy();
p._publicHandler = handler;
source.PropertyChanged += p.InternalHandler;
_table[source] = p;
}
class Proxy
{
public PropertyChangedEventHandler _publicHandler;
public void InternalHandler(object sender, PropertyChangedEventArgs args)
{
this._publicHandler(sender, args);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我还没有找到任何有关此内容的文档,但您可以查看WeakEventManager 源代码以了解为什么会发生这种情况。
管理器维护一个将注册的源对象映射到其处理程序的表。请注意,此源对象是您在添加处理程序时传入的对象。
当管理器接收到事件时,它使用事件的发送者作为键从该表中查找相关处理程序。显然,如果该发送者与注册的发送者不同,则将找不到预期的处理程序。
编辑
下面是一些伪代码来说明。
public class PseudoEventManager : IWeakEventListener
{
private static PseudoEventManager _instance = new PseudoEventManager();
private readonly Dictionary<object, List<object>> _handlerTable
= new Dictionary<object, List<object>>();
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
foreach (var handler in _handlerTable[sender]) // point of interest A
//invoke handler
}
public static void AddHandler(object source, object handler)
{
if (!_instance._handlerTable.ContainsKey(source))
_instance._handlerTable.Add(source, new List<object>()); //point of interest B
_instance._handlerTable[source].Add(handler);
//attach to event
}
}
Run Code Online (Sandbox Code Playgroud)
添加处理程序时,您传入的源将添加到查找表中。当接收到事件时,将在该表中查询该事件的发送者,以获取该发送者/源的相关处理程序。
在您的示例中,您正在收听的源是c,第一次也是 的值ActualSender。因此,事件的发送者与注册的源相同,这意味着处理程序被正确找到并调用。
然而,第二次时, 的ActualSender情况与 不同c。注册的源没有改变,但是sender参数的值现在不同了!因此它将无法检索处理程序,并且不会调用任何内容。