It seems like once a project gets to a certain size, magic strings – that is, character values hard-coded into the application – become inevitable. When a piece of software needs to read data generated by another piece of software, the use of magic strings becomes a requirement for sanity. The common pattern for dealing with magic strings is to encapsulate them in a constant:
public const String FRUITS_APPLE = "Apple";
That works fine when you have a small number of constants, but what happens when you have 10, 20, or more? Furthermore, what do you do when subsets of them are logically related to each other? One approach is to name them according to that logical group:
public const String APPLES_RED_DELICIOUS = "Red Delicious";
public const String APPLES_GALA = "Gala";
public const String APPLES_FUJI = "Fiji";
public const String ORANGES_BLOOD = "Blood";
public const String ORANGES_NAVEL = "Navel";
public const String ORANGES_VALENCIA = "Valencia";
public const String LEMONS_MEYER = "Meyer";
public const String LEMONS_EUREKA = "Eureka";
public const String LEMONS_LISBON = "Lisbon";
What I’d really prefer to do is to create enumerations for each of these groups. The problem is an enumeration can’t have string values behind it:
public enum Apples
{
RedDelicious = "Red Delicious",
Gala = "Gala",
Fuji = "Fuji"
}
This will return a compile-time error of "Cannot implicitly convert type ‘string’ to ‘int’".
I’ve gotten around this in the last few years by creating one or more subclasses, one for each logical grouping, and then creating constants within. I call these "constants-classes":
public class Apples
{
public const String RedDelicious = "Red Delicious";
public const String Gala = "Gala";
public const String Fuji = "Fiji";
}
public class Oranges
{
public const String Blood = "Blood";
public const String Navel = "Navel";
public const String Valencia = "Valencia";
}
public class Lemons
{
public const String Meyer = "Meyer";
public const String Eureka = "Eureka";
public const String Lsibon = "Lisbon";
}
In code, then, I can access these values just like I would the values of a true enumeration:
String CurrentApple = Apples.RedDelicious;
String CurrentOranage = Oranges.Navel;
This pattern has served me well over the years, but I ran into a new challenge with it in my current project. The application imports XML messages pulled off of a queue, and there are a small number of major types of entities that need to be processed. (For the purposes of this post, I’ll continue my fruit analogy and say that the types of entities are apples, oranges, and lemons; I’m not really writing a fruit app.)
Large chunks of the XML for these different types are actually the same, so I built a series of System.Xml.Linq.XElement extension methods that operate on a specific slice of the XML. When I import a given entity – let’s say a lemon – I break off the appropriate piece of the full XML document for that lemon, then call the appropriate extension method to de-serialize it and turn it into a strongly-typed object (“FruitProperties” in this case):
public static FruitProperties ExtractProperties(this XElement XmlToProcess)
{
}
The XML was largely made up of collections of <Attribute /> blocks, each of which contained the same basic components. One of these components was the "name" of the attribute – a unique handle that will never change, and allows me to safely identify the various bits of data for an entity. I defined several constants-classes to centralize these names.
Let’s say I had attributes like "PeelTexture", "Color", and "HasSeeds". The challenge arose when I realized that although all of my entity types had these attributes, the actual attribute names used in the apple-XML versus the orange-XML were different. For example, the PeelTexture attribute might appear as "Peel_Texture" for an apple, but "Orange_Peel" for an orange.
My first attempt to address this was to try to move the constants to an “IFruit” interface. My thought was to define the template for the constants, and then I would "implement" them (assign the real, apple-, orange-, and lemon-specific values) in the concrete classes that implemented that interface. However, I got another compile-time error because C# (and presumably all .NET languages) don’t allow interfaces to have static members, fields, or properties.
I tried several other approaches before I arrived at this slightly-hacky solution. First, I defined my constants-classes to hold all of my entities’ unique values:
public class Constants
{
public class Apple
{
public const String PeelTexture = "Peel_Texture";
public const String Color = "Color";
public const String HasSeeds = "Apple_HasSeeds";
}
public class Orange
{
public const String PeelTexture = "Orange_Peel";
public const String Color = "Orange_Color";
public const String HasSeeds = "Has_Seeds";
}
public class Lemon
{
public const String PeelTexture = "PeelTexture_2";
public const String Color = "Color_Lemon";
public const String HasSeeds = "Has_Any_Seeds";
}
}
I then modified my generic ExtractProperties method to take a Type object. This object would be the actual constants-class for the entity being processed:
public static FruitProperties ExtractProperties(this XElement XmlToProcess, Type FruitConstants)
{
}
This method would be invoked as follows:
XDocument MyDocument;
FruitProperties MyProperties;
// Load XML into MyDocument here
XElement SliceToImport = MyDocument.Element("Root").Element("Properties");
MyProperties = SliceToImport.ExtractProperties(typeof(Constants.Apple));
Within the ExtractProperties method, I then naively tried to do this to get the actual value of the constant:
String FieldToExtract = FruitConstants.PeelTexture;
But ExtractProperties had no idea what "FruitConstants" type really was, so this also throws a compile-time error. So, I then created a simple enumeration that became my "template" for my constants-classes. This enumeration’s members would have the same name as the constants defined in my classes:
public enum FruitConstantsTemplate
{
PeelTexture,
Color,
HasSeeds
}
I could use that template – and a little trust – to pull the desired value out of the constants-class by its name using reflection. I wrote a custom function to do just that:
public static String GetFieldName(Type T, String ConstantName)
{
FieldInfo CandidateConstant;
// Validate the parameters being passed in
ConstantName = (ConstantName ?? "").Trim();
if (String.IsNullOrEmpty(ConstantName)) { throw new ConstantException(); }
if (T == null) { throw new ClassException(); }
// Get the list of constants from type T using reflection
var Constants = T.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
// Find the one I'm looking for, based on name.
CandidateConstant = Constants.FirstOrDefault(c => c.Name == ConstantName);
if (CandidateConstant == null) { throw new ConstantNotFoundException(ConstantName, T.Name); }
return (String)CandidateConstant.GetValue(CandidateConstant);
}
I modified ExtractProperties to use this new function:
String FieldToExtract = GetFieldName(typeof(Constants.Apple), FruitConstants.PeelTexture.ToString());
I say "a little trust" because this function only returns a constant’s value if both of the following hold true:
- the FruitConstantsTemplate defines it; and
- the constants-class passed in as type T contains it
This sort of thing is what an interface would enforce for methods (for example) at compile-time. With this approach, however, there’s nothing to stop me from adding a value to the enumeration, and forgetting to add it to the constants-classes (or vice versa). There’s also nothing at compile time to stop me from passing in a completely invalid type to GetFieldName(), such as "String" or "DateTime" (again, something a true interface would catch at compile-time).
This approach does have its share of advantages, though:
- It allows me to concentrate the "generic-ness" in one function (rather than having the same basic logic duplicated in several functions, differing only by the constants it was using).
- It also allows me to define and continue using the class-constants just as I have always done. The only catch is when I add a constant, I have to remember to update the template with it.
***
While I have a functional solution, I also have a nagging thought. This is organic solution, grown (no pun intended) around the constraints of the framework and the language, but is there a better way to accomplish what I’m trying to do?
A question to revisit for my next project.