Skip to content

Instantly share code, notes, and snippets.

@RobThree
Last active March 25, 2025 01:37
Show Gist options
  • Save RobThree/5b84502f89a00dcbcddcabceec52c12b to your computer and use it in GitHub Desktop.
Save RobThree/5b84502f89a00dcbcddcabceec52c12b to your computer and use it in GitHub Desktop.
See Isha Fatima's post "Replace Multiple if-else with a Dictionary for Cleaner Code!": https://www.linkedin.com/posts/ishafatima1907_csharp-dotnet-cleancode-activity-7308759297859948545-SKut (archived: https://archive.is/zKfwv)
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
namespace IshaFatima;
internal class Program
{
private static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<RoleBenchmark>();
}
}
[MemoryDiagnoser]
public class RoleBenchmark
{
private static readonly Dictionary<int, string> _roles = new()
{
{1, "Admin"},
{2, "Editor"},
{3, "Viewer"}
};
private static readonly Dictionary<UserRole, string> _roleenums = new()
{
{UserRole.Admin, "Admin"},
{UserRole.Editor, "Editor"},
{UserRole.Viewer, "Viewer"}
};
public enum UserRole
{
Admin = 1,
Editor = 2,
Viewer = 3
}
// For the enum-based benchmarks we would have to case our random value to the enum. To make everything fair we
// will generate a random value and for both the enum and int-based benchmarks we will a lookup in the arrays
// below to get the value to use in the benchmark so that we are comparing apples to apples and not skewing
// the results with casting for the enum-based benchmarks.
private static readonly int[] _role_ids = [1, 2, 3, 4]; // 1,2,3 are valid role ids, 4 should return "Unknown"
private static readonly UserRole[] _role_enums = [UserRole.Admin, UserRole.Editor, UserRole.Viewer, (UserRole)4];
// This is the baseline benchmark that we will compare all other benchmarks to. This is the "wrong" way to do it
// according to Isha Fatima's LinkedIn post.
[Benchmark(Baseline = true)]
public void GetUserRoleIfElseBenchmark()
=> GetUserRoleIfElse(_role_ids[Random.Shared.Next(4)]);
public static string GetUserRoleIfElse(int roleId)
{
if (roleId == 1) return "Admin";
else if (roleId == 2) return "Editor";
else if (roleId == 3) return "Viewer";
return "Unknown";
}
// According to Isha Fatima's LinkedIn post this is the "right" way to do it.
[Benchmark]
public void GetUserRoleProposedDictionaryBenchmark()
=> GetUserRoleProposedDictionary(_role_ids[Random.Shared.Next(4)]);
public static string GetUserRoleProposedDictionary(int roleId)
{
var roles = new Dictionary<int, string>
{
{1, "Admin"},
{2, "Editor"},
{3, "Viewer"}
};
return roles.TryGetValue(roleId, out var role) ? role : "Unknown";
}
// This is the same as the above benchmark but using a static dictionary instead of creating a new one each time.
// It could be argued this is what auther _actually_ meant in the post (but that's not what was written).
[Benchmark]
public void GetUserRoleStaticDictionaryBenchmark()
=> GetUserRoleStaticDictionary(_role_ids[Random.Shared.Next(4)]);
public static string GetUserRoleStaticDictionary(int roleId)
=> _roles.TryGetValue(roleId, out var role) ? role : "Unknown";
// This variation gets the name of the enum value using Enum.GetName.
[Benchmark]
public void GetUserRoleEnumGetNameBenchmark()
=> GetUserRoleEnumGetName(_role_ids[Random.Shared.Next(4)]);
public static string GetUserRoleEnumGetName(int roleId)
=> Enum.IsDefined(typeof(UserRole), roleId) ? Enum.GetName(typeof(UserRole), roleId)! : "Unknown";
// This variation gets the name of the enum value using Enum.ToString().
[Benchmark]
public void GetUserRoleEnumToStringBenchmark()
=> GetUserRoleEnumToString(_role_enums[Random.Shared.Next(4)]);
public static string GetUserRoleEnumToString(UserRole role)
=> role.ToString();
// This variation uses a switch statement with the enum.
[Benchmark]
public void GetUserRoleEnumSwitchStatementBenchmark()
=> GetUserRoleEnumSwitchStatement(_role_enums[Random.Shared.Next(4)]);
public static string GetUserRoleEnumSwitchStatement(UserRole role) {
switch (role)
{
case UserRole.Admin: return "Admin";
case UserRole.Editor: return "Editor";
case UserRole.Viewer: return "Viewer";
default: return "Unknown";
};
}
// This variation uses a switch expression with the enum.
[Benchmark]
public void GetUserRoleEnumSwitchExpressionBenchmark()
=> GetUserRoleEnumSwitchExpression(_role_enums[Random.Shared.Next(4)]);
public static string GetUserRoleEnumSwitchExpression(UserRole role)
=> role switch
{
UserRole.Admin => "Admin",
UserRole.Editor => "Editor",
UserRole.Viewer => "Viewer",
_ => "Unknown"
};
// This variation uses a dictionary with the enum.
[Benchmark]
public void GetUserRoleDictionaryBenchmark()
=> GetUserRoleDictionary(_role_enums[Random.Shared.Next(4)]);
public static string GetUserRoleDictionary(UserRole role)
=> _roleenums.TryGetValue(role, out var result) ? result : "Unknown";
// This variation uses a switch expression with an int.
[Benchmark]
public void GetUserRoleSWitchExpressionBenchmark()
=> GetUserRoleSWitchExpression(_role_ids[Random.Shared.Next(4)]);
public static string GetUserRoleSWitchExpression(int roleId)
=> roleId switch
{
1 => "Admin",
2 => "Editor",
3 => "Viewer",
_ => "Unknown"
};
// This variation uses a switch statement with an int.
[Benchmark]
public void GetUserRoleSWitchStatementBenchmark()
=> GetUserRoleSWitchStatement(_role_ids[Random.Shared.Next(4)]);
public static string GetUserRoleSWitchStatement(int roleId)
{
switch (roleId)
{
case 1: return "Admin";
case 2: return "Editor";
case 3: return "Viewer";
default: return "Unknown";
};
}
}
Method Mean Error StdDev Ratio Gen0 Allocated
GetUserRoleIfElse 10.62 ns 0.129 ns 0.114 ns 1.00 - -
GetUserRoleProposedDictionary 58.64 ns 0.974 ns 0.911 ns 5.52 0.0215 216 B
GetUserRoleStaticDictionary 12.10 ns 0.070 ns 0.058 ns 1.14 - -
GetUserRoleEnumGetName 41.87 ns 0.755 ns 0.706 ns 3.94 0.0042 42 B
GetUserRoleEnumToString 26.46 ns 0.453 ns 0.402 ns 2.49 0.0024 24 B
GetUserRoleEnumSwitchStatement 14.72 ns 0.278 ns 0.260 ns 1.39 - -
GetUserRoleEnumSwitchExpression 14.90 ns 0.190 ns 0.178 ns 1.40 - -
GetUserRoleDictionary 12.25 ns 0.220 ns 0.195 ns 1.15 - -
GetUserRoleSWitchExpression 13.85 ns 0.107 ns 0.089 ns 1.30 - -
GetUserRoleSWitchStatement 13.63 ns 0.109 ns 0.097 ns 1.28 - -

