10

Feel free to question my sanity.

I need to determine if an Action<T> vs Action<T> is the original instance. What I have is a class with a class variable protected Action<T> MessageCallback = null; when my abstract class Message<T> is created via an abstract method I force "them" to initialize the MessageCallBack. This MessageCallback gets added to a IList<Action<object>>. Each action defined in this list can be different. Now, what I want to do is remove a specific action from the list but am failing in my attempts to compare it.

Below is a sample of the last setup I have attempted:

public void Unsubscribe<TMessage>(Action<TMessage> messageCallback)
    {
        var messageType = typeof(TMessage);

        var callbackTypes = messageReceivedCallbacks
            .Keys
            .Where(k => k.IsAssignableFrom(messageType));

        lock (messageReceivedCallbacks)
        {
            foreach (var callbackType in callbackTypes)
            {
                messageReceivedCallbacks[callbackType].Remove(new Action<object>(m => 
                    messageCallback((TMessage)m)));
            }
        }
    }

I understand what I want to do may not be possible but generally I'm just doing something an improper way or lack proper knowledge to do it like I am suppose to. Thanks in advance for any help you provide.

      • UPDATE after trying some of the methods below:

Comparing them keeps failing. None of the currently 3 suggestions below work. I do believe I can change how I am handling this and make it work how I need it to by passing in a key with the action that then points to separate list of <key, indexOfAction> then removing that by index. However, I feel like I still need to give this good effort to solve, so I am going to give a little more information to see if it helps.

Here is the list:

private readonly IDictionary<Type, IList<Action<object>>> messageReceivedCallbacks;

Here is how an action gets added to the list:

void AddMessageReceivedCallback<TMessage>(Action<TMessage> messageReceivedCallback)
    {
        var intermediateReceivedCallback = new Action<object>(m => 
            messageReceivedCallback((TMessage)m));

        var receivedList = messageReceivedCallbacks.GetOrCreateValue(typeof(TMessage),
            () => new List<Action<object>>());
        lock (receivedList)
        {
            receivedList.Add(intermediateReceivedCallback);
        }
    }

Please bear with me as I am rather new to some of this more advanced coding. I can tell this prevents me from doing a direct instance comparison because of the new keyword. In the attempt I (first) posted above I was trying to get my callback to match the form in which it was added. It does not work. I've tried comparing targets, methods, and even converting each to the others types and then comparing.

I decided to convert the callback I pass in the same way it gets added to the last aka:

var callbackConverted = new Action<object>(m =>
                messageReceivedCallback((TMessage)m));

Next, I used the immediate window to just get some information (callback is the one in the list and callbackConverted is the one I pass in):

callback.Target
{MessageBus.MessageCoordinator.<Tests.MessageBus.TestMessage>}
    messageReceivedCallback: {Method = {Void <InitializeMessageCallback>b__0(Tests.MessageBus.TestMessage)}}

callback.Method
{Void <AddMessageReceivedCallback>b__8(System.Object)}
    [System.Reflection.RuntimeMethodInfo]: {Void <AddMessageReceivedCallback>b__8(System.Object)}
    base {System.Reflection.MethodBase}: {Void <AddMessageReceivedCallback>b__8(System.Object)}
    MemberType: Method
    ReturnParameter: {Void }
    ReturnType: {Name = "Void" FullName = "System.Void"}
    ReturnTypeCustomAttributes: {Void }


callbackConverted.Target
{MessageBus.MessageCoordinator.<Tests.MessageBus.TestMessage>}
    messageReceivedCallback: {Method = {Void <InitializeMessageCallback>b__0(Tests.MessageBus.TestMessage)}}
    messageType: {Name = "TestMessage" FullName = "Tests.MessageBus.TestMessage"}

callbackConverted.Method
    {Void <Unsubscribe>b__1d(System.Object)}
        [System.Reflection.RuntimeMethodInfo]: {Void <Unsubscribe>b__1d(System.Object)}
        base {System.Reflection.MethodBase}: {Void <Unsubscribe>b__1d(System.Object)}
        MemberType: Method
        ReturnParameter: {Void }
        ReturnType: {Name = "Void" FullName = "System.Void"}
        ReturnTypeCustomAttributes: {Void }

