7

For serialization purpose, we are trying to generate the delegate to update some object property values on the fly and store them into a list for further use. Everything works quite well as long as we do not try to deserialize structs.

We based our code on this article about open delegate: http://codeblog.jonskeet.uk/2008/08/09/making-reflection-fly-and-exploring-delegates/

And here is our code to deal with property setter in a class based object.

    private static System.Action<object, object> ToOpenActionDelegate<T, TParam>(System.Reflection.MethodInfo methodInfo) where T : class
    {
        System.Type parameterType = typeof(TParam);

        // Convert the slow MethodInfo into a fast, strongly typed, open delegate
        System.Action<T, TParam> action = (System.Action<T, TParam>)System.Delegate.CreateDelegate(typeof(System.Action<T, TParam>), methodInfo);

        // Convert the strong typed delegate into some object delegate!
        System.Action<object, object> ret = (object target, object param) => action(target as T, (TParam)System.Convert.ChangeType(param, parameterType));

        return ret;
    }

As you have guessed, it does not work with struct. I found this post talking about how to deal with open delegate in struct: How can I create an open Delegate from a struct's instance method? (Actually, I found way more post than this one, but this one has a "simple" solution which does not use IL code generation for example...)

But, for now, everytime I try to bind a methodinfo of a property setter to a delegate using a ref parameter, I get an exception. Here is the current code I use:

    public delegate void RefAction<T, TParam>(ref T arg, TParam param) where T : class;
    private static RefAction<object, object> ToOpenActionDelegate<T, TParam>(System.Reflection.MethodInfo methodInfo) where T : class
    {
        // Convert the slow MethodInfo into a fast, strongly typed, open delegate
        System.Type objectType = typeof(T);
        System.Type parameterType = typeof(TParam);
        RefAction<object, object> ret;
        if (objectType.IsValueType)
        {
            RefAction<T, TParam> propertySetter = (RefAction<T, TParam>)System.Delegate.CreateDelegate(typeof(RefAction<T, TParam>), methodInfo);

            // we are trying to set some struct internal value.
            ret = (ref object target, object param) =>
            {
                T boxed = (T)target;
                propertySetter(ref boxed, (TParam)System.Convert.ChangeType(param, parameterType));
                target = boxed;
            };
        }
        else
        {
            System.Action<T, TParam> action = (System.Action<T, TParam>)System.Delegate.CreateDelegate(typeof(System.Action<T, TParam>), methodInfo);
            ret = (ref object target, object param) => action(target as T, (TParam)System.Convert.ChangeType(param, parameterType));
        }

        return ret;
    }

The problem appear when executing the following line:

RefAction<T, TParam> propertySetter = (RefAction<T, TParam>)System.Delegate.CreateDelegate(typeof(RefAction<T, TParam>), methodInfo);

Which is, at least for me, really the same as the one used in the linked post above:

SomeMethodHandler d = (SomeMethodHandler)Delegate.CreateDelegate(typeof(SomeMethodHandler), method);

Where:

delegate int SomeMethodHandler(ref A instance);
public struct A
{
    private int _Value;

    public int Value
    {
        get { return _Value; }
        set { _Value = value; }
    }

    private int SomeMethod()
    {
         return _Value;
    }
}

Anybody has an idea about why does it generate an exception on my side and not in the linked thread? Is it linked to the C# runtime version? I am working on unity so it's a mono framework almost equivalent to 3.5...

Anyway, thanks for reading and do not hesitate if I am doing something wrong in the question layout or syntaxe!

Cheers, flo.

Community
  • 1
  • 1
Thor Tillas
  • 115
  • 7

1 Answers1

2

You're creating a delegate to a static method instead of an open delegate. I added null to your CreateDelegate calls and it worked (I am not so sure about the performance, though, with double boxing/unboxing):

public struct S
{
    public string Value {get; set;}
}

static class Program
{   

    public delegate void RefAction<T, TParam>(ref T arg, TParam param);
    static RefAction<object, object> ToOpenActionDelegate<T, TParam>(System.Reflection.MethodInfo methodInfo)
    {
        // Convert the slow MethodInfo into a fast, strongly typed, open delegate
        Type objectType = typeof(T);
        Type parameterType = typeof(TParam);
        RefAction<object, object> ret;
        if (objectType.IsValueType)
        {
            RefAction<T, TParam> propertySetter = (RefAction<T, TParam>)Delegate.CreateDelegate(typeof(RefAction<T, TParam>), null, methodInfo);

            // we are trying to set some struct internal value.
            ret = (ref object target, object param) =>
            {
                T boxed = (T)target;
                propertySetter(ref boxed, (TParam)System.Convert.ChangeType(param, parameterType));
                target = boxed;
            };
        }
        else
        {
            Action<T, TParam> action = (Action<T, TParam>)Delegate.CreateDelegate(typeof(Action<T, TParam>), null, methodInfo);
            ret = (ref object target, object param) => action((T)target, (TParam)System.Convert.ChangeType(param, parameterType));
        }

        return ret;
    }

    public static void Main(string[] args)
    {
        var s = new S();

        var mi = s.GetType().GetMethod("set_Value");
        /*
        var deleg = (RefAction<S, string>)Delegate.CreateDelegate(typeof(RefAction<S, string>), null, mi);

        deleg(ref s, "hello");

        RefAction<object, object> deleg2 = (ref object target, object param) => {
            S boxed = (S)target;
            deleg(ref boxed, (string)param);
            target = boxed;
        };
        */

        RefAction<object, object> deleg2 = ToOpenActionDelegate<S, string>(mi);

        var o = (object)s;

        deleg2(ref o, "world");

        s = (S)o;

        Console.WriteLine(s.Value); //prints "world"

        Console.ReadKey(true);
    }
}
Alexey
  • 1,354
  • 13
  • 30
  • Thanks for the answer. I did try your code and it work. But it still doesn't when I using it in my application. I will try to push further before coming back, but still wanted to thank you for bringing hope to me ! Cheers! flo – Thor Tillas Jan 12 '16 at 13:56
  • Ok, I took the time to copy past the full production code in a new Visual project and everything work like a charm. Even in "Monodevelop" it's working. But once I do exactly the same "TestProject" in Unity it crash because "method arguments are incompatible". I will report this to Unity and hope for the best. Any idea about a workaround? – Thor Tillas Jan 12 '16 at 14:32
  • @ThorTillas No idea. Unity uses a rather old version of Mono, so I have no idea what might be wrong, especially since your code jumps through so many hoops. – Alexey Jan 12 '16 at 14:43
  • The Mono bug is still present in Unity. It's been reported in May 2011 [here](http://lists.ximian.com/pipermail/mono-bugs/2011-May/111963.html). You can see a Mono unit test file [here](https://searchcode.com/codesearch/view/51492977/) which references the bug number (695978), but it's tagged as "Not Working". The [BugZilla page](https://bugzilla.novell.com/show_bug.cgi?id=695978) marks the bug as confirmed, but not fixed. It seems the issue is dead, sadly. – Lazlo Apr 06 '17 at 04:52