CIL(MSIL)中的StringBuilder

5
我正在尝试生成代码,它接收一个 StringBuilder 并将类中所有属性的值写入字符串。我编写了以下代码,但目前遇到“Invalid method token”错误:
    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;
    }

有什么想法吗?提前感谢 :)
1个回答

5

任何值类型的属性(如int)都需要装箱;或者您需要使用不同的Append重载。

另外:

  • 每次需要加载对象(arg0)
  • StringBuilder.Append是一种流畅的API;您需要弹出该值,或者重新使用它:
  • 因此,您不需要该字段

(个人而言,我会返回一个string,但是“嘿”)

就像这样:

    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

这将生成相当于以下内容的代码:
StringBuilder ClassWriter(T obj) {
    return new StringBuilder.Append((object)obj.Foo).Append((object)obj.Bar)
                   .Append((object)obj.Blip).Append((object)obj.Blap);
}

啊,谢谢,这是一个超级好的解释!我明白你所说的装箱,我太习惯编译器自动解决正确的重载调用了。我不确定你所说的Append是一种流畅的API是什么意思,这是否意味着被附加的值没有从堆栈中消耗?Ldarg_0从哪里获取它的输入?抱歉问了这么多问题 xD - Josh
2
通过流畅,我的意思是Append不返回void - 它返回this; 你可以调用.Append(...).Append(...).Append(...)等。每次调用后,你都会在堆栈上留下一个值。arg0是输入参数(因为这是静态方法)。对于实例方法,arg0是“this”。 - Marc Gravell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接