Let's say I have the following code which update a field of a struct
using reflection. Since the struct instance is copied into the DynamicUpdate
method, it needs to be boxed to an object before being passed.
struct Person
{
public int id;
}
class Test
{
static void Main()
{
object person = RuntimeHelpers.GetObjectValue(new Person());
DynamicUpdate(person);
Console.WriteLine(((Person)person).id); // print 10
}
private static void DynamicUpdate(object o)
{
FieldInfo field = typeof(Person).GetField("id");
field.SetValue(o, 10);
}
}
The code works fine. Now, let's say I don't want to use reflection because it's slow. Instead, I want to generate some CIL directly modifying the id
field and convert that CIL into a reusable delegate (say, using Dynamic Method feature). Specially, I want to replace the above code with s/t like this:
static void Main()
{
var action = CreateSetIdDelegate(typeof(Person));
object person = RuntimeHelpers.GetObjectValue(new Person());
action(person, 10);
Console.WriteLine(((Person)person).id); // print 10
}
private static Action<object, object> CreateSetIdDelegate(Type t)
{
// build dynamic method and return delegate
}
My question: is there any way to implement CreateSetIdDelegate
excepts from using one of the following techniques?
Action<object, object>
, use a custom delegate whose signature is public delegate void Setter(ref object target, object value)
.Action<object, object>
, use Action<object[], object>
with the 1st element of the array being the target object. The reason I don't like 2 & 3 is because I don't want to have different delegates for the setter of object and setter of struct (as well as not wanting to make the set-object-field delegate more complicated than necessary, e.g. Action<object, object>
). I reckon that the implementation of CreateSetIdDelegate
would generate different CIL depending whether the target type is struct or object, but I want it to return the same delegate offering the same API to user.
EDIT again: This works structs now.
There's a gorgeous way to do it in C# 4, but you'll have to write your own ILGenerator
emit code for anything before that. They added an ExpressionType.Assign
to the .NET Framework 4.
This works in C# 4 (tested):
public delegate void ByRefStructAction(ref SomeType instance, object value);
private static ByRefStructAction BuildSetter(FieldInfo field)
{
ParameterExpression instance = Expression.Parameter(typeof(SomeType).MakeByRefType(), "instance");
ParameterExpression value = Expression.Parameter(typeof(object), "value");
Expression<ByRefStructAction> expr =
Expression.Lambda<ByRefStructAction>(
Expression.Assign(
Expression.Field(instance, field),
Expression.Convert(value, field.FieldType)),
instance,
value);
return expr.Compile();
}
Edit: Here was my test code.
public struct SomeType
{
public int member;
}
[TestMethod]
public void TestIL()
{
FieldInfo field = typeof(SomeType).GetField("member");
var setter = BuildSetter(field);
SomeType instance = new SomeType();
int value = 12;
setter(ref instance, value);
Assert.AreEqual(value, instance.member);
}