Introduction
A co-worker of mine was working on some UTs for his current setup and wanted to invoke/access private
members on some of his production objects. I introduced him to the PrivateObject type in .NET to get his work done. That all went well until he wanted to get at a private field that was in a class that his main object inherited from. Turns out that’s not something built-in to the reflection/PrivateObject
stack of .NET.
Consider the following set of simple classes:
class BaseClass
{
private string myField = "base class my field";
}
class OtherClass : BaseClass { }
Now consider this unit test:
1: [TestMethod]
2: public void PrivateObjectTest()
3: {
4: var po = new PrivateObject(new OtherClass());
5:
6: try
7: {
8: po.GetField("myField", System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
9: Assert.Fail("GetField shouldn't have worked!");
10: }
11: catch
12: {
13: Assert.IsTrue(true);
14: }
15:
16: try
17: {
18: po.RealType.GetField("myField",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
19: Assert.Fail("GetField shouldn't have worked!");
20: }
21: catch
22: {
23: Assert.IsTrue(true);
24: }
25:
26: string foundFieldValue;
27: Assert.IsTrue(po.TryFindField<string>("myField",
28: System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance,
29: out foundFieldValue));
30: Console.WriteLine(foundFieldValue);
31: }
This UT will pass, but note what it’s asserting; that your GetField
calls do not work as you’d think they would. “myField
” is indeed a non-public
instance field on your object, but it’s on your object’s base class and therein lies the rub (for both PrivateObject
, the first try
/catch
, and System.Reflection
, the second try
/catch
).
How Do We Fix This?
Well a brute-force way of doing this would be to GetType().BaseType
and ask it for GetField()
. But then, my code has to know that the field I want to play with exists on the base type. Kind of annoying.
Extension method time!
Add this beauty to your codebase:
1: using System;
2: using System.Linq;
3: using Microsoft.VisualStudio.TestTools.UnitTesting;
4:
5: public static class PrivateObjectExtensions
6: {
7: public static bool TryFindField<T>
(this PrivateObject po, string name, out T value)
8: {
9: return po.TryFindField<T>
(name, System.Reflection.BindingFlags.Default, out value);
10: }
11:
12: public static bool TryFindField<T>(this PrivateObject po,
string name, System.Reflection.BindingFlags flags, out T value)
13: {
14: Type t = po.RealType;
15: bool found = false;
16: value = default(T);
17: do
18: {
19: var field = t.GetFields(flags)
20: .FirstOrDefault(f => f.Name == name);
21: if (field != default(System.Reflection.FieldInfo))
22: {
23: value = (T)field.GetValue(po.Target);
24: found = true;
25: }
26: else
27: {
28: t = t.BaseType;
29: }
30: } while (!found && t != null);
31:
32: return found;
33: }
34: }
And add a few more lines to the UT:
string foundFieldValue;
Assert.IsTrue(po.TryFindField<string>("myField",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance,
out foundFieldValue));
Console.WriteLine(foundFieldValue);
And voila!
There’s one minor gotchya here, and likely the reason that .NET doesn’t build this in automatically for you. If you have a chain of inheritance, you can have private
fields with the same name within that chain. The code I have here will simply find the one “closest to the top” and return its value. If there’s one deeper, you won’t get to it. The built-in GetField
methods will work on protected
fields just fine – because of course you can’t get a name collision that way. So just keep that in mind.
Enjoy!