SteGriff

Blog

Next & Previous

Getting Enum attributes - fast!

Sometimes you want to attach extra information to Enum members so that you can get, for example, a user-facing string representation of the underlying value. There are a number of Stack Overflow answers and the like which will tell you how to do this with Attributes, particularly the [Display(Name = x)] attribute:

using System.ComponentModel.DataAnnotations;

public enum DocumentTypeEnum
{
    [Display(Name = "Policy Terms", ShortName = "tcs")]
    PolicyTerms = 1,

	...
	
    [Display(Name = "Home Emergency Add-on Summary", ShortName = "heipid")]
    IpidAddOnHE = 23,

}

…but to pull these attribute values out, we have to use reflection. Reflection is code which is introspective and pulls up its own implementation details. It is famously slow in action. So is this technique too slow to use?

Benchmarking Enum reflection vs a switch statement

I decided to roll a quick test using BenchmarkDotNet, which I recently learned. My first comparison was against a simple switch to retrieve the names.

All my benchmarks are in a file called EnumNamingBenchmarks.cs which is kicked off from a benchmarking console app, using BenchmarkRunner.Run<EnumNamingBenchmarks>();

Here is the code for the reflection benchmark:

private List<DocumentTypeEnum> DocumentTypes
{
	// Datasource for all benchmarks
	// returns a new List<DocumentTypeEnum>() { ... }
}

[Benchmark]
public void GetEnumNamesByReflection()
{
	var results = new List<string>();
	foreach (var docType in DocumentTypes)
	{
		var res = GetDisplayName(docType);
		results.Add(res);
	}
}

private string GetDisplayName(Enum enumValue)
{
	return enumValue
		.GetType()
		.GetMember(enumValue.ToString())
		.First()
		.GetCustomAttribute<DisplayAttribute>()
		.Name;
}

And the switch benchmark:

[Benchmark]
public void GetEnumNamesBySwitch()
{
	var results = new List<string>();
	foreach (var docType in DocumentTypes)
	{
		var res = GetEnumNameBySwitch(docType);
		results.Add(res);
	}
}

private string GetEnumNameBySwitch(DocumentTypeEnum docType)
{
	switch (docType)
	{
		case DocumentTypeEnum.PolicyTerms:
			return "Policy Terms";
		case DocumentTypeEnum.PolicySummary:
			return "Policy Summary";
		case DocumentTypeEnum.IpidAddOnHE:
			return "Home Emergency Add-on Summary";
		case DocumentTypeEnum.IpidAddOnLE:
			return "Legal Expenses Add-on Summary";
		case DocumentTypeEnum.IpidAddOnRP:
			return "Rent Protection Add-on Summary";
		default:
			return "";
	}
}

Here are the results:


BenchmarkDotNet=v0.11.3, OS=Windows 7 SP1 (6.1.7601.0)
Intel Core i7-6700 CPU 3.40GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
Frequency=3328232 Hz, Resolution=300.4598 ns, Timer=TSC
  [Host]     : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.2558.0  [AttachedDebugger]
  DefaultJob : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.2558.0

Method Mean Error StdDev Median
GetEnumNamesBySwitch 249.1 ns 7.576 ns 22.34 ns 232.9 ns
GetEnumNamesByReflection 28,248.0 ns 162.917 ns 152.39 ns 28,209.3 ns

Uh-oh, looks like reflection is more than 100 times slower in this case! Now, in real terms, 28000ns is only 0.028ms and the impact on response times isn’t going to be noticeable until you’re doing this operation 1000 times. On the other hand, why make a processor do 100 times as many operations as necessary to accomplish a simple goal? I’m not an advocate for efficiency at any cost but we should be writing code which at least makes responsible use of resources.

Can we go faster?

I then half-remembered a lesson on performance from computing class and decided to see if array lookup was any faster than a switch:

[Benchmark]
public void GetEnumNamesByArrayAccess()
{
	var results = new List<string>();
	foreach (var docType in DocumentTypes)
	{
		var res = GetEnumNameByArray(docType);
		results.Add(res);
	}
}

private string GetEnumNameByArray(DocumentTypeEnum docType)
{
	//DocumentTypeNames is an array set up with string values in the corresponding array slots
	return DocumentTypeNames[(int)docType];
}

But this was, in fact, three times slower:

Method Mean Error StdDev
GetEnumNamesBySwitch 228.0 ns 1.730 ns 1.534 ns
GetEnumNamesByReflection 27,589.3 ns 118.814 ns 111.138 ns
GetEnumNamesByArrayAccess 796.4 ns 2.335 ns 2.070 ns

I have a feeling that I got the point of that lesson backwards… it’s often the case that the more memory we use to add useful information to an operation (through storage and through code), the less processing we have to do; they trade-off (think of procedural generation vs storing the world in memory). In this case, my direct switching comes out more performant than looking up from an array - I suppose that lets us draw some conclusions about the IL that was generated (but I’m not digging that deep today!)

Conclusions

There are some things you can only do with reflection. For everything else, you should probably avoid reflection if you want performance! That said, sometimes performance isn’t as important as, say, maintainability. A reflection solution might require less maintenance because you don’t have to keep updating the algorithm for new items of data like you would have to with a switch.

So remember to balance developer experience - empathy with those who come after you - with today’s performance concerns.