Private Member Reflection

snarfblam

Ultimate Contributor
Joined
Jun 10, 2003
Messages
2,097
Location
USA
I was working with my Reflector class when I ran into a little problem, and after a couple of hours of debugging and toying around and researching I can't seem to make any progress.

What I am trying to do is access a private field (dialogResult) on an instance of a class that derives from Windows.Forms.Form. I found it very interesting that I could access the dialogResult field from a System.Type that refers to System.Windows.Forms.Form, but not a System.Type that refers to the actual runtime type of my derived form. I found it even more interesting that while I could access the _message field of an exception with a System.Type that refers to System.Exception, I could also access it with a System.Type that refers to the derived System.UriFormatException. How very inconsistent...

The code I was using was the following:

Code:
[/color][Color=DarkRed]Exception [/Color]ex = [Color=Blue]new [/Color][Color=DarkRed]UriFormatException[/Color]("Moo");
[Color=DarkRed]Reflector [/Color]Rex = [Color=Blue]new [/Color][Color=DarkRed]Reflector[/Color](ex);
Rex.PrivateAccess = [Color=Blue]true[/Color];
[Color=DarkRed]MessageBox[/Color].Show(Rex.GetField("_Message").ToString());

[Color=DarkRed]Reflector [/Color]Me = [Color=Blue]new [/Color][Color=DarkRed]Reflector[/Color]([Color=Blue]this[/Color]);
Me.IgnoreCase = [Color=Blue]false[/Color];
Me.PrivateAccess = [Color=Blue]true[/Color];
[Color=DarkRed]MessageBox[/Color].Show(Me.GetField("dialogResult").ToString());

[Color=Green]// I also tried the following InvokeMember equivalent and got the same result:
[/Color][Color=DarkRed]Exception [/Color]ex = [Color=Blue]new [/Color][Color=DarkRed]UriFormatException[/Color]("Moo");
[Color=Blue]object [/Color]exMessage = ex.GetType().InvokeMember(
    "_message",
    [Color=DarkRed]System.Reflection.BindingFlags[/Color].Instance |
    [Color=DarkRed]System.Reflection.BindingFlags[/Color].FlattenHierarchy |
    [Color=DarkRed]System.Reflection.BindingFlags[/Color].NonPublic |
    [Color=DarkRed]System.Reflection.BindingFlags[/Color].GetField,
    [Color=Blue]null[/Color], ex, [Color=Blue]null[/Color]);
[Color=DarkRed]MessageBox[/Color].Show(exMessage.ToString());

[Color=Blue]object [/Color]thisDialogResult = [Color=Blue]this[/Color].GetType().InvokeMember(
    "dialogResult",
    [Color=DarkRed]System.Reflection.BindingFlags[/Color].Instance |
    [Color=DarkRed]System.Reflection.BindingFlags[/Color].FlattenHierarchy |
    [Color=DarkRed]System.Reflection.BindingFlags[/Color].NonPublic |
    [Color=DarkRed]System.Reflection.BindingFlags[/Color].GetField,
    [Color=Blue]null[/Color],
    this, [Color=Blue]null[/Color]);

[Color=DarkRed]MessageBox[/Color].Show(thisDialogResult.ToString());

The reason that I used UriFormatException is because it is defined in a different assembly than System.Exception (I thought that the issue might have had to do with the fact that System.Windows.Forms.Form is defined in a different assembly than my form class).

I am stumped. Can anyone think of anything?
 
The first thing I noticed about your example is that the _message field is declared as internal and the dialogResult field is declared as private. It seems that private fields are not included in the hierarchy for reflection past the current type. It looks like you would have to iterate through all the parent types and retrieve the private fields manually.
 
I knew I must have been missing something obvious. Unfortunately, the documentation isn't at all clear on accessing private/internal members. This is what it has to say about BindingFlags regarding nonpublic members:
MSDN said:
NonPublic - Specifies that non-public members are to be included in the search.
FlattenHierarchy - Specifies that public and protected static members up the hierarchy should be returned. Private static members in inherited classes are not returned. Static members include fields, methods, events, and properties. Nested types are not returned.
...
Specify BindingFlags.NonPublic to include non-public members (that is, private and protected members) in the search.
Not very informative. I guess I will have to walk up the inheritance hierarchy. That's unfortunate because it will make my code much more complex. I can't understand why Microsoft would choose to implement private member access in this manner.
 
FlattenHierarchy = public and protected, not private

If you actually think about it, this behaviour makes perfect sense. Reflection aside, private members are accessible only from within their declaring type, and not from within derived classes. Hence a derived class can contain another field with the exact same name without any naming conflicts. Notice that the documentation for FlattenHierarchy explicitly states that only public and protected members up the inheritance hierarchy are considered. I would expect this to be regardless of whether NonPublic is specified.

Although only visible within a particular assembly, an internal member is technically a public one, so it makes sense that it be treated as such, although it is a possible source of ambiguity.
 
FlattenHierarchy actually refers only to static members. I did not realize this at the moment I posted it, though. It provides access to protected and internal static members of base classes.

As far as internal members being technically public in terms of reflection, that is hard to either agree with or disagree. Yes, I can declare a field with the same name as a private field in the base class, but can't my deriving type defined in a separate assembly declare a field with the same name as the base class' internal field? Note the VB keyword used to declare a member internal: Friend. This modifier is meant to indicate that a member is not actually part of a classes interface at all. Maybe they are "more public" than private members, but I would say that the same "non-public" protection principal still applies.

At the same time, I understand where you were going with what you said. It makes some sense that inheritance plays no role in visibility with internal members but does with private members and that that difference is manifest in reflection, but I am still troubled by the fact that ultimately private members are accessible yet you have to loop through System.Type.BaseType properties to get the right class. They are protected, but they aren't.

It is kind of a paradox, I suppose. The behavior supports the spirit of OOP, but it does not support the spirit of private reflection. The two are mutually exclusive. If it were up to me, though I would have chosen to support the latter when it came to implementing reflection.
 
Last edited:
Back
Top