views:

132

answers:

1

Hey there,

I'm trying to generate code that takes a StringBuilder, and writes the values of all the properties in a class to a string. I've got the following, but I'm currently getting a "Invalid method token" in the following code:

    public static DynamicAccessor<T> CreateWriter(T target) //Target class to *serialize*
    {
        DynamicAccessor<T> dynAccessor = new DynamicAccessor<T>();

        MethodInfo AppendMethod = typeof(StringBuilder).GetMethod("Append", new[] { typeof(Object) }); //Append method of Stringbuilder

        var method = new DynamicMethod("ClassWriter", typeof(StringBuilder), new[] { typeof(T) }, typeof(T), true);
        var generator = method.GetILGenerator();
        LocalBuilder sb = generator.DeclareLocal(typeof(StringBuilder)); //sb pointer


        generator.Emit(OpCodes.Newobj, typeof(StringBuilder)); //make our string builder 
        generator.Emit(OpCodes.Stloc, sb);                     //make a pointer to our new sb


        //iterate through all the instance of T's props and sb.Append their values.
        PropertyInfo[] props = typeof(T).GetProperties();
        foreach (var info in props)
        {
            generator.Emit(OpCodes.Callvirt, info.GetGetMethod()); //call the Getter
            generator.Emit(OpCodes.Ldloc, sb);                     //load the sb pointer
            generator.Emit(OpCodes.Callvirt, AppendMethod);        //Call Append 
        }

        generator.Emit(OpCodes.Ldloc, sb);
        generator.Emit(OpCodes.Ret);           //return pointer to sb

        dynAccessor.WriteHandler = method.CreateDelegate(typeof(Write)) as Write;
        return dynAccessor;
    }

Any ideas? Thanks in advance :)

+3  A: 

Any properties that are value-types (int etc) will need boxing; or you will need to use a different Append overload.

Also:

  • you need to load the object each time (arg0)
  • StringBuilder.Append is a fluent API; you either need to pop the value, or re-use it:
  • as a consequence, you don't need the field

(personally, though, I'd return a string, but "meh")

Like so:

    DynamicAccessor<T> dynAccessor = new DynamicAccessor<T>();
    MethodInfo AppendMethod = typeof(StringBuilder).GetMethod("Append", new[] { typeof(Object) }); //Append method of Stringbuilder

    var method = new DynamicMethod("ClassWriter", typeof(StringBuilder), new[] { typeof(T) }, typeof(T), true);
    var generator = method.GetILGenerator();
    generator.Emit(OpCodes.Newobj, typeof(StringBuilder).GetConstructor(Type.EmptyTypes)); //make our string builder 
    //iterate through all the instance of T's props and sb.Append their values.
    PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
    foreach (var info in props)
    {   
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Callvirt, info.GetGetMethod()); //call the Getter
        if (info.PropertyType.IsValueType)
        {
            generator.Emit(OpCodes.Box, info.PropertyType);
        }
        generator.Emit(OpCodes.Callvirt, AppendMethod);        //Call Append 
    }
    generator.Emit(OpCodes.Ret);           //return pointer to sb

This generates the equivalent to:

StringBuilder ClassWriter(T obj) {
    return new StringBuilder.Append((object)obj.Foo).Append((object)obj.Bar)
                   .Append((object)obj.Blip).Append((object)obj.Blap);
}
Marc Gravell
+1, very nicely explained.
Darin Dimitrov
Ah thanks, this is a super explanation!I see what you mean by the boxing, I'm so use to the compiler automatically resolving the correct overload to call.I'm not sure what you mean by Append is a fluent API, does that mean that the value that's being appended isn't being consumed from the stack? And where does Ldarg_0 get it's input from?Sorry for all the questions xD
Fauxide
by fluent, I mean that Append doesn't return `void` - it returns `this`; you call `.Append(...).Append(...).Append(...)` etc. You were leaving a value on the stack after each call. `arg0` is the input parameter (since this is a static method). For an instance method, `arg0` is "this".
Marc Gravell
I see, thanks for the help =)
Fauxide