C# – Pass in a Func to override behavior

If I want to change the behavior of a method from the outside, I can pass in a function pointer. This approach exists in every language, and is one way to implement the Strategy Pattern.

In C#, function pointers are referred to as delegates, and the two most common ones are Action and Func. The difference between these two is Func returns something and Action doesn’t.

Example

In the following code, I am passing in different methods to control how bytes are formatted.

Code

/// <summary>
/// Default formatter = decimal. Pass in a formatter function to override this behavior.
/// </summary>
static void PrintBytes(byte[] data, Func<byte, string> formatterFunc=null)
{
	if(formatterFunc == null)
	{
		formatterFunc = (b) => b.ToString();
	}


	for(int i = 0; i < data.Length; i++)
	{
		Console.WriteLine($"Byte {i} = { formatterFunc(data[i])}");
	}
}

static void Main(string[] args)
{
	byte[] bytes = new byte[]
	{
		0b0110_1000, 0b0110_0101, 0b0110_1100, 0b0110_1100, 0b011_01111, 
		0b0010_0000, 0b0111_0111, 0b0110_1111, 0b0111_0010, 0b0110_1100, 0b0110_0100
	};
	PrintBytes(bytes);

	PrintBytes(bytes, (b) => b.ToString("x2"));
	
	PrintBytes(bytes, (b) => Convert.ToChar(b).ToString());
}

Output

Output of formatting bytes in three different ways - decimal, hex, and ascii

What is Func?

In my example my parameter is this:

Func<byte, string>

Func specifies a method signature, and I can pass in any method that has that same signature.

That means if I have a Func<byte, string> parameter, I can pass in this method:

string MethodName(byte b)

Func accepts generic types, and there are several Func types accepting varying numbers of parameters. This is nice, because then we don’t have to create our own custom delegates to get the same behavior.

A few examples:

FuncExample method
Func<byte>byte GenerateAByte()
Func<string, byte>string ConvertByte(byte b)
Func<int, int, int>int Add(int a, int b)

Why not pass in an interface or class instead?

Yep, you can use Interfaces/Classes to achieve the same thing. They are the other way to implement the Strategy Pattern.

Let’s see how my byte formatting example would be implemented using an interface instead.

public interface IByteFormatter
{
	string Format(byte b);
}
public class DefaultByteFormatter : IByteFormatter
{
	public string Format(byte b)
	{
		return b.ToString();
	}
}
public class ByteToHex : IByteFormatter
{
	public string Format(byte b)
	{
		return b.ToString("x2");
	}
}

static void PrintBytes(byte[] data, IByteFormatter byteFormatter=null)
{
	if(byteFormatter == null)
	{
		byteFormatter = new DefaultByteFormatter();
	}


	for(int i = 0; i < data.Length; i++)
	{
		Console.WriteLine($"Byte {i} = { byteFormatter.Format(data[i])}");
	}
}

static void Main(string[] args)
{
	byte[] bytes = new byte[]
	{
		0b0110_1000, 0b0110_0101, 0b0110_1100, 0b0110_1100, 0b011_01111, 
		0b0010_0000, 0b0111_0111, 0b0110_1111, 0b0111_0010, 0b0110_1100, 0b0110_0100
	};
	PrintBytes(bytes);

	PrintBytes(bytes, new ByteToHex());
}

Wow, that’s verbose.

I’m passing in an interface so it can call a method? Why not just pass in the method itself?

Don’t get me wrong, there are definitely scenarios where you’d want to use an interface for implementing the Strategy Pattern instead, but in this example, passing an interface is definitely overkill.

Why not pass in a flag to control formatting?

Aren’t we overengineering things here? Why not just pass a flag that will control how the method formats?

Take a look at the version of the code that passes in a flag:

public enum ByteFormats
{
	Decimal,
	Hex,
	Ascii
}
static void Main(string[] args)
{
	byte[] bytes = new byte[]
	{
		0b0110_1000, 0b0110_0101, 0b0110_1100, 0b0110_1100, 0b011_01111, 
		0b0010_0000, 0b0111_0111, 0b0110_1111, 0b0111_0010, 0b0110_1100, 0b0110_0100
	};
	PrintBytes(bytes);

	PrintBytes(bytes, ByteFormats.Hex);

	PrintBytes(bytes, ByteFormats.Ascii);
}
static void PrintBytes(byte[] data, ByteFormats byteFormat = ByteFormats.Decimal)
{

	for(int i = 0; i < data.Length; i++)
	{
		string formatted = "";
		byte b = data[i];

		switch(byteFormat)
		{
			case ByteFormats.Decimal:
				formatted = b.ToString();
				break;
			case ByteFormats.Hex:
				formatted = b.ToString("x2");
				break;
			case ByteFormats.Ascii:
				formatted = Convert.ToChar(b).ToString();
				break;
		}

		Console.WriteLine($"Byte {i} = { formatted}");
	}
}

Let’s say we want to add a new format. We would need to add a new enum value, a new case in the method, and implement the formatter functionality.

This violates the Open-Closed Principle, which states that we want code that’s open to extension and closed to modification. Furthermore, isn’t the PrintBytes() method doing too much here? We want methods to be as simple as possible and only have a single responsibility (Single Responsibility Principle).

Now think about how easy it would be to extend this code if we were using the function pointer approach. We would simply pass in a new method. The PrintBytes() method wouldn’t need to be modified at all.

Leave a Comment