Formatting enums using CustomFormatters
C# enums are named constants, making code easier to write and read. But the enum names should not be written to the UI. This post shows two methods of formatting an enum to a string suitable for the UI.A C# enum is simply a named constant: Let’s say you are working on a application where you need to represent the fundamental forces in some way (if you cannot remember what that is, don’t worry, this could be anything really – fruits, cars, countries). The four forces are represented with a number, from 1 to 4 - and the C# enum construct lets you attach a name to each number:
public enum Force : byte
{
[Description("Gravity")]
Gravity = 1,
[Description("Electromagnetic force")]
Electromagnetic = 2,
[Description("Weak nuclear force")]
Weak = 3,
[Description("Strong nuclear force")]
Strong = 4
}
Each enum pair is also given a description using the
DescriptionAttribute
(located in System.ComponentModel
). I will return to this attribute later.Now, if you want to output an enum name to the UI (e.g. the console or a Webpage), the simplest way is to do the following:
Force f = Force.Strong;
Console.WriteLine(String.Format("Force: {0}", f));
The
String.Format
method simply calls f.ToString()
, and inserts the resulting string in the overall string. The result is as expected:Force: Strong
But this solution should be avoided. The enum name is a coding “shortcut”, and should never be displayed on the UI. That is because special characters and spaces are not allowed in the enum names, and localization is not supported.
The means that if I would like to output Strong nuclear force instead of the enum name Strong, I would need another way. This could be accomplished but outputting the description instead of the name. This requires some coding, but can be done like this:
public static string GetDescription<T>(T value)
{
DescriptionAttribute[] d = (DescriptionAttribute[])(typeof
(T).GetField
(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute),
false));
return d[0].Description;
}
This method takes an enum type and an enum value, and returns the
DescriptionAttribute
as a string:Console.WriteLine(String.Format("Force: {0}", GetDescription<Force>(f)));
And the result is:
Force: Strong nuclear force
While this method is rather common, it is not a favorite of mine. The reason is, that the
DescriptionAttribute
is not a suitable place to put UI strings because it can only accessed
through reflection. The method is not very flexible, say you need to
format the same enum in two different ways in different parts of you
application, since you cannot have more than one description to an enum
pair.The best way is, in my opinion, to use a
CustomFormatter
. This needs a little explanation: Formatting in C# is done by objects that implement the IFormatProvider
interface. This interface contains only one method, object GetFormat(Type formatType)
. When a method employs a IFormatProvider
, it will ask the GetFormat
method for the specific formatter needed. If the method needs to format some numbers it will call GetFormat
and ask for a NumberFormatInfo
formatter. This object contains all the methods needed to format numbers.On a side note, notice that the most common
IFormatProvider
is CultureInfos
: They got a GetFormat
method, and asked for a NumberFormatInfo
object, it will return a culture specific NumberFormatInfo
object. The CultureInfo
object contains other formatters as well, formatters for formatting Dates, formatters for comparing strings and so on. The IFormatProvider
is a factory, that will construct a whole range of different formatters when asked to.Well, back to the enums: When the
String.Format
method is called with a IFormatProvider
, it will first try to retrieve a type of formatter, that implementing the ICustomFormatter
interface. It will then use this CustomFormatter
to format each of the parameters.The
ICustomFormatter
interface contains a single method:
string Format(string format, object o, IFormatProvider formatProvider).
This method is called by String.Format for each parameter. The first
parameter takes a format string. This string can be added in the
String.Format method, like:String.Format(myFormatProvider, "Force: {0:R}", f);
Then the string “R” is passed to the CustomFormatter. This can be used to support different kinds of formatting with the same
CustomFormatter
. The object o
is the object to be formatted, and the formatProvider is the formatProvider that actually constructed this CustomFormatter
.Now, let’s create a
CustomFormatter
that will format the Force
enum:
public class ForceCustomFormatter : ICustomFormatter
{
public string Format(string format, object o, IFormatProvider
formatProvider)
{
if (!o.GetType().Equals(typeof(Force)))
{
return o.ToString();
}
else
{
Force force = (Force)o;
switch (force)
{
case Force.Electromagnetic:
return "Electromagnetic force";
case Force.Strong:
return "Strong nuclear force";
case Force.Weak:
return "Weak nuclear force";
default:
return force.ToString();
}
}
}
}
The formatter is simple: If the supplied object is anything but a
Force
enum, the formatter will simply call the objects ToString
method. Force
objects is on the other hand formatted using a switch
statement.Last, we will need a provider that will construct a
ForceCustomFormatter
:public class ForceFormatProvider : IFormatProvider
{
public object GetFormat(Type formatType)
{
return (formatType == typeof(ICustomFormatter)) ? new
ForceCustomFormatter
() : null;
}
}
This method constructs a
ForceCustomFormatter
is asked for a ICustomFormatter
.Let’s summarize: The call
Console.WriteLine(String.Format(new ForceFormatProvider(), "Force: {0}", f));
Needs to format the
Force
enum f
. It will call the ForceFormatProvider to retrieve a CustomFormatter
. Then it will pass the parameters on at a time to the CustomFormatter
,
and use the resulting string. This is, in my opinion, the best way to
format enum – and other objects as well. In reality, you want to make
formatters that can format more than one object. You will also want to
take a look at the part:
if (!o.GetType().Equals(typeof(Force)))
{
return o.ToString();
}
This is the most basic formatting option, and if the object – like e.g.
DateTime
supports formatting with ToString(string format, IFormatProvider)
, you should revert to this method instead
0 comments:
Post a Comment