0

As the title says, using reflection I get methods of the type that have a given attribute. I then want to store that method of that instance of the object so I can call it at a later time. I'm not sure how to store that method with that instance in a dictionary that holds Action. The message is the key use in the dictionary. All the methods that have this attribute will be required to take 1 argument of dynamic type.

static Dictionary<string, Action<dynamic>> networkHooks = new Dictionary<string, Action<dynamic>>();

private static void RegisterNetworkMethods(Type type, INetworkEnabled obj)
{
    // get the methods of this type that have the NetworkMethodAttribute
    var methods = (from m in type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
                       where m.GetCustomAttributes(false).OfType<NetworkMethodAttribute>().Count() > 0
                       select m).ToList();

    foreach(var method in methods)
    {
        // get the NetworkMethodAttribute Message variable that was assigned
        var message = method.GetCustomAttribute<NetworkMethodAttribute>().Message;

        // todo: store this instance method in an Action somehow so it can be called later
        // networkHooks.Add(message, ???);
    }
}
nalka
  • 1,894
  • 11
  • 26
user441521
  • 6,942
  • 23
  • 88
  • 160
  • It looks like you're on track... you just need to convert from `MethodInfo method` to an `Action @delegate` and store it... Here is a link to something that should help. https://stackoverflow.com/questions/3021578/how-can-i-create-an-action-delegate-from-methodinfo – Trae Moore Jan 17 '20 at 13:58
  • Are you using `Action` because the method could take any type of parameter? – nalka Jan 17 '20 at 14:16

2 Answers2

2

If you're using Action<dynamic> because the methods could take any kind of parameter, you could use this version to make it work. You can then call DynamicInvoke(param) on the delegates stored in the Dictionary to call the methods

static Dictionary<string, Delegate> networkHooks = new Dictionary<string, Delegate>();

private static void RegisterNetworkMethods(Type type, object target)
{
    // get the methods of this type that have the NetworkMethodAttribute
    var methods = (from m in type.GetMethods(NonPublic | Instance)
                   where m.GetCustomAttributes(false).OfType<NetworkMethodAttribute>().Count() > 0
                   select m).ToList();

    foreach (var method in methods)
    {
        // get the NetworkMethodAttribute Message variable that was assigned
        var message = method.GetCustomAttribute<NetworkMethodAttribute>().Message;

        // todo: store this instance method in an Action somehow so it can be called later
        networkHooks.Add(message, Delegate.CreateDelegate(typeof(Action<Anything>).GetGenericTypeDefinition().MakeGenericType(method.GetParameters()[0].ParameterType), target, method.Name));
    }
}

Side note : .OfType<NetworkMethodAttribute>().Count() > 0 can be changed to .OfType<NetworkMethodAttribute>().Any(), it's faster

nalka
  • 1,894
  • 11
  • 26
  • How does this know the object instance to call on? – user441521 Jan 17 '20 at 14:45
  • Also it seems to be missing using the object instance. I assume perhaps you think I'll create these objects at some other point later, but I create the objects before I run this code to store their function delegates. – user441521 Jan 17 '20 at 14:49
  • 1
    @user441521 do you have a reference to these objects when you call `RegisterNetworkMethods`? – nalka Jan 17 '20 at 14:54
  • Yes, I create and manage all those object instances at the time of calling RegisterNetworkMethods. This is for a game so these class instances are doing a bunch of other stuff while the game is running. They just need a way to know when we get a message from the server so they can update their state internally with that data for the message. – user441521 Jan 17 '20 at 15:11
  • @user441521 Alright, i'm editing my answer according to this, can you review my edit on your question? – nalka Jan 17 '20 at 15:17
  • Yep it looks good. Your way of doing this is interesting. So this allows for the object method to have any 1 parameter type? Right now since I'm using dynamic there is of course a potential run-time issue that could happen if I reference a variable on the parameter that isn't really defined. Am I correct in thinking that with the Anything idea the args to these methods could be actual types? I'm making a link between the message id and an argument type right now but I just send it along as dynamic. It would be nice when I call the method to send the actual type along. – user441521 Jan 17 '20 at 15:25
  • How would you add multiple methods to the delegate? += isn't defined on delegate. I know in my example I just use .Add() but that only adds one per message. The reality of what I do is if the key already exists I then += to add more than one if needed. I want more than one instance method to be able to be raised for a given message. With Action this works fine, but with Delegate it's not allowing +=. Do I have to store it as a list of delegates then? – user441521 Jan 17 '20 at 15:36
  • Also where are you getting that Anything from? When I do that it's not coming up as valid type and not giving me suggestions of missing a using statement or anything. – user441521 Jan 17 '20 at 15:40
  • @user441521 Yes, this allows to register methods with any type of parameter, as long as they only have one parameter. += isn't what, you're looking for, it's reserved to events. As you said, you can use a `List` instead. I wrote `typeof(Action)` because it doesn't matter what type takes `Anything`'s place as we "replace" it with the method's first parameter type by doing `.GetGenericTypeDefinition().MakeGenericType(method.GetParameters()[0].ParameterType)` after it – nalka Jan 17 '20 at 15:50
  • I replaced Anything with object and now have this working. This gives me type safe compile time args which is really nice! I changed message from string to enum/int. I then create classes for each message type and store it's type in a dictionary where the message enum is the key. When a message comes in I convert that int to enum, then lookup the arg type that I stored and dynamically create that arg type at the time of raising the delegate. I Convert.CHangeType() from the dynamically created arg type to the type stored and it works! C# is insane sometimes. – user441521 Jan 17 '20 at 16:01
  • Perfect, if your whole problem is solved you can accept (and edit if needed) the answer – nalka Jan 17 '20 at 16:38
1

Something like this should move you in the right direction.

    private static void RegisterNetworkMethods(Type type, INetworkEnabled obj)
    {
        // get the methods of this type that have the NetworkMethodAttribute
        var methods = (from m in type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
                       where m.GetCustomAttributes(false).OfType<NetworkMethodAttribute>().Count() > 0
                       select m).ToList();

        foreach(var method in methods)
        {
            // get the NetworkMethodAttribute Message variable that was assigned
            var message = method.GetCustomAttribute<NetworkMethodAttribute>().Message;

            var action = (Action<dynamic>) Delegate.CreateDelegate(typeof(Action<dynamic>), method);

            networkHooks.Add(message, action);
        }
    }
Trae Moore
  • 1,759
  • 3
  • 17
  • 32
  • This is missing passing the obj to CreateDelegate(). I got that from the link you posted in the comment. – user441521 Jan 17 '20 at 14:47
  • You will have to pass the object instance to the static method. Do understand, when you reference a non-static delegate on the class instance, you're keeping an instance reference as a long-lived object... meaning until the delegate is no longer used and removed from the collection, your instance references will still exist on the heap... what I'm actually trying to convey, is to make sure you clean up after yourself if you're going to use class instance references. – Trae Moore Jan 17 '20 at 15:01
  • 1
    Yep for sure. I manage all the object instances for this. No worries on that end. This is for a game and the object instances in question are doing a bunch of other stuff as well for the game, they just need to know when certain network messages from the server came in to updated their internal states with the data the server sends. I had all of that centralized in 1 file but it's getting way to unmanageable and is only going to grow. So I wanted to do this register style instead so each class instance can get informed of a server msg and data and manage itself. – user441521 Jan 17 '20 at 15:10