【问题标题】:Why WeakEventManager does not fire an event when the sender is not the nominal?为什么当发送者不是名义上的时候 WeakEventManager 不会触发事件?
【发布时间】:2014-09-03 08:47:42
【问题描述】:

我不喜欢非标准模式,但我正在对我的应用进行快速测试,但遇到了这种奇怪的行为。

考虑一个公开事件的普通类,这里是非常常见的 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)
            );
    }
}

编辑:这是实现预期行为的粗略方法(为简单起见,硬链接)。

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

【问题讨论】:

  • 您是否检查了WeakEventManager 中的NewListenerList 以查看仍然引用了哪些侦听器
  • 检查该方法看起来有点复杂,但是我尝试使用内存分析器并且似乎从未调用过(虽然不确定)。

标签: c# weak-events weakeventmanager


【解决方案1】:

我没有找到任何说明此问题的文档,但您可以查看 WeakEventManager source code 了解为什么会发生这种情况。

管理器保留一个表,将已注册的源对象映射到其处理程序。请注意,此源对象是您在添加处理程序时传入的对象。

当管理器收到一个事件时,它会从这个表中查找相关的处理程序,使用事件的发送者作为键。显然,如果这个发件人与注册的发件人不同,就找不到预期的处理程序。


编辑

下面是一些伪代码来说明。

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

添加处理程序时,您传入的源将添加到查找表中。当接收到一个事件时,会在此表中查询此事件的发送者,以获取此发送者/源的相关处理程序。

在您的示例中,您正在收听的源是c,第一次也是ActualSender 的值。因此,事件的发送者与注册的源相同,这意味着正确找到并调用了处理程序。

然而,第二次ActualSenderc 是不同的实例。注册的源没有改变,但是sender参数的值现在不同了!因此它将无法检索处理程序,并且不会调用任何内容。

【讨论】:

  • 好的,但这仍然很奇怪。保留每个发件人的 WeakReference 并在发件人死亡时将其丢弃不是更简单吗?说真的,我看不出有什么好的理由这样做。
  • 我没有看到任何问题:我为一个弱的多听众可观察源做了类似的事情。只需注册一组听众而不是一个听众。我从这篇优秀的文章开始:blog.stephencleary.com/2009/07/nitoweakreference-and.html
  • 我的意思是你说的,但是 - 再次 - 我没有看到问题。管理器充当引发事件的实例的代理侦听器,对吗?因此,“AddHandler”方法应该订阅事件并为侦听器公开一个弱集合。然后,实例引发一个事件,并由代理接收。现在,由于任何 CLR 事件都继承了 EventArgs,它会将“sender”字段传递给代理。此时,为什么代理在将事件转发给真正的听众时不使用这个“发送者”引用?
  • 这与管理器持有的许多不同的源无关:不要将源表(这是 AddHandler 的第一个 arg)与事件 arg 混合!无需在管理器中保存“发送者”数据。
  • 我在帖子中添加了一些示例伪代码,以更好地解释正在发生的事情。我不相信有任何方法可以在兴趣点 A 处检索注册的源,除了依赖于 sender 参数相同。 (并且我正在删除以前的 cmets,因为我认为在此编辑之后它们不再相关。)
猜你喜欢
  • 1970-01-01
  • 2010-11-18
  • 2011-04-27
  • 2011-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-18
  • 1970-01-01
相关资源
最近更新 更多