委托转换符平等和unables从事件断开 [英] Delegate conversion breaks equality and unables to disconnect from event

查看:192
本文介绍了委托转换符平等和unables从事件断开的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近发现代表一些奇怪的行为。它的出现,是铸造一个委托给其他一些(兼容的,甚至是同一个)破代表的平等。假设我们有一些类方法:

I recently found some strange behaviour of delegates. It appears, that casting a delegate to some other (compatible, or even the same) breaks the equality of delegates. Suppose we have some class with method:

public class Foobar {
   public void SomeMethod(object sender, EventArgs e);
}

现在让我们做一些代表:

Now let's make some delegates:

var foo = new Foobar();
var first = new EventHandler(foo.SomeMethod);
var second = new EventHandler(foo.SomeMethod);

当然,因为与会代表相同的目标,方法和调用列表被认为是相等的,这个断言会通过:

Of course, because delegates with the same target, method and invocation list are considered equal, this assertion will pass:

Assert.AreEqual(first, second);

不过,这种说法不会:

But this assertion won't:

Assert.AreEqual(new EventHandler(first), new EventHandler(second));

不过,接下来的断言会通过:

However, the next assertion will pass:

Assert.AreEqual(new EventHandler(first), new EventHandler(first));

这是相当尴尬的行为,因为这两个代表们认为是相等的。以某种方式将其转换为即使是同一类型的委托打破其平等。同样的将是,我们定义自己的类型的委托:

This is quite awkward behaviour, since both delegates are considered equal. Converting it to a delegate of even the same type in some way breaks its equality. The same will be, we define our own type of delegate:

public delegate MyEventHandler(object sender, EventArgs e);

代表可以从事件处理程序转换为 MyEventHandler ,而在相反的方向,但是这个转换后,他们赢得'T是平等的。

Delegates can be converted from EventHandler to MyEventHandler, and in reverse direction, however after this conversion, they won't be equal.

此行​​为是非常误导的时候,我们要定义与事件明确添加删除来传递处理程序某些其他对象。因此,这两个以下事件定义的行为是不同的:

This behaviour is very misleading when we want to define an event with explicit add and remove to pass handler to some other object. Therefore both of event definitions below are acting differently:

public event EventHandler MyGoodEvent {
   add {
      myObject.OtherEvent += value;
   }
   remove {
      myObject.OtherEvent -= value;
   }
}

public event EventHandler MyBadEvent {
   add {
      myObject.OtherEvent += new EventHandler(value);
   }
   remove {
      myObject.OtherEvent -= new EventHandler(value);
   }
}

第一个将正常工作。第二个将导致内存泄漏,因为当我们的一些方法连接的情况下,我们将无法断开:

The first one will work fine. The second one will cause memory leaks, because when we connect some method to the event, we won't be able to disconnect:

var foo = new Foobar();
// we can connect
myObject.MyBadEvent += foo.SomeMethod;
// this will not disconnect
myObject.MyBadEvent -= foo.SomeMethod;

这是因为,正如有人指出,转换(发生在事件添加删除)的代表后,不相等。其中加入代表是不一样的,因为它被除去。这可能会导致严重的,而且很难找到内存泄漏。

This is because, as it was pointed, after conversion (which occurs in event add and remove) delegates are not equal. Delegate which is added is not the same, as which is removed. This can lead to serious, and hard to find memory leaks.

当然,我们可以说只使用第一种方法。但它可能无法在某些情况下,尤其是当与泛型处理

Of course one can say to use only first approach. But it may be impossible in some circumstances, especially when dealing with generics.

考虑以下情形。让我们假设,我们有委托和接口的第三方库,它看起来是这样的:

Consider the following scenario. Let's suppose, that we have delegate and interface from third party library, which look like this:

public delegate void SomeEventHandler(object sender, SomeEventArgs e);

public interface ISomeInterface {
   event SomeEventHandler MyEvent;
}

我们想实现该接口。内蒙古实施这将是基于其他一些第三方库,其中有一个泛型类:

We would like to implement that interface. Inner implementation of this will be based on some other third party library, which has a generic class:

public class GenericClass<T> where T : EventArgs
{    
    public event EventHandler<T> SomeEvent;
}

