简体   繁体   English

为什么WeakEventManager在发件人不是名义上时不会触发事件?

[英]Why WeakEventManager does not fire an event when the sender is not the nominal?

I don't like off-the-standard pattern, but I was making a quick test on my app, and I bumped against this strange behavior. 我不喜欢不合标准的模式,但我正在对我的应用程序进行快速测试,并且我遇到了这种奇怪的行为。

Consider a normal class exposing an event, here the very common PropertyChanged, but I think could be any other. 考虑一个暴露事件的普通类,这里是非常常见的PropertyChanged,但我认为可能是其他任何一个。

The subscriber chooses to subscribe the event via the WeakEventManager helper. 订户选择通过WeakEventManager帮助程序订阅事件。 Now, the "odd" thing is the actual sender reference: as long the instance is the same as was used on the subscription, everything goes fine. 现在,“奇怪”的东西是实际的发送者引用:只要实例与订阅上使用的实例相同,一切都很顺利。 However, when you use another object, no notification will be issued. 但是,当您使用其他对象时,不会发出通知。

Again, that's NOT a good pattern, but I wonder whether there is any good reason for this limitation, or rather that is a kind a bug. 同样,这不是一个好的模式,但我想知道这个限制是否有任何好的理由,或者说这是一种错误。 More a curiosity than a real need. 更多的是好奇心而不是真正的需求。

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

EDIT: here is a rough way to achieve the expected behavior (hard-links for sake of simplicity). 编辑:这是一种实现预期行为的粗略方法(为简单起见,硬链接)。

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

I haven't found any documentation that says anything about this, but you can look at the WeakEventManager source code to see why this happens. 我没有找到任何有关此内容的文档,但您可以查看WeakEventManager源代码以了解发生这种情况的原因。

The manager keeps a table mapping the registered source objects to its handlers. 管理器保持一个表,将已注册的源对象映射到其处理程序。 Note that this source object is the one you pass in when adding the handler. 请注意,此源对象是您在添加处理程序时传递的对象。

When the manager receives an event, it looks up the relevant handlers from this table, using the sender of the event as key. 当管理器收到事件时,它会使用事件的发送者作为密钥从该表中查找相关的处理程序。 Obviously, if this sender is not the same as the one that is registered, the expected handlers will not be found. 显然,如果此发件人与注册的发件人不同,则找不到预期的处理程序。


Edit 编辑

Below is some pseudo-code to illustrate. 下面是一些伪代码来说明。

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

When adding a handler, the source you pass in gets added to a lookup table. 添加处理程序时,传入的源将添加到查找表中。 When an event is received, this table is queried for the sender of this event, to get the relevant handlers for this sender/source. 收到事件后,将为此事件的发件人查询此表,以获取此发件人/来源的相关处理程序。

In your sample, the source you're listening on is c , which the first time is also the value for ActualSender . 在您的示例中,您正在侦听的源是c ,这是第一次也是ActualSender的值。 Thus, the sender of the event is the same as the registered source, which means the handler is correctly found and called. 因此,事件的发送者与注册的源相同,这意味着正确地找到并调用了处理程序。

The second time however, the ActualSender is a different instance than c . 但是,第二次, ActualSender是一个与c不同的实例。 The registered source doesn't change, but the value of the sender parameter is now different! 注册的源不会更改,但sender参数的值现在不同了! Hence it won't be able to retrieve the handlers, and nothing gets called. 因此,它将无法检索处理程序,也无法调用任何内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM