Saturday, November 2, 2013

Functions as Function Arguments

 

This post talks about a set of utilities provided in the BrightSword SwissKnife library (nuget, codeplex).

Reflection is a commonly used meta-programming technique where run-time inspection of types allows for more elegant solutions in some cases. The .NET framework provides powerful primitives to use reflection, which have a nice orthogonal and consistent interface for the various type elements.

A necessary, but less desirable, consequence of an orthogonal interface is the ‘boilerplate’ nature of the code which uses different elements. There is typically a great deal of symmetry and similarity in the code, which generally leads to a cut-copy-paste kind of code-proliferation – and results in unmaintainable code.

In this post, we’ll talk about the software techniques we can use to mitigate this form of code-proliferation, and actively develop a lean and maintainable utility without sacrificing flexibility.

You will find these utilities here, where there are a set of extension methods on the Type type. 

 

Reflection Utilities

One of the fundamental principles of functional programming is the concept of a First-Class Function.

A language with a First Class function is one that allows functions to be treated exactly the same way as other first class entities such as variables and constants. This generally means that there is the concept of being able to pass in a function as an argument to another function (just like one can pass in a string as an argument), assign a function to a variable, or return the function as the result of a function.

This is a very powerful concept, as it allows us to parameterize logic just like we traditionally parameterize values.

GetAllProperties|GetAllMethods|GetAllEvents

Consider the problem of reflecting over a type and enumerating over its properties:

A naïve implementation of a function to enumerate a type’s properties might look like this:

public static IEnumerable<PropertyInfo> GetProperties(Type t) 
{
foreach (var item in t.GetProperties())
{
yield return item;
}
}



An analogous method to enumerate methods would then be:

public static IEnumerable<MethodInfo> GetMethods(Type t) 
{
foreach (var item in t.GetMethods())
{
yield return item;
}
}



The reason this is a naïve implementation is that interfaces do not inherit, in the traditional sense of the term, the members of their base interfaces – rather they inherit the requirement that the base interface is implemented as well. See my earlier post for the background to this problem.


The complete solution involves recursively walk up the inheritance chain for interfaces, whilst keep track of interfaces seen before, and enumerating the members of each interface encountered.


The code to do this is somewhat more involved:


public static IEnumerable<PropertyInfo> GetInterfaceProperties(this Type type,
ISet<Type> processedInterfaces = null)
{
Debug.Assert(type.IsInterface);

processedInterfaces = processedInterfaces ?? new HashSet();

if (processedInterfaces.Contains(type))
{
yield break;
}

foreach (var _pi in type.GetProperties())
{
yield return _pi;
}

foreach (var _pi in type.GetInterfaces()
.SelectMany(_ => _.GetInterfaceProperties(processedInterfaces)))
{
yield return _pi;
}

processedInterfaces.Add(type);
}
After taking a single glance at this code, one can imagine what the analogous code for Methods and Events might be – a lot of similar code with a few method calls and types changed.


This is an ideal situation to use the functional programming support provided by C#.


We can wrangle out the boilerplate code out of this to get:

public static IEnumerable<TMemberInfo> GetInterfaceMembers(this Type type, 
Func<Type, IEnumerable<TMemberInfo>> accessor,
ISet<Type> processedInterfaces = null)
where TMemberInfo : MemberInfo
{
Debug.Assert(type.IsInterface);

processedInterfaces = processedInterfaces ?? new HashSet();

if (processedInterfaces.Contains(type))
{
yield break;
}

foreach (var _pi in accessor(type))
{
yield return _pi;
}

foreach (var _pi in type.GetInterfaces()
.SelectMany(_ => _.GetInterfaceMembers(accessor, processedInterfaces)))
{
yield return _pi;
}

processedInterfaces.Add(type);
}



So by introducing the <TMember> type parameter to represent a MemberInfo generically, and providing a function argument accessor which provides a collection of members of the specified type, we can abstract out the boilerplate code and parameterize the accessor logic.


SwissKnife uses this approach to provide a set of extension methods on the Type type, which simply parameterize a common function:


public static IEnumerable<PropertyInfo> GetAllProperties(this Type _this, BindingFlags bindingFlags = DefaultBindingFlags) 
{
return _this.GetAllMembers((_type, _bindingFlags) => _type.GetProperties(_bindingFlags), bindingFlags);
}

public static IEnumerable<MethodInfo> GetAllMethods(this Type _this, BindingFlags bindingFlags = DefaultBindingFlags)
{
return _this.GetAllMembers((_type, _bindingFlags) => _type.GetMethods(_bindingFlags), bindingFlags);
}

public static IEnumerable<EventInfo> GetAllEvents(this Type _this, BindingFlags bindingFlags = DefaultBindingFlags)
{
return _this.GetAllMembers((_type, _bindingFlags) => _type.GetEvents(_bindingFlags), bindingFlags);
}
which allows for usage like this:
var readonlyProperties = typeof (T).GetAllProperties()
.Where(_ => !_.CanWrite || _.GetSetMethod() == null);



A functional approach to coding up these functions has reduced coding noise and resulted in simpler, easier-to-use, easier-to-understand and easier-to-maintain code.


Summary







  • SwissKnife provides a utility mechanism to simplify reflection over a type to enumerate different types of members. This is especially useful in the case of interface hierarchies.


  • We can use the functional programming principle of first-class functions to extract boilerplate code and parameterize logic in addition to values. This makes code easier to maintain.

No comments:

Post a Comment