Are you tired of feeling like your code is meaningless? Sick of people not referring to your code as “black magic,” or struggling to gain recognition from your boss for writing sufficiently obfuscated code?
If you’ve answered “yes” to any of the above, Reflection might be for you!
Reflection
I’ve talked about reflection a couple of times before. It allows you to dive deep into a type and get out information at run time. This can be really handy, especially if you’re trying to make things easier for future programmers to add more functionality in the future without having to reinvent the entire process.
Recently I had created a series of classes that each contained a class with constant strings. These constant strings were an attempt to consolidate magic strings, which I hate. So, you can statically use the constant strings without instantiating the class. Basically, something like this:
public class MyProtocol | |
{ | |
public class Incoming | |
{ | |
// Define const strings here to avoid magic strings | |
public const string Hello = "Hello"; | |
public const string World = "World"; | |
} | |
// ... other implementation ... | |
} |
public MyProtocol _protocol = new MyProtocol(); | |
public void SendToProtocol() | |
{ | |
// Rather than have a string "Hello" here, we can use the const | |
// string inside of the Incoming class inside of MyProtocol | |
_protocol.Send(MyProtcol.Incoming.Hello); | |
} |
Testing
I’ll leave the implementation details up to you, but suppose the Protocol needs to be able to handle each of the strings defined in the Incoming class and we want to write some tests to make sure that this is true.
You could easily enough write a generic test that has a series of inputs to test ([InlineData] if you’re using XUnit):
[Theory(DisplayName="Protocol should do a thing with each Incoming")] | |
[InlineData(MyProtocol.Incoming.Hello)] | |
[InlineData(MyProtocol.Incoming.World)] | |
public void Protocol_DoesThing(string incoming) | |
{ | |
Assert.True( . . . ); | |
} |
But what happens when you inevitably add another const string? You’ll have to remember to go update that test. I dunno about you, but I would almost certainly forget.
Testing with Reflection
Luckily, we can use reflection to create the test cases for us. We can easily lift the reflection that we wrote earlier to enumerate the constant strings in a class and add one extra step:
public static IEnumerable<object[]> | |
{ | |
get | |
{ | |
// The irony of using a magic string, "Incoming" here is noted. | |
// GetNestedType gets the class by name that is defined inside of the type. | |
foreach (string command in typeof(MyProtocol).GetNestedType("Incoming") | |
.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) | |
.Where(f => f.IsLiteral && !f.IsInitOnly && f.FieldType == typeof(string)) | |
.Select(f => (string)f.GetValue(null))) | |
{ | |
yield return new object[] { command }; | |
} | |
} | |
} |
GetNestedType will let you get the class by name that is defined inside of the parent type.
GetNestedTypes will enumerate the classes that are defined inside of the parent type (in case you’re not sure which one you want).
I adapted this from an even more generic version where there were multiple classes that implemented an interface, and that interface was then used as a generics parameter to the test class. Since each class that implemented the interface also had (by convention) a class Incoming, I was able to use this technique to automate the testing.