How does protobuf-net handle readonly fields?

2020-03-02 04:55发布

问题:

I use protobuf-net to serialize/deserialize my data.

I have some rather simple classes, so that's no real problem.

As far as I know, protobuf-net uses IL generation to create serialization/deserialization code. While I have readonly fields in my model, I wonder how is it possible to write to such a field with IL? I can plainly see it works well, but I don't know why...

I've tried to spy it in the code, but it's a bit too complicated.

My attempts to generate such code myself always result in IL validator errors.

回答1:

Actually, I can't get it to fail - at least, when generating in memory.

Let's start simply, with a public readonly field (so we aren't breaking any accessebility rules); my first attempt is as below, and it works fine:

using System;
using System.Reflection;
using System.Reflection.Emit;
class Foo
{
    public readonly int i;
    public int I { get { return i; } }
    public Foo(int i) { this.i = i; }
}
static class Program
{
    static void Main()
    {
        var setter = CreateWriteAnyInt32Field(typeof(Foo), "i");
        var foo = new Foo(123);
        setter(foo, 42);
        Console.WriteLine(foo.I); // 42;
    }
    static Action<object, int> CreateWriteAnyInt32Field(Type type, string fieldName)
    {
        var field = type.GetField(fieldName,
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
        var method = new DynamicMethod("evil", null,
            new[] { typeof(object), typeof(int) });
        var il = method.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Castclass, type);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Stfld, field);
        il.Emit(OpCodes.Ret);
        return (Action<object, int>)method.CreateDelegate(typeof(Action<object, int>));
    }
}

The only time it gets interesting is if the field is private:

private readonly int i;

The code above then gives the oh-so-vague:

Operation could destabilize the runtime.

But we get around that by pretending that the method is inside the field's declaring type:

var method = new DynamicMethod("evil", null,
    new[] { typeof(object), typeof(int) }, field.DeclaringType);

Some other internal checks can be done by enabling skipVisibility:

var method = new DynamicMethod("evil", null,
    new[] { typeof(object), typeof(int) }, field.DeclaringType, true);

However, note that not all of this is possible if generating standalone assemblies. You are held to much higher standards when creating actual dlls. For this reason, the precompiler tool (to pre-generate assemblies) cannot handle quite the same range of scenarios that the in-memory meta-programming code can.



回答2:

As I am quite interested in this discussion, I have tried Marc Gravell's example code and... it throws VerificationException on MS .NET 4.0.

I've managed to make it work but I needed to use DynamicMethod constructor with owner parameter set to field.DeclaringType even in case of public i field. SkipVisibility parameter seems to be redundant in this case.

PS. I believe that this entry should be a comment, but due to the lack of rep I'm unable to comment other's answers.