Introduction
This tip is about how to get collect the values for all instance fields of a given type.
Background
While working on TaskMan recently, I have encountered the problem of how to obtain the values for all instance fields of a particular type.
Giving more context: the future versions of TaskMan will make extensive use of command line flags. For now, I have made up my mind about only a few of them, and declared each flag as an instance field in my main program class (Flag<T>
is a simple data structure I use to represent a CLI command flag):
public class TaskMan
{
Flag<bool> _displayHelpFlag = new Flag<bool>(nameof(_displayHelpFlag), "?|help");
Flag<bool> _displayLicenseFlag = new Flag<bool>(nameof(_displayLicenseFlag), "license");
Flag<bool> _displayVersionFlag = new Flag<bool>(nameof(_displayVersionFlag), "version");
Flag<string> _priorityFlag = new Flag<string>(nameof(_priorityFlag), "p|priority");
Flag<string> _descriptionFlag = new Flag<string>(nameof(_descriptionFlag), "d|description");
}
At the same time, for one particular purpose irrelevant to the point of this post, I also needed to collect all flags into a collection of the non-generic base class type, Flag
.
Of course, at first I wrote something like the following code in the constructor:
public TaskMan()
{
_flags = new Flag[]
{
_displayHelpFlag,
_displayLicenseFlag,
_displayVersionFlag,
};
}
It didn't look good enough. First, it smells of ugly code duplication – I essentially declare things twice. Second, it's bug-prone: what if I add another flag and forget to update the collection in the constructor?
Using the code
So that's what I ended up with, reflection to the rescue:
_flags = typeof(TaskMan)
.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.Where(fieldInfo => typeof(Flag).IsAssignableFrom(fieldInfo.FieldType))
.Select(fieldInfo => fieldInfo.GetValue(this))
.Cast<Flag>();
Note how concise and human-readable this looks using Linq / Lambda syntax! You can even read it out aloud:
- We ask the type
TaskMan
:
- "Get us all your non-public instance fields,
- where the field type is assignable to a variable of type
Flag
,
- collect values from each of these fields,
- and cast them to the
Flag
type".
You can customize this snippet for your needs, substituting the encompassing type name for TaskMan
and the desired field type name for Flag
.
Also, if you're interested in public fields as well, don't forget to remove the BindingFlags.NonPublic from the GetFields call.
History
2016.06.13 – First version of the article