Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Reflection to determine which Fields are backing fields of a Property

I'm using reflection to map out objects. These objects are in managed code but I have no visibility into their source code, underlying structure, etc. other than through reflection. The overarching goal of all this is a rudimentary memory map of an object (similar in functionality to SOS.dll DumpObject and !ObjSize commands). As such, I'm trying to determine which members are being "double counted" as both a field and a property.

For example:

public class CalendarEntry
{
    // private property 
    private DateTime date { get; set;}

    // public field 
    public string day = "DAY";
}

When mapped shows:

  • Fields
    • day
    • k__BackingField
  • Properties
    • date

Where as a class like this:

public class CalendarEntry
{
    // private field 
    private DateTime date;

    // public field 
    public string day = "DAY";

    // Public property exposes date field safely. 
    public DateTime Date
    {
        get
        {
            return date;
        }
        set
        {
                date = value;
        }
    }
}

When mapped shows:

  • Fields
    • day
    • date
  • Properties
    • Date

At first glance there's nothing to tell you that the Date property's "backing field" is the field named date. I'm trying to avoid counting date twice in this scenario since that will give me a bad memory size approximation.

What's more confusing/complicated is I've come across scenarios where properties don't always have a corresponding field that will be listed through the Type.GetFields() method so I can't just ignore all properties completely.

Any ideas on how to determine if a field in the collection returned from Type.GetFields() is essentially the backing field of some corresponding property returned from Type.GetProperties()?

Edit- I've had trouble determining what conditions a property will not have a corresponding field in listed in the collection returned from Type.GetFields(). Is anyone familiar with such conditions?

Edit 2- I found a good example of when a property's backing field would not be included in the collection returned from Type.GetFields(). When looking under the hood of a String you have the following:

  • Object contains Property named FirstChar
  • Object contains Property named Chars
  • Object contains Property named Length
  • Object contains Field named m_stringLength
  • Object contains Field named m_firstChar
  • Object contains Field named Empty
  • Object contains Field named TrimHead
  • Object contains Field named TrimTail
  • Object contains Field named TrimBoth
  • Object contains Field named charPtrAlignConst
  • Object contains Field named alignConst

The m_firstChar and m_stringLength are the backing fields of the Properties FirstChar and Length but the actual contents of the string are held in the Chars property. This is an indexed property that can be indexed to return all the chars in the String but I can't find a corresponding field that holds the characters of a string. Any thoughts on why that is? Or how to get the backing field of the indexed property?

like image 264
nbelisle11 Avatar asked Jan 14 '13 16:01

nbelisle11


3 Answers

The name of a property's backing field is a compiler implementation detail and can always change in the future, even if you figure out the pattern.

I think you've already hit on the answer to your question: ignore all properties.

Remember that a property is just one or two functions in disguise. A property will only have a compiler generated backing field when specifically requested by the source code. For example, in C#:

public string Foo { get; set; }

But the creator of a class need not use compiler generated properties like this. For example, a property might get a constant value, multiple properties might get/set different portions of a bit field, and so on. In these cases, you wouldn't expect to see a single backing field for each property. It's fine to ignore these properties. Your code won't miss any actual data.

like image 88
Peter Ruderman Avatar answered Nov 06 '22 16:11

Peter Ruderman


You can ignore all properties completely. If a property doesn't have a backing field, then it simply doesn't consume any memory.

Also, unless you're willing to (try to) parse CIL, you won't be able to get such mapping. Consider this code:

private DateTime today;

public DateTime CurrentDay
{
    get { return today; }
}

How do you expect to figure out that there is some relation between the today field and the CurrentDay property?

EDIT: Regarding your more recent questions:

If you have property that contains code like return 2.6;, then the value is not held anywhere, that constant is embedded directly in the code.

Regarding string: string is handled by CLR in a special way. If you try to decompile its indexer, you'll notice that it's implemented by the CLR. For these few special types (string, array, int, …), you can't find their size by looking at their fields. For all other types, you can.

like image 26
svick Avatar answered Nov 06 '22 17:11

svick


To answer your other question:Under what circumstances do properties not have backing fields?

public DateTime CurrentDay
{
    get { return DateTime.Now; }
}

or property may use any other number of backing fields/classes

public string FullName 
{
    get {return firstName + " " + lastName;}
}
like image 1
Alexei Levenkov Avatar answered Nov 06 '22 17:11

Alexei Levenkov