Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does protobuf-net handle readonly fields?

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.

like image 764
Piotr Zierhoffer Avatar asked Jun 14 '13 13:06

Piotr Zierhoffer


1 Answers

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.

like image 173
Marc Gravell Avatar answered Oct 08 '22 04:10

Marc Gravell