Wednesday, August 30, 2006

Arrays of methods in C#

How many times have you written code that goes something like this:

if (someValue == SomeEnum.Type1)
Method1("value 1");
else if (someValue == SomeEnum.Type2)
Method2("value 2");
else if (someValue == SomeEnum.Type2)
Method3("value 3");

maybe you used a switch statement to accomplish the same kind of thing.


You can see the pattern there, Method1, Method2 and Method3 all have the same signature and they get executed when the value Type1, Type2 or Type3 is passed. Wouldn't be nice if we could reduce that to a single line?


That's exactly what arrays of methods can do for you, let's see an example, before we can use them we need to set them up


Since all the methods share the same signature it means we can use a delegate to represent all of the methods.

delegate void AddStringDelegate(string someValue);

Now we can declare an array of delegates:

AddStringDelegate[] addStringMethods;

We will use an enumeration to access the items of the array:

enum StringType {
Type1,
Type2,
Type3,
Type4
}

Then we need the actual methods declaration:

void AddStringType1(string someValue) {
OutputText(string.Format("String Type 1: {0}", someValue));
}
void AddStringType2(string someValue) {
OutputText(string.Format("String Type 2: {0}", someValue));
}
void AddStringType3(string someValue) {
OutputText(string.Format("String Type 3: {0}", someValue));
}

finally, on the constructor of our class, we assign the methods to the array to get our array of methods:

addStringMethods = new AddStringDelegate[3];
addStringMethods[(int)StringType.Type1] = new AddStringDelegate(AddStringType1);
addStringMethods[(int)StringType.Type2] = new AddStringDelegate(AddStringType2);
addStringMethods[(int)StringType.Type3] = new AddStringDelegate(AddStringType3);

It's ready to be used, let's see what the implementation looks like:

public void AddString(string someValue, StringType stringType) {
addStringMethods[(int)stringType](someValue);
}

As you can see we can access the required method by converting the enumeration to an int type, our code was just reduced to 1 line; passing the desired enum type gets to our method in one shot


I'll let you decide when you want to use this technique, I wouldn't recommend using it in a small class that gets instantiated and thrown very fast and often, but in some cases it might even be useful there


I think it would be specially useful if we had this kind of pattern inside a singleton class where the class could potentially live a lot longer, you could do all the work to initialize your array of methods and be able to re-use it effectively


Here's the full code for this example, on my next post I'll show you how you could use this technique to implement some kind of factory pattern or what is sometimes referred to as simple factory pattern

class FileGeneratorBase {
delegate void AddStringDelegate(string someValue);

AddStringDelegate[] addStringMethods;

public FileGeneratorBase() {
addStringMethods = new AddStringDelegate[4];
addStringMethods[(int)StringType.Type1] = new AddStringDelegate(AddStringType1);
addStringMethods[(int)StringType.Type2] = new AddStringDelegate(AddStringType2);
addStringMethods[(int)StringType.Type3] = new AddStringDelegate(AddStringType3);
addStringMethods[(int)StringType.Type4] = new AddStringDelegate(AddStringType4);
}

public void AddString(string someValue, StringType stringType) {
addStringMethods[(int)stringType](someValue);
}
void AddStringType1(string someValue) {
OutputText(string.Format("String Type 1: {0}", someValue));
}
void AddStringType2(string someValue) {
OutputText(string.Format("String Type 2: {0}", someValue));
}
void AddStringType3(string someValue) {
OutputText(string.Format("String Type 3: {0}", someValue));
}
void AddStringType4(string someValue) {
OutputText(string.Format("String Type 4: {0}", someValue));
}
void OutputText(string someValue) {
Console.WriteLine(someValue);
}
}

class Program {
static void Main(string[] args) {
FileGeneratorBase fg = new FileGeneratorBase();
fg.AddString("some value", StringType.Type1);
fg.AddString("some other value", StringType.Type2);
fg.AddString("one last value", StringType.Type3);
fg.AddString("testing out of bounds", StringType.Type4);
Console.ReadLine();
}
}

updated: To initialize the array using the same enum instead of hardcoded values of 0..4

5 comments:

Ayende Rahien said...

I do something similar, but with dictionaries and anonymous methods.

zproxy said...

this is old...

also, you could implement a typeswitch (see google)

var a = new TypeSwitch<MyEnum>();

a[string] = delegate
{

};


a[System.IDisposable] = delegate
{

};

a.Run(myEnumValue);


this is c# with linq.

BlackTigerX said...

The concept would be very similar with dictionaries, I'll have to try that out

the TypeSwitch looks interesting but requires LINQ

Steven said...

Nice,

But it gets even better when we use a generic dictionary to solve this problem.

static class FileGeneratorBase
{
delegate void AddStringDelegate(string someValue);

private static Dictionary<StringType, AddStringDelegate> addStringMethods;

static FileGeneratorBase()
{
addStringMethods = new Dictionary<StringType, AddStringDelegate>(4);

addStringMethods.Add(StringType.Type1, AddStringType1);
addStringMethods.Add(StringType.Type2, AddStringType2);
addStringMethods.Add(StringType.Type3, AddStringType3);
addStringMethods.Add(StringType.Type4, AddStringType4);
}

public static void AddString(string someValue, StringType stringType)
{
addStringMethods[stringType](someValue);
}
}

And of course, when our factory class is static, our Dictionary will only be instantiated once during the lifetime of our application.

BlackTigerX said...

nice, thanks. Now I just need to see if using Dictionaries causes any significant overhead