3
\$\begingroup\$

I needed a better string formatting options like ToUpper and ToLower but I also wanted to be able to use the default formattings so I created a custom formatter. It's main purpose is to support a template engine where the user should be able to use different formattings like upper or lower case. I still however couldn't figure out how to be make the date time upper case.

public class ExtendableFormatter : IFormatProvider, ICustomFormatter
{
    public IDictionary<Type, IFormatProvider> FormatProviders { get; set; }

    public ExtendableFormatter()
    {
        FormatProviders = new Dictionary<Type, IFormatProvider>
        {
            { typeof(Byte), CultureInfo.InvariantCulture.NumberFormat },
            { typeof(SByte), CultureInfo.InvariantCulture.NumberFormat },
            { typeof(Int16), CultureInfo.InvariantCulture.NumberFormat },
            { typeof(Int32), CultureInfo.InvariantCulture.NumberFormat },
            { typeof(Int64), CultureInfo.InvariantCulture.NumberFormat },
            { typeof(UInt16), CultureInfo.InvariantCulture.NumberFormat },
            { typeof(UInt32), CultureInfo.InvariantCulture.NumberFormat },
            { typeof(UInt64), CultureInfo.InvariantCulture.NumberFormat },
            { typeof(Single), CultureInfo.InvariantCulture.NumberFormat },
            { typeof(Double), CultureInfo.InvariantCulture.NumberFormat },
            { typeof(Decimal), CultureInfo.InvariantCulture.NumberFormat },
            { typeof(DateTime), CultureInfo.InvariantCulture.DateTimeFormat },
            { typeof(String), new StringCaseFormatter() }
        };
    }

    public object GetFormat(Type formatType)
    {
        return formatType == typeof(ICustomFormatter) ? this : null;
    }

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        var fp = (IFormatProvider)null;
        if (FormatProviders.TryGetValue(arg.GetType(), out fp))
        {
            formatProvider = fp;
        }

        format = string.IsNullOrEmpty(format) ? string.Empty : ":" + format;
        var result = string.Format(formatProvider, "{0" + format + "}", arg);
        return result;
    }

}

public class StringCaseFormatter : IFormatProvider, ICustomFormatter
{
    public object GetFormat(Type formatType)
    {
        return formatType == typeof(ICustomFormatter) ? this : null;
    }

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        switch (format)
        {
            case "U": return arg.ToString().ToUpper();
            case "L": return arg.ToString().ToLower();
            default: return arg.ToString();
        }
    }
}

Example:

string.Format(
    new ExtendableFormatter(),
    "foo {0:U} baz {1,-5:f1} {2:ddMMMyy}",
    "qux", 1.234f, DateTime.Now).Dump();

Result:

foo QUX baz 1.2 05Jun16

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$
public IDictionary<Type, IFormatProvider> FormatProviders { get; set; }  

This is bad. Having a public setter for the dictionary will let a user of that code pass in null resulting in the Format() method throwing a NullReferenceException.


You don't check the passed in parameters of the Format() method if they are valid. By omiting the validation of the arguments you are exposing implementation details for the case they aren't valid. E.g if the passed object arg is null then the call to arg.GetType() will throw and exposes that you are requesting the type of that object.

You only want to show the user that the passed in arguments are invalid, but you shouldn't let the code do it for you.

\$\endgroup\$
1
  • \$\begingroup\$ oops, you're right. I thought the framework would prevent a null from being passed to the Format method but I just tested it and it didn't. \$\endgroup\$
    – t3chb0t
    Commented Jun 6, 2016 at 11:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.