6

With the addtion of Extension methods to C# we've seen a lot of them crop up in our group. One debate revolves around extension methods like this one:

public static class StringExt
{
    /// <summary>
    /// Shortcut for string.Format.
    /// </summary>
    /// <param name="str"></param>
    /// <param name="args"></param>
    /// <returns></returns>
    public static string Format(this string str, params object[] args)
    {
        if (str == null) return null;
        return string.Format(str, args);
    }
}

Does this extension method break any programming best practices that you can name? Would you use it anyway, if not why? If I renamed the function to "F" but left the xml comments would that be epic fail or just a wonderful savings of keystrokes?

ChrisF
  • 38,948
  • 11
  • 127
  • 168
P. Roe
  • 593

4 Answers4

14

It seems pretty pointless to me.

It's more code that you have to maintain - and in a year's time no one will remember the debates you had and half your code will use string.Format the other half will use this method.

It's also distracting you from the real task of developing software that solves real problems.

You've probably wasted more money by having these discussions that it would have cost the company to buy ReSharper licenses for everyone.

ChrisF
  • 38,948
  • 11
  • 127
  • 168
6

FxCop complains when using String.Format(...) without an IFormatProvider. So it makes sense to have extension methods like or FormatInvariant(...) or FormatLocal(...) because it saves callers from being forced to fiddle around with the System.Globalization namespace a lot of times.

2

Extension methods have one major disadvantage. Say you have an extension method like this:

public static string ToRomanNumeral(this int number)
{
    // whatever whatever whatever
}

You do this in .NET 4. Then in in .NET 5 Microsoft responds to the outpouring of Roman-numeral love among developers by adding a method to int:

public string ToRomanNumeral()
{
    // whatever whatever whatever
}

When you recompile your code against .NET 5, suddenly, and without any error or warning cropping up, Microsoft's Roman numeral code will be run instead of yours.

This is a silly example off the top of my head, but it can happen in any case where you use an extension method, then later add that same method to the class the extension method took as a parameter. In case of a conflict, methods on a class always take precedence over extension methods, and there's absolutely no notification of the conflict.

So...just something to keep in mind before you go extension-method crazy.

Kyralessa
  • 3,724
1

I really dislike the fact that String.Format uses the current culture. This means that the output depends on the user's system. For example, if I code this quickly without thinking about cultures:

var now = DateTime.Now;
string json = String.Format("{{ 'Date': \"{0}\" }}", now);

I can test this, and it works great on my system.

{ 'Date': "9-11-2013 13:37:00" }

And after a while I forget about this little snippet of code, and it ends up in my product. Then some user in Saudi Arabia uses program, and everything fails since it produced:

{ 'Date': "06/01/35 01:37:00 ?" }

To prevent such bugs I want String.Format to use the invariant culture by default, and if I want to use a specific culture I can specify the culture explicitly. This will prevent all such bugs.


In C# 6 you can use string interpolation to do string formatting. It's still current culture specific though.

string json = $"{{ 'Date': \"{now}\" }}";

Luckily there is a way to make it culture invariant. Statically import the System.FormattableString namespace and use the Invariant method, like this:

using static System.FormattableString;

string json = Invariant($"{{ 'Date': \"{now}\" }}");

For C# 5 and earlier, I wrote myself a set of extension methods to do that for me. In the process, it also provides a much nicer syntax.

public static string FormatInvariant(this string format, params object[] args)
{
    if (format == null) throw new NullReferenceException("format");
    if (args == null) throw new NullReferenceException("args");

    return FormatWith(format, CultureInfo.InvariantCulture, args);
}

public static string FormatCurrentCulture(this string format, params object[] args)
{
    if (format == null) throw new NullReferenceException("format");
    if (args == null) throw new NullReferenceException("args");

    return FormatWith(format, CultureInfo.CurrentCulture, args);
}

public static string FormatWith(this string format, IFormatProvider provider, params object[] args)
{
    if (format == null) throw new NullReferenceException("format");
    if (provider == null) throw new NullReferenceException("provider");
    if (args == null) throw new NullReferenceException("args");

    return String.Format(provider, format, args);
}

(Get the full source code with comments and Code Contracts.)

Example usage:

string json = "{{ 'Date': \"{0}\" }}".FormatInvariant(now);