Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

"Automatically" take care of casting and parameters during reflected Invoke?

Discussion in 'Scripting' started by qwertyuiop11110, Mar 17, 2020.

  1. qwertyuiop11110

    qwertyuiop11110

    Joined:
    Mar 16, 2020
    Posts:
    24
    I have following snippet:
    Code (CSharp):
    1. public static class testing
    2. {
    3.  
    4.     public static void Start()
    5.     {
    6.         Inwok("SquareIt", 5);
    7.     }
    8.  
    9.  
    10.     public static void SquareIt(int numToSquare)
    11.     {
    12.         // I could "int numToSquare = (int)myArray[0];", but I want to avoid that
    13.         Debug.Log(numToSquare);
    14.     }
    15.  
    16.  
    17.     public static void Inwok(string invokeFunction, params object[] data)
    18.     {
    19.         MethodInfo mi = typeof(testing).GetMethod(invokeFunction);
    20.         mi.Invoke(null, new dynamic[] { data });
    21.     }
    22. }
    It would be highly beneficiary if I could instantly resolve SquareIt's parameters. I could cast it from
    params object[] myParams 
    , but it would be easier to read the code, if I could get
    mi.Invoke
    to resolve variables to assign and cast variables and then put them through. So first input item in the array is casted as the first item that method requires, second input item in the array, is casted as second variable. Otherwise throw
    InvalidCastException
    . Is that achievable?

    Edit: Start() is invoked by another script without params.
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,385
    Technically you can. You get call 'GetParameters' on the MethodInfo to find out what parameter types it takes:
    https://docs.microsoft.com/en-us/do...ethodbase.getparameters?view=netframework-4.8

    As for casting/converting... this can get a little complicated. Lets cover some of them...

    1) class types - if they don't match they don't match, you can't convert them. And if they do match, there's no casting necessary.

    2) numeric types - (enums included) you can easily convert numeric types. Here's a method I had laying around that does this:
    Code (csharp):
    1.  
    2.         private static bool ParameterSignatureMatchesNumericallyUnstrict(object[] args, ParameterInfo[] paramInfos, bool allowOptional, bool convertArgsOnSuccess)
    3.         {
    4.             if (args == null) args = ArrayUtil.Empty<object>();
    5.             if (paramInfos == null) ArrayUtil.Empty<ParameterInfo>();
    6.  
    7.             if (args.Length == 0 && paramInfos.Length == 0) return true;
    8.             if (args.Length > paramInfos.Length) return false;
    9.  
    10.             for (int i = 0; i < args.Length; i++)
    11.             {
    12.                 if (args[i] == null)
    13.                 {
    14.                     continue;
    15.                 }
    16.                 var atp = args[i].GetType();
    17.                 if (atp.IsAssignableFrom(paramInfos[i].ParameterType))
    18.                 {
    19.                     continue;
    20.                 }
    21.                 if(ConvertUtil.IsNumericType(atp) && ConvertUtil.IsNumericType(paramInfos[i].ParameterType))
    22.                 {
    23.                     continue;
    24.                 }
    25.  
    26.                 return false;
    27.             }
    28.  
    29.             if( paramInfos.Length == args.Length || (allowOptional && paramInfos[args.Length].IsOptional))
    30.             {
    31.                 if(convertArgsOnSuccess)
    32.                 {
    33.                     for (int i = 0; i < args.Length; i++)
    34.                     {
    35.                         if (args[i] == null)
    36.                         {
    37.                             continue;
    38.                         }
    39.                         var atp = args[i].GetType();
    40.                         if (atp.IsAssignableFrom(paramInfos[i].ParameterType))
    41.                         {
    42.                             continue;
    43.                         }
    44.                         if (ConvertUtil.IsNumericType(atp) && ConvertUtil.IsNumericType(paramInfos[i].ParameterType))
    45.                         {
    46.                             args[i] = ConvertUtil.ToPrim(args[i], paramInfos[i].ParameterType);
    47.                         }
    48.                     }
    49.                 }
    50.                 return true;
    51.             }
    52.             else
    53.             {
    54.                 return false;
    55.             }
    56.         }
    57.  
    I use it in my DynamicUtil class to validate the signature of a method and allow converting numeric types to coerce numeric data into a method if the types don't precisely match. It does rely on ConvertUtil.IsNumericType and ConvertUtil.ToPrim for the conversion which is here:
    https://github.com/lordofduct/space...uppyUnityFramework/Utils/ConvertUtil.cs#L2386

    Though you could write your own conversion code that meets your needs.

    3) value types - structs, these can get a little more complicated in the amount of heavy lifting you would have to do. You'd have to make decisions here... like are you going to search the type for an explicit/implicit conversion method defined on it? This is your easiest first step. But what if one doesn't exist? Are you going to do any heavier lifting like coercing types?
     
    qwertyuiop11110 likes this.
  3. qwertyuiop11110

    qwertyuiop11110

    Joined:
    Mar 16, 2020
    Posts:
    24
    My proposed solution:
    Code (CSharp):
    1.         public static void Inwok(string invokeFunction, params object[] data)
    2.         {
    3.             int i = 0;
    4.             MethodInfo mi = typeof(testing).GetMethod(invokeFunction);
    5.             List<dynamic> myInput = new List<dynamic>();
    6.             ParameterInfo[] pinfo = mi.GetParameters();
    7.  
    8.  
    9.             // For every method parameter, cast an item from object array into new casted array
    10.             foreach (var pinfo_ in pinfo) {
    11.                 dynamic parsedValue = Convert.ChangeType(data[i], pinfo_.ParameterType);
    12.                 myInput.Add(parsedValue);
    13.                 i++;
    14.             }
    15.  
    16.             mi.Invoke(null, myInput.ToArray());
    17.         }
    Is there something that I should keep in mind? Is there any predictable shortcoming from that snippet?
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,385
    This will throw exceptions when Convert fails, but you wanted that in your original description, so it'll work just fine to that effect.

    I would argue you're creating a lot of collections unnecessarily. You also didn't pass in the object you're invoking against. Lastly why not take in the target object as a parameter? You could do something like this instead:
    Code (csharp):
    1.  
    2. public static void Inwok(object target, string methodName, params object[] parameters)
    3. {
    4.     var mi = target.GetType().GetMethod(methodName);
    5.     if(mi == null) throw new ArgumentException("No method named '" + methodName + "' found on target.");
    6.     var pinfos = mi.GetParameters();
    7.     if(parameters.Length != pinfos.Length) throw new ArgumentException("Argument count mismatch.");
    8.  
    9.     for(int i = 0; i < pinfos.Length; i++)
    10.     {
    11.         if(!pinfos[i].ParameterType.IsInstanceOfType(parameters[i]))
    12.             parameters[i] = Convert.ChangeType(parameters[i], pinfos[i].ParameterType);
    13.     }
    14.  
    15.     mi.Invoke(target, parameters);
    16. }
    17.  
    Then again, this might be because you were trying to call a static method. Which I guess makes sense. In which case you can take the static method's class type as a generic parameter:
    Code (csharp):
    1.  
    2.         public static void Inwok<T>(T target, string methodName, params object[] parameters)
    3.         {
    4.             var mi = typeof(T).GetMethod(methodName);
    5.             var pinfos = mi.GetParameters();
    6.             if (parameters.Length != pinfos.Length) throw new ArgumentException("Argument count mismatch.");
    7.  
    8.             for (int i = 0; i < pinfos.Length; i++)
    9.             {
    10.                 if (!pinfos[i].ParameterType.IsInstanceOfType(parameters[i]))
    11.                     parameters[i] = Convert.ChangeType(parameters[i], pinfos[i].ParameterType);
    12.             }
    13.  
    14.             mi.Invoke(target, parameters);
    15.         }
    16.  
    (passing in null as target is ok in this version)
     
    Last edited: Mar 18, 2020
  5. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    I don't see any benefit in doing this, other than unnecessarily coercing the types, instead of letting them unbox themselves.

    This can only diminish the normal performance of Invoke. And Invoke is a silly thing and shouldn't be used for frequent calls or as a substitute for Action and Func delegates.

    It's ok only for events, and occasionally with custom delegates, as part of some callback mechanism.

    I was under the impression, from your previous thread, that you wanted a method reflection that performed well, but you either didn't understand my answer there, or you didn't (want to) see it. So here I'm reminding you to consider that solution once again. If you have any questions, ask away.

    Also there is no need to use dynamic.

    Code (csharp):
    1. public static void Inwok(string invokeFunction, params object[] data) {
    2.    MethodInfo mi = typeof(testing).GetMethod(invokeFunction);
    3.    mi.Invoke(null, new object[] { data }); // this is all you need to do, it will be unboxed automatically
    4. }
    5.  
    6. // usage
    7. Debug.Log(Inwok("SquareIt", 5));
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,385
    Oh, there's another thread in relation to this? And they expect good performance?

    Yeah... none of this is going to perform well.
     
  7. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Yeah, well, at least that's how I understood it.
    https://forum.unity.com/threads/when-does-reflection-penalty-matter.848290/

    There I showed him a solution in the other thread where I create a delegate on the fly and assign it to a prepared field, similar to having an abstract method. And there are ways to make these fields set up dynamically as well, letting one build entire proxy interfaces and dynamic telegraphs at runtime. No invoke is ever needed.
     
    Last edited: Mar 18, 2020