Posted On: 2016-10-23
Reflection is a concept in programming that is incredibly powerful, but often underutilized. Many languages have built-in support for it (and even some that don't can be extended to support it), yet it seems that many developers are still unfamiliar or uncomfortable with using it. Unfortunately, by avoiding reflection, many developers are missing out on simple solutions to many very common problems. I'd like to highlight some of those problems, as well as examples of how they can be solved with reflection, in the hopes that it will help others.
While I will use javascript for my examples below, the principles still apply for any language that supports reflection.
Imagine you have two objects, a taco and a meat:
var taco = { };
var meat = { name: 'steak', heartiness: 'intense', plateworthiness:'high' };
and you are writing code to add the meat to the taco. To do that, you need to copy the heartiness from the meat to the taco. A simple implementation would be:
function addMeat(taco, meat){
taco.heartiness = meat.heartiness;
}
function addVegetable(taco, vegetable){
You might start to wonder if there is a better way to do this - after all, so much of the code is similar, and there are plenty more kinds of ingredients that can be added to tacos.
You might even wonder if you can write one method addIngredient(taco, ingredient, attributesToCopy)
and just call it to handle all the ingredients:
addIngredient(taco, meat, ['heartiness']);
addIngredient(taco, vegetable, ['crunchiness', 'moistness'])
addIngredient(taco, flavoring, ['heat', 'aftertaste'])
Thanks to reflection, you can do exactly that! You can take the string from the attribute (ie. 'heartiness'
) and get the property that has that name. That will allow you to set the value of the property with that name on the taco to be the value of the property with that name on the ingredient:
function addIngredient(taco, ingredient, attributesToAdd){
for(var i = 0; i < attributesToAdd.length; i++){
var attributeName = attributesToAdd[i];
taco[attributeName] = ingredient[attributeName];
}
}
If the line taco[attributeName] = ingredient[attributeName];
looks odd to you, that is understandable. In javascript, properties can be accessed either using dot notation (taco.heartiness
) or using bracket notation (taco['heartiness']
). Regardless of how you access the property, you can get the value from it or set a value to it (ie. taco.heartiness = meat['heartiness']
).
Some other languages use a little bit more obtuse syntax, but the principles behind it are the same. For example, here is the addIngredient function in C#:
public void AddIngredient(Taco taco, object ingredient, string[] attributesToAdd){
foreach(var attributeName in attributesToAdd){
PropertyInfo tacoProperty = taco.GetType().GetProperty(attributeName);
PropertyInfo ingredientProperty = ingredient.GetType().GetProperty(attributeName);
object ingredientAttributeValue = ingredientProperty.GetValue(ingredient, null);
tacoProperty.SetValue(taco, ingredientAttributeValue, null);
}
}
It is also worth mentioning that compiled languages (like C#) won't check the type until runtime, so you may get exceptions if, for example, you try to use reflection to assign a string
to an int. As a result, those languages typically have ways that the developer can check the type before using it (for example if( tacoProperty.PropertyType == ingredientProperty.PropertyType )
could be used in the above example to make sure it is safe to set the value.)
This one example ended up being much lengthier than anticipated. Rather than provide that same level of detail for some other examples, I'll just list a few other places I have used reflection to achieve a lot of flexibility with very little code.
Hopefully this information was helpful. If you'd like for me to go into detail on any of the other examples please let me know.