我们希望这个泛型类的事件暴露的接口。例如,我们可以做这样的事情:

We want this generic class to expose its event to the interface. For example, we can do something like that:

public class MyImplementation : ISomeInterface {

   private GenericClass<SomeEventArgs> impl = new GenericClass<SomeEventArgs>();

   public event SomeEventHandler MyEvent {
      add { impl.SomeEvent += new SomeOtherEventHandler(value); }
      remove { impl.SomeEvent -= new SomeOtherEventHandler(value); }
   }

}

由于类使用通用的事件处理程序,接口采用一些其他的,我们必须做出的转换。当然,这使得一个事件不可能从断开。唯一的办法是存储一个委托到一个变量,它连接,并在需要时断开连接。但是,这是非常脏的做法。

Because class uses generic event handler, and interface uses some other, we have to make a conversion. Of course, this makes an event impossible to disconnect from. The only way is to store a delegate into a variable, connect it, and disconnect when needed. This is however very dirty approach.

谁能告诉我,如果是打算像这样工作,或者这是一个错误?如何使用断开连接的能力,连接一个事件处理程序兼容的一在一个干净的方式?

Can someone tell, if it is intended to work like this, or it is a bug? How to connect one event handler to compatible one in a clean way with the ability to disconnect it?

推荐答案

这似乎意。 1 当你说新DelegateType( otherDelegate)你实际上是创建一个新的委托指向不为 otherDelegate 相同的目标和方法做,但它指向 otherDelegate 作为目标和 otherDelegate.Invoke(...)的方法。所以他们的确是不同的代表:

This appears to be intended.1 When you say new DelegateType(otherDelegate) you are actually creating a new delegate that points not to the same target and method as otherDelegate does, but that points to otherDelegate as the target and otherDelegate.Invoke(...) as the method. So they are indeed different delegates:

csharp> EventHandler first = (object sender, EventArgs e) => {};
csharp> var second = new EventHandler(first);
csharp> first.Target;
null
csharp> first.Method;
Void <Host>m__0(System.Object, System.EventArgs)
csharp> second.Target;
System.EventHandler
csharp> second.Method;
Void Invoke(System.Object, System.EventArgs)
csharp> second.Target == first;
true


1 经检查C#规格,目前尚不清楚对我来说,如果这是在技术上违反了规范的。我这里重现§7.5.10.5的一部分,从C#laguange规范3.03.0:


1 Upon examining the C# specification, it is not clear to me if this is technically in violation of the spec. I reproduce here part of §7.5.10.5 from the C# laguange specification 3.03.0:

代表创建EX $ P $形式pssion 新的D(E)运行时处理,其中 D 委托类型电子 EX pression 的,包括以下步骤:

The run-time processing of a delegate-creation-expression of the form new D(E), where D is a delegate-type and E is an expression, consists of the following steps:

      
  • ...
  •   
  • 如果电子委托类型的值:   
        
    • ...
    •   
    • 在新的委托实例使用相同的调用列表由电子给出的委托实例初始化。
    •   
    • ...
    • If E is a value of a delegate-type:
      • ...
      • The new delegate instance is initialized with the same invocation list as the delegate instance given by E.

      也许正是跨pretation是否具有相同的调用列表初始化的事情也算是满足通过让一名代表称对方委托的的invoke()方法。我倾向于倾向于不的。 (乔恩斯基特向倾斜是的。

      Perhaps it is a matter of interpretation whether "initialized with the same invocation list" can be considered satisfied by having one delegate call the other delegate's Invoke() method. I tend to lean towards "no" here. (Jon Skeet leans towards "yes.")

      作为一种变通方法,您可以使用该扩展方法向与会代表转换,同时保留其确切的调用列表:

      As a workaround, you can use this extension method to convert delegates while retaining their exact invocation list:

      public static Delegate ConvertTo(this Delegate self, Type type)
      {
          if (type == null) { throw new ArgumentNullException("type"); }
          if (self == null) { return null; }
      
          return Delegate.Combine(
              self.GetInvocationList()
                  .Select(i => Delegate.CreateDelegate(type, i.Target, i.Method))
                  .ToArray());
      }
      

      看到一个演示。)

      这篇关于委托转换符平等和unables从事件断开的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