I’m having real problems today with some C# code for a an ICustomFormatter … it works fine if I call String.Format(...) but fails miserably if I use .ToString( ... ) and I can’t figure out why. I’ll paste the code in here, and continue the discussion below it.

namespace RMST.Utilities {
   using System;
   using System.Reflection;

   #region NUnit Tests
   using NUnit.Framework;
   [TestFixture]
   public class EnumTest {

      // Just a simple sample enumeration
      public enum Currencies {
         [Text("US Dollars")]
         USD,
         [Text("British Pounds")]
         GBP,
         [Text("Costa Rican Colones")]
         CRC,
         [Text("European Dollar")]
         EUD
      }

      [Test]
      public void SimpleTest() {
         Assert.AreEqual( "US Dollars", Currencies.USD.ToString( new TAFormat() ) );
         Assert.AreEqual( "US Dollars", String.Format(TAFormat.Formatter, "{0}", Currencies.USD ) );
      }

   }
   #endregion


   // We create a "Text" attribute
   public class TextAttribute : Attribute {
      public readonly string Text;
      public TextAttribute( string text ) {
         Text = text;
      }
   }

   public class TAFormat : IFormatProvider, ICustomFormatter {
      public static TAFormat Formatter = new TAFormat();

      static string GetEnumText( Enum arg ) {
         // get the item we want from the list of the members of the enum
         Type enumType = arg.GetType();
         MemberInfo[] members = enumType.GetMember(arg.ToString());

         // if we found it, get it's text attribute, or return null
         if( members != null && members.Length > 0 ) {
            object[] attribs = members[0].GetCustomAttributes(typeof(TextAttribute), false);
            if( attribs.Length > 0 ) {
               return ((TextAttribute)attribs[0]).Text;
            }
         }
         return null;
      }

      static object GetEnumValue(string text, Type enumType) {
         // get a list of the members of the enum
         MemberInfo[] members = enumType.GetMembers();
         // iterate through checking the TextAttribute until we find a match
         foreach( MemberInfo mi in members ) {
            object[] attrs = mi.GetCustomAttributes(typeof(TextAttribute), false);
            if( attrs.Length == 1 && ((TextAttribute)attrs[0]).Text == text ) {
               return Enum.Parse(enumType, mi.Name);
            }
         }
         throw new ArgumentOutOfRangeException("text", text,
                  "The text passed does not correspond to an attributed enum value");
      }

      #region IFormatProvider Members
      // String.Format calls this method to get an instance of an
      // ICustomFormatter to handle the formatting.
      public object IFormatProvider.GetFormat (Type service) {
         if (service == typeof (ICustomFormatter)) {
            return this;
         }
         else {
            return null;
         }
      }
      #endregion

      #region ICustomFormatter Members
      // After String.Format gets the ICustomFormatter, it calls
      // this format method on each argument.
      public string ICustomFormatter.Format (string format, object arg, IFormatProvider provider) {
         string formattedString = null;
         if( null == arg ) {
            throw new ArgumentNullException();
         }         

         // Custom Formatting
         if( arg is Enum && (format == null || format.Equals("T"))) {
            formattedString = GetEnumText( arg as Enum );
         }

         // Default Formatting
         if( null == formattedString ) {
            DefaultHandler( format, arg, provider);
         }

         return formattedString; 
      }


      private string DefaultHandler(string format, object arg, IFormatProvider provider) {
         // Default handling ... wish there was a SystemDefaultFormatter.Format( ... )
         if (arg is IFormattable) {
            formattedString = ((IFormattable)arg).ToString( format, formatProvider);
         } else { // if arg != null, but we already tested for that
            formattedString = arg.ToString();
         }
      }
      #endregion
   }
}
 

Hopefully you’re able to see what I’m trying to do here. Basically, I’m creating an attribute-based custom string conversion for Enum types. The idea is that each member of the Enum gets a TextAttribute which specifies how it should be converted to text. The EnumStringFormat custom formatter checks for that attribute and uses it, or else uses the default method (which returns the “Name” of the enum member). As a side note my “Currencies” example test code is just that: an example. It has nothign to do with the Currency type, it’s merely an example enumeration I thought would be simple to grasp.

Anyway, if you put that code into a C# file and compile it and run that test through NUnit, you will see that String.Format(TAFormat.Formatter, "{0}", Currencies.USD ) returns “US Dollars” but Currencies.USD.ToString( TAFormat.Formatter ) returns “USD” (the Name).

If anyone has any insight on this, I’d love to hear it. The whole point of the excercise was supposed to be to simplify the attribute trick to just passing the formatter (as opposed to some other examples which required calling a static member of a custom class).