Introduction
(Ignore the image until you have read the "Background")
Sometimes, in order to complete a task, you need to access (i.e., get
and/or set
) member data which is outside your field of view (e.g., "private
"). 99% of the time, you should never be accessing non-public data, and instead should be thinking why you need to do such and what other mechanisms are available which don't break encapsulation. For the other 1% of the time, you can use the tricks developed in this article to use Linq Expressions to both get
and set
non-public members. I've developed solutions for both Reference and Value-Types, and the member data could be field or properties of a type.
Background
A while ago, I started work on some code which deals heavily with Guid
values. .NET's provided Guid
was a great starting point but had some limitations which I didn't care so much for. First, once a Guid
is created, you have no easy access to individual parts of the Guid
(e.g., Data1
). Second, the only way to introspect the actual Guid
value is to either use ToString
or ToByteArray
. Neither of which were appealing as they allocated new data when it wasn't actually needed, and required additional (i.e., wrapper) code.
Why is this a limitation? For my own .NET libraries, I have implementations of BinaryReader
and Writer
which respect the endian format of the underlying stream (Note: I use explicit binary serialization since the stream data needs to interop well with C++ code). Part of my usage of Guid
values in one part of a library required binary serialization (to and from a stream) of the Guid
. Reading Guid
s isn't so much of a problem, the Guid
's constructor provides multiple overloads for defining each DataX
field. However, in order to write a Guid
, I'd have to call ToByteArray
...which would create a new byte[]
every time. Not very appealing, especially if there are multiple points in a binary stream which contain Guid
s. It wouldn't be so bad if ToByteArray
had an overload which accepted an existing byte[]
and index to copy the values to, but it doesn't. :(
So I started browsing the internet for solutions. Initially it was for solutions that didn't call for a re-write of System.Guid
. Preliminary searches (specific to Guid
) didn't return anything useful. Looking at Microsoft's shared-source for .NET 2.0 made me realize that I didn't want to re-implement the wheel, so I searched for simple ways of accessing private
data. Most hits were Reflection and ILCodegen related (barf), but eventually I stumbled upon this Roger Alsing blog entry which uses Linq Expressions instead. His blog entry is the main inspiration of the code developed hereinafter.
Using the Code
The source code for this article uses Microsoft's Code Contracts for pre- and post-condition annotation. I also included an excerpt from a unit test (via, Visual Studio's framework) that demonstrates this utility code (using Guid
!).
For 'getting' fields or properties, there is one method (with two overloads) for generating Func<>
instances which provide the needed access: GenerateMemberGetter
. One overload allows you to provide the parent type as a generic parameter, while the other allows you to provide a Type
as a parameter. Both overloads require a string
of the name of the member. For the latter overload, the generated Func<>
takes an object as its parameter instead of a type-safe parameter.
For 'setting' fields or properties, there are two methods (one with two overloads) for generating two explicit delegate types for providing the needed access: GenerateValueTypeMemberSetter
and GenerateReferenceTypeMemberSetter
(the latter having overloads in the same manner as the Getter from earlier).
Since value-types are passed by value, calling a method which sets a member of that type won't actually reflect on the instance you passed. So we have to use the ValueTypeMemberSetterDelegate
delegate which takes the "this
" parameter by reference. The code below should illustrate this:
static readonly byte[] kSrcData = new byte[] {
0xD2, 0xF4, 0x6F, 0xFD,
0xF4, 0x70,
0x33, 0x43,
0xBF, 0xA3, 0x1E, 0xBC, 0x08, 0x66, 0x36, 0x8F
};
static readonly Guid kSrcValue = new Guid(kSrcData);
static readonly byte[] kTargetData =
new byte[] {
0xD3, 0xF4, 0x6F, 0xFD,
0xF4, 0x70,
0x33, 0x43,
0xBF, 0xA3, 0x1E, 0xBC, 0x08, 0x66, 0x36, 0x8F
};
static readonly Guid kTargetValue = new Guid(kTargetData);
var kGetData1 = Util.GenerateMemberGetter<Guid, int>("_a");
var kSetData1 = Util.GenerateValueTypeMemberSetter<Guid, int>("_a");
Guid test_value = kSrcValue;
int new_data1 = kGetData1(test_value) + 1;
kSetData1(ref test_value, new_data1);
Console.WriteLine("before member access:\t{0}", kSrcValue.ToString().ToUpper());
Console.WriteLine("after member access:\t{0}", test_value.ToString().ToUpper());
Assert.IsTrue( kTargetValue.CompareTo(test_value) == 0 );
Conclusion
This is my first article, so please go easy on me :). The only other time I've found this recipe useful was for setting BinaryReader
's BaseStream
to null
(BinaryWriter
allows access to its BaseStream
, via its OutStream
member). However, who knows what situations other people on the interwebs may find themselves in!
History
- 2011 Sep 16 - Initial article creation