Compare Delegates Action

2020-08-16 16:33发布

问题:

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.

回答1:

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



回答2:

Will this work?

messageReceivedCallbacks[callbackType].Remove(messageReceivedCallbacks[callbackType].FirstOrDefault(x => x.Target == messageCallback.Target && x.Method == messageCallback.Method));


回答3:

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