Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Generating Private Member Accessors

4.83/5 (7 votes)
19 Sep 2011CPOL4 min read 23.3K   193  
Using LINQ Expressions to generate private member (field/property) accessors (get/set)
Sample Image - maximum width is 600 pixels

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 Guids 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 Guids. 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:

C#
static readonly byte[] kSrcData = new byte[] { // "FD6FF4D2-70F4-4333-BFA3-1EBC0866368F"
	0xD2, 0xF4, 0x6F, 0xFD, //0xFD6FF4D2, 
	0xF4, 0x70,             //0x70F4, 
	0x33, 0x43,             //0x4333, 
	0xBF, 0xA3, 0x1E, 0xBC, 0x08, 0x66, 0x36, 0x8F
};
static readonly Guid kSrcValue = new Guid(kSrcData);

// After modifications, D2 should now be D3
static readonly byte[] kTargetData = 
	new byte[] { // "FD6FF4D3-70F4-4333-BFA3-1EBC0866368F"
	0xD3, 0xF4, 0x6F, 0xFD, //0xFD6FF4D3, 
	0xF4, 0x70,             //0x70F4, 
	0x33, 0x43,             //0x4333, 
	0xBF, 0xA3, 0x1E, 0xBC, 0x08, 0x66, 0x36, 0x8F
};
static readonly Guid kTargetValue = new Guid(kTargetData);


// Generate accessors for Data1 of Guid (internally defined as "int _a")
var kGetData1 = Util.GenerateMemberGetter<Guid, int>("_a");
var kSetData1 = Util.GenerateValueTypeMemberSetter<Guid, int>("_a");

Guid test_value = kSrcValue;

// Increment the Data1 part of the Guid by one
int new_data1 = kGetData1(test_value) + 1;
// Update test_value with the new Data1 value
kSetData1(ref test_value, new_data1);

Console.WriteLine("before member access:\t{0}", kSrcValue.ToString().ToUpper());
// before member access:    FD6FF4D2-70F4-4333-BFA3-1EBC0866368F
// this char will change           ^
Console.WriteLine("after member access:\t{0}", test_value.ToString().ToUpper());
// after member access:     FD6FF4D3-70F4-4333-BFA3-1EBC0866368F
// this char changed               ^

// After our member setter, test_value should compare exactly to kTargetValue
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)