In this post, Isha Fatima claims:

π—ͺπ—΅π˜† π—Άπ˜€ π—§π—΅π—Άπ˜€ π—•π—²π˜π˜π—²π—Ώ?

  • Faster Execution β†’ Dictionary lookups are O(1), while if-else is O(n) in worst cases.
  • Cleaner & Readable Code β†’ The logic is more compact and easier to update.
  • Scalability β†’ If new roles are added, just update the dictionaryβ€”no need to modify complex conditions!

Let's pick it apart:

Is it faster? Hell no! You can reason about this even without benchmarking it: The proposed solution is horrible because it allocates a dictionary each and every time the method is invoked. This involves memory allocation(s), building the dictionary (calculating hashes and the lookup table) each and every invocation.

Ok, but what if we make the dictionary static and assume Isha actually meant that? Well, then still each and every invocation a hash will have to be calculated to do the lookup. However, in the specific case of an int the hash will likely be the int making it a very cheap hash. But that still doesn't make Isha's advice a good advice; for any other given type of lookup (e.g. string) numbers will (wildly) vary. It's just not a good idea to make a general statement like this.

Cleaner and more readable code is pretty subjective in this particular case.

Scalability... I fail to see how adding another "if/else" to the code would be more 'complex' than the dictionary. Both read perfectly fine. However, the "better" approach doesn't provide any compile-time safety where one of the, often proposed in the comments, enum-variants of this implementation will. When these are used the compiler can help out and tell you that you need to forgot to add a case for a newly added value. That said, the "if-else-else" approach doesn't provide much compile-time safety either.

As we can see, the proposed method performs 5.52 times worse and allocates 216 bytes each invocation. The 'improved' static dictionary still performs 1.15 times worse than the baseline (so next time maybe run a benchmark before posting misinformation Isha?).

As you can see, actually the "if-else-else" performs best over all other alternatives!

For shits'n'giggles I ran the test with 64 values. Here are the results:

Method Mean Error StdDev Ratio Allocated Alloc Ratio
IfElse 19.745 ns 0.1915 ns 0.1698 ns 1.00 - NA
SwitchExpression 15.116 ns 0.1985 ns 0.1759 ns 0.77 - NA
SwitchStatement 15.069 ns 0.2882 ns 0.2695 ns 0.76 - NA
Dictionary 7.272 ns 0.1470 ns 0.1303 ns 0.37 - NA

The tests for above results and a lot more results and a graph can be found here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment