VerificationException when setting readonly fields (C#)

117 Views Asked by At

I am trying to write code that can set arbitrary fields on caller-provided objects which may include anonymous ones. Creating delegates is not possible (Expression compiler realizes that anonymous objects' fields are readonly), so I chose to emit some IL. However, when doing this I am running into VerificationException ("Operation could destabilize he runtime"). The same simple code runs just fine on objects with regular fields. Fails on readonly fields. What else could be done here? I am running .Net 4.6.2.

Thanks in advance!

class TestRegular
{
    private string field;
}

class TestReadOnly
{
    private readonly string field;
}

class Program
{
    static void Main(string[] args)
    {
        Verify(new TestRegular());     // this works
        Verify(new TestReadOnly());    // this does not work
        Verify(new { field = "abc" }); // this does not work
        Console.WriteLine("Done");
    }

    private static void Verify<T>(T test)
    {
        var fields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
        Action <T, object> setter = CompileSetter<T>(fields[0]);
        setter(test, "value");
    }

    private static Action<TResult, object> CompileSetter<TResult>(FieldInfo field)
    {
        string methodName = field.ReflectedType.FullName + ".TestSetter";
        DynamicMethod setterMethod = new DynamicMethod(methodName, null, new[] { typeof(TResult), typeof(object) }, true);
        ILGenerator gen = setterMethod.GetILGenerator();

        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Ldarg_1);
        gen.Emit(OpCodes.Castclass, field.FieldType);
        gen.Emit(OpCodes.Stfld, field);

        gen.Emit(OpCodes.Ret);
        return (Action<TResult, object>)setterMethod.CreateDelegate(typeof(Action<TResult, object>));
    }
}
1

There are 1 best solutions below

0
On

What you are seeing is absolutely in line with the specs. Have a look at the ECMA-335 Section II.16.1.2:

initonly marks fields which are constant after they are initialized. These fields shall only be mutated inside a constructor. If the field is a static field, then it shall be mutated only inside the type initializer of the type in which it was declared. If it is an instance field, then it shall be mutated only in one of the instance constructors of the type in which it was defined. It shall not be mutated in any other method or in any other constructor, including constructors of derived classes.

[ Note: The use of ldflda or ldsflda on an initonly field makes code unverifiable. In unverifiable code, the VES need not check whether initonly fields are mutated outside the constructors. The VES need not report any errors if a method changes the value of a constant. However, such code is not valid. end note ]

To handle this case gracefully you can use the FieldInfo.IsInitOnly Property.