I hope some of you can help me here as I am at my wits end with this one. It seems like I can add instancemethod delegates but note remove them. The object开发者_开发技巧 reference to the delegate is the same, surely?
Here is a distilled reproduction of the bug: Given this simple little C# class:
public class TypedEvent<T1> : TypedEventBase {
/** A definition of the function signature. */
public delegate void ActionSignature(T1 kParam1);
/** @brief A reference to the delegate which stores our handles. */
protected ActionSignature pAction = null;
public virtual bool addHandler(ActionSignature kHandler)
{
// If we are already contained in the list then we don't need to be added again.
if (pAction != null)
{
if (this.pAction.GetInvocationList().Contains(kHandler))
return false;
}
// Add us to the list and return success.
this.pAction += kHandler;
return true;
}
public virtual bool removeHandler(ActionSignature kHandler)
{
// If we have no handles return false.
if (pAction == null)
return false;
// If we do not contain the handler then return false.
if (!this.pAction.GetInvocationList().Contains(kHandler))
return false;
// Remove the handler and return true.
this.pAction -= kHandler;
return true;
}
public void invoke(T1 kParam1)
{
if (this.pAction != null)
this.pAction(kParam1);
}
}
This works as expected:
## -- Procedural functions (function) work. ---
a = App.TypedEvent[object]()
def test(s):
print s
a.removeHandler(test)
a.addHandler(test)
a.addHandler(test)
# Output
a.invoke("Hello")
>>> Hello
a.invoke("Hello")
>>> Hello
as does this:
## -- Static methods (unbound) work. ---
a = App.TypedEvent[object]()
class Foo:
@staticmethod
def test(s):
print s
a.removeHandler(Foo.test)
a.addHandler(Foo.test)
a.addHandler(Foo.test)
# Output
a.invoke("Hello")
>>> Hello
a.invoke("Hello")
>>> Hello
yet this does not work:
## -- Instance methods (bound) do not work. --
a = App.TypedEvent[object]()
class Foo:
def test(self, s):
print s
a.removeHandler(self.test)
a.addHandler(self.test)
f = Foo()
a.addHandler(f.test)
# Output
a.invoke("Hello")
>>> Hello
a.invoke("Hello")
>>> Hello
>>> Hello
a.invoke("Hello")
>>> Hello
>>> Hello
>>> Hello
>>> Hello
It looks like the instance methods are somehow being changed as they are passed into the function and different object references are making the invocation list. I get the feeling that I am missing something stupid!
Cheers,
John
I've not found a perfect answer yet, but this comes close to resolving the problem.
To recap, the problem is that when you pass a Python instancemethod into the CLR and try to attach it to a delegate, the DLR wraps the target object callsite with another class which acts as a staging platform to invoking the dynamic event. Now, this causes us problems because as best as I can tell, this is done automatically in the language and creates a different instance for each time we try and pass the instancemethod between the CLR and the DLR.
So I've come up with a nicer solution than DLR-side reference storing that won't create memory leaks. The premise is that it will try to find a similar delegate in the current invocation list. This will do the basic checks to find a match between the delegate passed and the existing delegate list. Then, if none are found it will delve into the IronPython base code and have a rummage around, comparing a few things. If it THEN finds a match, it removes that instance.
Here is the function: ` /// /// Find an existing delegate bound to the same instance/method as the parameter. /// /// This is useful for working around the problem described http://ironpython.codeplex.com/workitem/30338 /// The delegate to find an existing copy of (data-wise, not reference wise). /// Null if one was not found, otherwise return the existing delegate. internal ActionSignature findDelegate(ActionSignature kInstance) { // Skip null data. if (kInstance == null) return null;
// Otherwise get the invocation list from our multicast delegate.
var lInvocationList = pAction.GetInvocationList();
ActionSignature kExisting = null;
// Do the most basic check (i.e. is our object reference stored in here already!)
if (lInvocationList.Contains(kInstance))
return kInstance;
// Go through and find if one already stored matches our new instance.
foreach (var kIter in lInvocationList)
{
// Cast to our type.
var kIterAS = kIter as ActionSignature;
// Firstly, check our methods are the same. This works for all.
if (kIterAS.Method == kInstance.Method)
{
// Now check the targets match (this way works for IPYs staticmethods and functions).
if (kInstance.Target.Equals(kIterAS.Target))
{
// We matched, so save and break.
kExisting = kIterAS;
break;
}
// Now check if the targets match as instancemethods.
// This is to get around a problem with IronPython where instancemethods
// cannot be removed from CLR delegates. See here:
// http://ironpython.codeplex.com/workitem/30338
var oarr_dd = kIterAS.Target as object[];
var oarr_kh = kInstance.Target as object[];
if (oarr_dd != null && oarr_dd.Length > 0 && oarr_kh != null && oarr_kh.Length > 0)
{
IronPython.Runtime.Method m_dd = oarr_dd[0] as IronPython.Runtime.Method;
IronPython.Runtime.Method m_kh = oarr_kh[0] as IronPython.Runtime.Method;
if (m_dd != null && m_kh != null)
{
if (m_kh.im_self == m_dd.im_self)
{
// We matched, so save and break.
kExisting = kIterAS;
break;
}
}
}
// If we ended up here, we have no match so we can assume this is not the delegate
// we are looking for!
}
}
// Now if our matched delegate is null, it is not found.
return kExisting;
}
`
Hope that helps someone! :)
精彩评论