开发者

IronPython instancemethods cannot be removed from CLR delegate

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! :)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