Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to initialize a struct in array with readonly values using Expression Trees in C#

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.

like image 822
Yuri Astrakhan Avatar asked Feb 22 '23 00:02

Yuri Astrakhan


2 Answers

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.

like image 139
StriplingWarrior Avatar answered Feb 23 '23 21:02

StriplingWarrior


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);
}
like image 43
Michael Sander Avatar answered Feb 23 '23 21:02

Michael Sander