4

I need to generate code using Expression trees that quickly fills out an array of structs T[] where T contains a readonly field. I need to initialize it like after GetUninitializedObject() + IL or reflection-based setters.

UPDATE: At the moment it appears to be impossible. Please vote for it at MS Suggestions

struct Strct
{
    public readonly int Value;
}

this code fails:

Expression.Assign(
    Expression.Field(structByIndexFromArrayExp, "Value"),
    deserializedValueExp)

During the expression tree construction, I get this error: Expression must be writeable Which totally makes sense from the regular code perspective, but not during deserialization.

FormatterServices.GetUninitializedObject() returns an object, which I would guess I need to avoid as it is boxed and therefore significantly slower.

What is the quickest way to initialize such struct arrays?

Update: At the moment the only realistic way I see is to dynamically generate a clone of struct T but without readonly attribute on fields, fill them out, fix both arrays in memory and do a memory copying. Please vote to tell Microsoft to fix it.

Yuri Astrakhan
  • 8,808
  • 6
  • 63
  • 97

2 Answers2

6

Just because you're deserializing doesn't mean you get to break the rules of the language. The compiler complains if I try this:

void Main()
{
    var a = new Foo{Bar = 1};
}

public struct Foo
{
    public readonly int Bar;
}

Expression trees can't be expected to perform actions that you can't perform in code. If the property shouldn't really be readonly, remove the readonly keyword. Otherwise, you should have a constructor that allows you to initialize it.

public struct Foo
{
    public Foo(int bar) {this.Bar = bar;}
    public readonly int Bar;
}

Then create an expression that calls that constructor rather than trying to set the field directly.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • 1
    I agree about the language, but Expressions are closer to IL generation and reflection, both of which would allow me to perform such operations. GetUninitializedObject() is specifically designed especially for these cases in the object world. – Yuri Astrakhan Mar 29 '12 at 19:17
  • 1
    @Yurik: Good point. However, note Eric Lippert's comment on this post: http://stackoverflow.com/a/934944/120955. While reflection technically allows you to do this, there is nothing in the specs that says all CLR implementations need to allow this. You'd be relying on unspecified behavior. Expressions tread the middle-ground between actual languages and compiled code. In this case, they behave more like the language and less like pure IL. – StriplingWarrior Mar 29 '12 at 20:45
  • Microsoft itself allows readonly field serialization, therefor it is legal http://msdn.microsoft.com/en-us/library/ms731923.aspx. The DataContractSerializer does it via IL generation, but Expressions should offer a viable alternative. But thanks for a thorough answer. – Yuri Astrakhan Mar 30 '12 at 00:49
2

There actually is a workaround since you can use Expressions to call reflection methods. Only be aware that this is a lot slower.

public static Expression CreateSetValueExpression(Expression target, Expression value, FieldInfo fieldInfo)
{
    // workaround for readonly fields: use reflection, this is a lot slower but the only way except using il directly
    if (fieldInfo.IsInitOnly)
    {
        MethodInfo fieldInfoSetValueMethod = typeof(FieldInfo).GetMethod("SetValue", new[] { typeof(object), typeof(object) }); 
        return Expression.Call(Expression.Constant(fieldInfo), fieldInfoSetValueMethod, target, Expression.Convert(value, typeof(object)));
    }

    return Expression.Assign(Expression.Field(target, fieldInfo), value);
}
Michael Sander
  • 2,677
  • 23
  • 29