Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Local Inversion of Control

0.00/5 (No votes)
20 Feb 2013 1  
Kind of like inversion of control, but without DI containers

The term ‘Inversion of Control’ is typically used in relation to DI/IoC frameworks. However, that same idea works at the micro scale too. I for one encounter a lot of what I could call Local Inversion of Control (LIoC) all the time, and this is what this short post is all about.

Examples

Let’s start with the simplest example. Say you want to add an item to the collection: this is typically written as

myCollection.Add(x)

If you pause for a moment and think about the above statement and voice it in English, what you’ll get is myCollection should add to itself item x which is very much unreadable. This is precisely the case where IoC is useful, and here’s how: let us define an AddTo() extension method:

public static T AddTo<T>(this T self, ICollection<T> collection)
{
  collection.Add(self);
  return self;
}

Now, instead of calling the Add() method on the collection, control is inverted, so the AddTo() method is called on the collection:

var aList = new List<int>();
2.AddTo(aList);

Translated to English, the above now reads 2 should be added to aList. Makes more sense, doesn’t it? Also, as an added bonus, the AddTo() operation is fluent because it returns the original argument. This means that a value can be added to more than one list at once, e.g.:

2.AddTo(someList).AddTo(someOtherList);

Let’s try something else. Suppose you want to check that a collection is empty. Typically, you’d write

if (myClass.Fields.Count == 0) { ... }
// or
if (!myClass.Fields.Any()) { ... }

Both of these approaches are okay, I guess, but the code reads strange. The first example says if myClass’s fields count is equal to zero whereas the second reads if not myClass’s fields are any in number, which is even worse.

So here’s a pair of extension methods:

public static bool HasSome<TSubject, T>(this TSubject subject, 
  Func<TSubject, IEnumerable<T>> propertyToCheck)
{
  return propertyToCheck(subject).Any();
}
public static bool HasNo<TSubject, T>(this TSubject subject, 
  Func<TSubject, IEnumerable<T>> propertyToCheck)
{
  return !HasSome(subject, propertyToCheck);
}

Now, a check for the presence of fields turns into

if (myClass.HasNo(c => c.Fields)) {}

which reads as myClass has no fields, which is precisely what we’re checking here.

Okay, one more example. Say you’re checking a string value, and you want to compare it with a set of values. This is typically represented as

if (myOp == "AND" || myOp == "OR" || myOp == "XOR") { ... }

This reads in a kind of OK, but fragmented, fashion. A much better way would be to group the variants into an array but, in C#, this just looks ugly:

if (new[]{"AND", "OR", "XOR"}.Contains(myOp)) { ... }

This is even less semantic. What if there was an IsOneOf() function instead?

public static bool IsOneOf<T>(this T self, params T[] variants)
{
  return variants.Contains(self);
}

Now, the above check would turn to

if (myOp.IsOneOf("AND", "OR", "XOR")) { ... }

The great thing about the above approach is that the arguments are declared as params T[], so we don’t have to initialize any arrays with new[] before using them as a test.

Summary

So here’s the basic premise of LIoC: given a function f(x,y...), it may in certain cases be more benefitial from the semantic and usability perspectives to instead define an extension method on x that applies the function f using the argument(s) y.

Additional benefits are provided by the fact that:

  • Extension methods may take generic arguments, thus letting you define inverted operations on groups of similar objects.

  • The params keyword lets your API behave in a ‘variadic’ fashion by unchaining you from explicit collection initialization.

  • Additional level of expressiveness can be gained from passing in lambdas or expressions.

The disadvantages of this approach are:

  • Having to manage separate classes containing extension methods.

  • API pollution — any time you define an extension method on a generic type, all objects get an additional IntelliSense popup member.

  • Possible performance overhead — for example, in cases where you use a lambda instead of direct member access.

I use LIoC all over the place, just like I use the Maybe monad. It makes the code a lot more readable, maintainable and amenable to refactoring. ▪

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here