[2019 edit: Since this post has always been one of my favorites, it is bittersweet to note that the approach I show here has been wholly superseded, in my own projects, by a newer, entirely different, and much sleeker technique, which I detail at this answer].
Using the new “ref return” feature in C# 7.0 can make the process of creating and using runtime dynamically-generated get/set accessors much simpler and syntactically transparent. Instead of having to use DynamicMethod to emit separate getter and setter functions for accessing the field, you can now have a single method that returns a managed pointer-type reference to the field, essentially a single accessor that (in turn) enables convenient, ad-hoc get a̲n̲d̲ set access. Below, I provide a helper utility function which simplifies generating a ByRef getter function for any arbitrary (i.e. private) instance field in any class.
➜ For “just the code,” skip to note below.
As a running example, let's say we want to access a private instance field m_iPrivate
, an int
defined in the class OfInterestClass
:
public class OfInterestClass
{
private int m_iPrivate;
};
Next let's assume we have a static field “reference-getter” function that takes a OfInterestClass
instance and returns the desired field value by reference using the new C# 7 “ref return” capability (below, I'll provide code to generate such functions at runtime, via DynamicMethod):
public static ref int __refget_m_iPrivate(this OfInterestClass obj)
{
/// ...
}
Such a function (“ref-getter,” let's say) is all we need in order to to have full read/write access to the private field. In the following examples, note especially the setter-invoking operation—and the demonstrations of using the (i.e.) ++
and +=
operators—since applying those operators directly to a method call may look a little unusual if you're not up-to-speed on C#7.
void MyFunction(OfInterestClass oic)
{
int the_value = oic.__refget_m_iPrivate(); // 'get'
oic.__refget_m_iPrivate() = the_value + 100; // 'set'
/// or simply...
oic.__refget_m_iPrivate() += 100; // <-- yes, you can
oic.__refget_m_iPrivate()++; // <-- this too, no problem
ref int prv = ref oic.__refget_m_iPrivate(); // via "ref-local" in C#7
prv++;
foo(ref prv); // all of these directly affect…
prv = 999; // …field m_iPrivate 'in-situ'
}
As is the point, every operation shown in these examples manipulates m_iPrivate
in situ (i.e., directly within its containing instance oic
) such that any and all changes are publicly visible there immediately. It's important to realize that this means that prv
, despite being int
-typed and locally declared, does not behave like your typical “local” variable. This is especially significant for concurrent code; not only are changes visible b̲e̲f̲o̲r̲e̲ MyFunction
has exited, but now with C# 7, callers have the ability to retain a ref return managed pointer (as a ref local) and thus continue modifying the target for an arbitrarily long time a̲f̲t̲e̲r̲wards (albeit necessarily remaining below the ref-obtaining stack frame, that is).
Of course a main and obvious advantage of using a managed pointer here—and elsewhere in general—is that it continues to remain valid (again, within its stack frame's lifetime), even as oic
—itself a reference-type instance allocated in the GC heap—may be moved around during garbage collection. This is a gigantic difference versus native pointers.
As sketched above, the ref-getter is a static
extension method that can be declared and/or used from anywhere. But if you're able to create your own class that's derived from OfInterestClass
(that is, if OfInterestClass
isn't sealed), you can make this even nicer. In a derived class, you can expose C# syntax for using the base class's private field as if it were a public field of your derived class. To do this, just add a C# read-only ref return property to your class which binds the static ref-getter method to the current instance this
:
public ref int m_iPrivate => ref __refget_m_iPrivate(this);
Here, the property is made public
so anybody can access the field (via a reference to our derived class). We've essentially publicly published the private field from the base class. Now, in the derived class (or elsewhere, as appropriate) you can do any or all of the following:
int v = m_iPrivate; // get the value
m_iPrivate = 1234; // set the value
m_iPrivate++; // increment it
ref int pi = ref m_iPrivate; // reference as C# 7 ref local
v = Interlocked.Exchange(ref m_iPrivate, 9999); // even do in-situ atomic operations on it!
As you can see, because the property, like the earlier method, also has a by reference return value, it behaves almost exactly like a field does.
So now for the details. How do you create the static ref-getter function that I showed above? Using DynamicMethod
, this should be trivial. For example, here is the IL code for a traditional (by-value) static getter function:
// static int get_iPrivate(OfInterestClass oic) => oic.m_iPrivate;
IL_0000: ldarg.0
IL_0001: ldfld Int32 m_iPrivate/OfInterestClass
IL_0006: ret
And here is the IL code that we want instead (ref-return):
// static ref int refget_iPrivate(OfInterestClass oic) => ref oic.m_iPrivate;
IL_0000: ldarg.0
IL_0001: ldfld̲a Int32 m_iPrivate/OfInterestClass
IL_0006: ret
The only difference from the by-value getter is that we are using the ldflda
(load field address) opcode instead of ldfld
(load field). So if you're well-practiced with DynamicMethod
it should be no problem, right?
Wrong!...
unfortunately DynamicMethod
does not allow a by-ref return value!
If you try to call the DynamicMethod
constructor specifying a ByRef
type as the return value...
var dm = new DynamicMethod(
"", // method name
typeof(int).MakeByRefType(), // by-ref return type <-- ERROR
new[] { typeof(OfInterestClass) }, // argument type(s)
typeof(OfInterestClass), // owner type
true); // private access
...the function throws NotSupportedException
with the following message:
The return Type contains some invalid type (i.e. null, ByRef)
Apparently, this function did not get the memo on C#7 and ref-return. Fortunately, I found a simple workaround that gets it working. If you pass a non-ref type into the constructor as a temporary "dummy," but then immediately afterwards use reflection on the newly-created DynamicMethod
instance to change its m_returnType
private field to be the ByRef-type type (sic.) that you actually want, then everything seems to work just fine.
To speed things up, I'll cut to the completed generic method which automates the whole process of by creating/returning a static ref-getter function for the private instance field of type U
, having the supplied name, and defined in class T
.
If you just want the complete working code, copy from below this point to the end
First we have to define a delegate that represents the ref-getter, since a Func<T,TResult>
delegate with ByRef usage can't be declared. Fortunately, the older delegate
syntax does work for doing so (phew!).
public delegate ref U RefGetter<T, U>(T obj);
Place the delegate, along with the following static function in a centralized utility class where both can be accessed throughout your project. Here's the final ref-getter creation function which can be used to create a static ref-getter for the so-named instance field in any class.
public static RefGetter<T, U> create_refgetter<T, U>(String s_field)
{
const BindingFlags bf = BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.DeclaredOnly;
var fi = typeof(T).GetField(s_field, bf);
if (fi == null)
throw new MissingFieldException(typeof(T).Name, s_field);
var s_name = "__refget_" + typeof(T).Name + "_fi_" + fi.Name;
// workaround for using ref-return with DynamicMethod:
// a.) initialize with dummy return value
var dm = new DynamicMethod(s_name, typeof(U), new[] { typeof(T) }, typeof(T), true);
// b.) replace with desired 'ByRef' return value
dm.GetType().GetField("m_returnType", bf).SetValue(dm, typeof(U).MakeByRefType());
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldflda, fi);
il.Emit(OpCodes.Ret);
return (RefGetter<T, U>)dm.CreateDelegate(typeof(RefGetter<T, U>));
}
Returning now to outset of this article, we can easily provide the __refget_m_iPrivate
function that got everything started. Instead of a static function written directly in C#, we will use the static ref-getter creation function to create the function body at runtime and store it in a static delegate-typed field (with the same signature). The syntax for calling it in the instance property (as shown above, and repeated below) or elsewhere is the same as if the compiler had been able to write the function.
Finally, to cache the dynamically-created ref-getter delegate, place the following line in any static
class of your choice. Replace OfInterestClass
with the type of the base class, int
with the field type of the private field, and change the string argument to match the name of the private field. If you aren't able to create your own class derived from OfInterestClass
(or don't want to), you're done; just make this field public
and you can call it like a function, passing any OfInterestClass
instance to get a reference which lets you read, write, or monitor its int
-valued private
field "m_iPrivate
."
// Static delegate instance of ref-getter method, statically initialized.
// Requires an 'OfInterestClass' instance argument to be provided by caller.
static RefGetter<OfInterestClass, int> __refget_m_iPrivate =
create_refgetter<OfInterestClass, int>("m_iPrivate");
Optionally, if you want to publish the hidden field with a cleaner or more natural syntax, you can define a (non-static) proxy class of your own which either contains an instance of—or perhaps even better (if possible), derives from—the field hiding class OfInterestClass.
Instead of deploying the line of code previously shown globally in a static
class, place it in your proxy class instead, and then also add the following line:
// optional: ref-getter as an instance property (no 'this' argument required)
public ref int m_iPrivate => ref __refget_m_iPrivate(this);
Delegate.CreateDelegate
? You already have a delegate withgetter
. Simply callinggetter(myInstanceOfT)
will invoke thefieldInfo.GetValue
method and return you the value. – HeraclidFunc<S, T> getter = s => (T)fieldInfo.GetValue(s);
all you can do, because field doesn't have the setter\getter method as it is in a property. If performance is the key I recommend to use the Expression. – Determineget-set
method pair for fieldinfo (similar to properties) and not at all after a function that is namedCreateDelegate
but internally goes the expression route. That was not the purport of the question. If you took it so, I'm sorry. I accept I may not have worded the question as clearly as you would have wanted. I did not ask a question not knowing how to refactor a method and give it a name. Certainly not to open a bounty for it. – Ainslee