I hope this additional information helps.

      • **UPDATE

I have figured out that I was making this too complex. All I needed to do was key the addition of my action then remove (the sole instance of it) from each dictionary. I was going way out of my way to do something complex.

No method currently provided I can say for sure works but I am marking the one I feel others would have the best shot of using as the answer. Thank you all who contributed.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
WaffleTop
  • 157
  • 1
  • 10

3 Answers3

8

Are you talking about finding an action that does the same thing, or the exact same instance? If it's the exact same instance you can just use:

messageReceivedCallbacks[callbackType].Remove(messageCallback);

If you want to compare the method bodies, you can do something like this:

private bool ActionComparer<T>(Action<T> firstAction, Action<T> secondAction)
{
    if(firstAction.Target != secondAction.Target)
        return false;

    var firstMethodBody = firstAction.Method.GetMethodBody().GetILAsByteArray();
    var secondMethodBody = secondAction.Method.GetMethodBody().GetILAsByteArray();

    if(firstMethodBody.Length != secondMethodBody.Length)
        return false;

    for(var i = 0; i < firstMethodBody.Length; i++)
    {
        if(firstMethodBody[i] != secondMethodBody[i])
            return false;
    }
    return true;
}

Action<bool> actionOne = (param1) => {return;};
Action<bool> actionTwo = (param2) => {var i = 1; return;};
Action<bool> actionThree = (param1) => {return;};
Action<bool> actionFour = (param2) => {Thread.Sleep(1); return;};

var areEqualOneTwo = ActionComparer(actionOne, actionTwo);
var areEqualOneThree = ActionComparer(actionOne, actionThree);
var areEqualOneFour = ActionComparer(actionOne, actionFour);

Console.WriteLine("action one vs two: " + areEqualOneTwo);
Console.WriteLine("action one vs three: " + areEqualOneThree);
Console.WriteLine("action one vs four: " + areEqualOneFour);

Result:

No compiler optimisations Thanks to RenniePet's comment

action one vs two: False
action one vs three: True
action one vs four: False

With compiler optimisations

action one vs two: True
action one vs three: True
action one vs four: False

Note however, the comparison between action one and two

Community
  • 1
  • 1
Rob
  • 26,989
  • 16
  • 82
  • 98
  • I did try this but I believe it fails because of reasons other than the method you provided. I think legitimately they are different. – WaffleTop Jul 15 '11 at 15:05
  • 1
    This is nitpicking, but I'll just mention that your sample testing provides a different result for Debug and Release builds. You presumably ran your test as a Release build, and that does produce the results you show. If you run it as a Debug build then action one vs. two results in false, because the compiler keeps the useless "var i = 1;" statement in the IL. – RenniePet Nov 23 '15 at 07:59
  • @RenniePet You're absolutely right; I believe I ran this on LINQPad back when optimisations were enabled by default, and didn't pay attention. I'll edit in the results with optimisations both on and off. Thanks – Rob Nov 23 '15 at 10:21
2

Will this work?

messageReceivedCallbacks[callbackType].Remove(messageReceivedCallbacks[callbackType].FirstOrDefault(x => x.Target == messageCallback.Target && x.Method == messageCallback.Method));
Devin Garner
  • 1,361
  • 9
  • 20
0

To determine whether two delegates are the same, you only need to compare the method and the target object:

var list = messageReceivedCallbacks[callbackType];
for (var i = list.Count-1; i >= 0; i--)
    if (list[i].Method == messageCallback.Method && list[i].Target == messageCallback.Target)
        list.RemoveAt(i);
Timwi
  • 65,159
  • 33
  • 165
  • 230
  • Each delegate has one or more (target, method) pairs, you're only checking the first one. – Ben Voigt Jul 15 '11 at 14:58
  • @Ben: I know, and so does the accepted answer. I assumed the author would always use single-method delegates. – Timwi Jul 16 '11 at 11:41