Accessing private fields in inherited classes

By | December 6, 2013

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 w/ the same name w/in 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!