Stupid C# Tricks - readonly reflection - Continued
Whew! Lot of answers! The most promising ones seem to be the ones relating to binding - binding is stuff I just plain don't understand. The possibility that it's making some kind of copy of the type seems like it might be on the money.
Just to clarify:
I'm not using const - I'm using readonly. According to Richter, const gets compiled into your code, as you'd expect. But readonly is a regular ol' variable that is protected by the compiler and runtime.
It's not a property; it really is a field.
The field is static, so SetField should not take the owner (there isn't one anyway), it should take null. This is from MSDN.
I can't use different build-time dependent values because in the unit tests I test it with different caps in the same run.

fyi: just tested.
while i can set an instance readonly by passing in a instance value to the SetField(), and non readonly statics work regardless if i pass null or an instance in the first param:
i cannot set an readonly static value regardless of the parameter.
nicely no exception seems to get raised either.
that seems like a bug, but possibly , since it's a readonly static, the complier assumes that the value can be evaluated completely at compile time, and makes it, essentially, const.
( a friend-like container class could still meet your needs -- you can drop the preprocessor foo if you don't need / want it. :)
Posted by:ionous | May 24, 2007 at 12:10 PM
sorry - i take that back -- i got up to get a cup of coffee, looked at the debugger, and low and behold: the value in the read only static had changed, but simple assignments using it still result in the old value!
ie.
class Test { public readonly static value=5.0f };
fi.SetValue( null, 10.0f );
float res= Test.value;
in the watch window:
* Type.staticvalue=10;
* res= 5;
but even better:
calling a custom function:
static void F() { float localres= Test.value; };
fi.SetValue( null, 10.0f );
F();
results in localres = 10.0f!
the compiler may be optimizing local assignments incorrectly.
you *may* therefore be able to do what you want if you are careful.....
Posted by:ionous | May 24, 2007 at 12:28 PM
What about this?
Then define UNIT_TEST_BUILD only when creating your unit tests.
Posted by:Ryan Schneider | May 24, 2007 at 02:03 PM
So I did a bit of investigating and found your problem. There's actually an explicit IL instruction which loads a static field onto the stack which is being optimized for the read only variable.
Here's the relevant code I'm testing:
Type enemyType = typeof(Enemy);
FieldInfo sMaxEnemiesField = enemyType.GetField("sMaxEnemies");
sMaxEnemiesField.SetValue(null, 100); Console.WriteLine(Enemy.sMaxEnemies.ToString());
Here's the IL that results starting with the SetValue call (from Reflector):
L_006b: callvirt instance void [mscorlib]System.Reflection.FieldInfo::SetValue(object, object)
L_0070: nop
L_0071: ldsfld int32 ReflectionTest.Enemy::sMaxEnemies
L_0076: stloc.s CS$0$0000
L_0078: ldloca.s CS$0$0000
L_007a: call instance string [mscorlib]System.Int32::ToString()
L_007f: call void [mscorlib]System.Console::WriteLine(string)
The ldsfld int32 call at L_0071 gets compiled into the following assembly(from the debugger):
mov dword ptr [esp+8],5
lea ecx,[esp+8]
call 786ED520
Instead of moving an address, the JIT optimized by moving the value of 5 on to the stack. My guess is that because the variable is read only, the JIT assumed that optimization was safe. In my mind, changing the variable this way is similar to getting the address of a const in C++ and then changing the value. 90% of the time, it’s not going to actually change the value throughout the code.
However, if you can live without the optimization of read only, you can make the variable private, provide a public set method, and then change it through reflection through this:
FieldInfo sMaxEnemiesField = enemyType.GetField("sMaxEnemies", BindingFlags.NonPublic | BindingFlags.Static);
sMaxEnemiesField.SetValue(null, 100);
It results in the same IL, but the compiled assembly is a bit more involved.
call dword ptr ds:[009195B8h]
mov esi,eax
mov dword ptr [esp+8],esi
lea ecx,[esp+8]
call 786ED550
But it will result in 100 being the result.
Posted by:Jeff Ward | May 25, 2007 at 08:32 AM
Jeff for president.
Posted by:Jamie Fristrom | May 27, 2007 at 06:19 PM