Click here to Skip to main content
15,891,019 members
Articles / Programming Languages / C#
Article

Generating Private Member Accessors

Rate me:
Please Sign up or sign in to vote.
4.83/5 (7 votes)
19 Sep 2011CPOL4 min read 22.7K   193   13   5
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)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generalnot-generic GenerateMemberGetter Pin
Christophe Bertrand19-Aug-13 5:52
Christophe Bertrand19-Aug-13 5:52 
GeneralMy vote of 5 Pin
Kanasz Robert6-Nov-12 2:26
professionalKanasz Robert6-Nov-12 2:26 
GeneralMy vote of 5 Pin
Nish Nishant19-Sep-11 10:03
sitebuilderNish Nishant19-Sep-11 10:03 
QuestionMore information Pin
Steve Hansen19-Sep-11 8:50
Steve Hansen19-Sep-11 8:50 
Some information about the actual methods would be useful Smile | :)
AnswerRe: More information Pin
kornman001-Oct-11 5:22
kornman001-Oct-11 5:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.