Overriding ToString() for debugging and logs - sho

2019-04-23 19:48发布

问题:

I'm designing a .NET library that will be used by other developers making both web and desktop applications. I'm overriding ToString() in various classes to provide information for debugging purposes and for inclusion in application log files.

Some of my classes contain numbers and dates.

Consider an object that contains a DateTime called date and a double called value (and maybe other fields as well)... If I override that object's ToString(), I might want to do something like:

public override string ToString() {
    return "ObjectName[date=" + date + ", value=" + value + "]";
}

But that will include the result from date.ToString() and value.ToString(), which will give me strings that are localized according to Thread.CurrentThread.CurrentCulture.

And that, to me, seems wrong. The strings returned by my ToString() implementations are meant for debugging and log messages, not for user interfaces. So I think returning localized strings could only confuse matters. If "2,341" appears in log files, a developer would need to know the value of the thread's CurrentCulture to know whether it meant 2 thousand 341 or 2 point 341. It's even more confusing with dates - a string like xx/xx/xxxx could be dd/mm/yyyy or mm/dd/yyyy. I don't want my ToString() methods to create that sort of ambiguity.

So my inclination is to make my ToString() methods culture-insensitive, to ensure that all returned strings are consistent across cultures. For example, internally my ToString() methods would do like value.ToString(CultureInfo.InvariantCulture) to format a number.

However, the norm in .NET libraries seems to be to use CurrentCulture in default no-args ToString() implementations. Many objects that have a ToString() method also have a ToString(IFormatProvider) method as well. It's as if the designers of .NET decided that the default use of ToString() should be for user-interface display (localized), and that debugging and logs (for which you'd need to call ToString(CultureInfo.InvariantCulture)) are secondary.

So if I implement my ToString() methods in a culture-insensitive way, I feel I'd be going against the grain somewhat. But it seems silly to create culture-sensitive strings by default, when culture-sensitivity makes no sense for log files or debugging.

I could use the CurrentCulture to represent numbers and dates in my default ToString() implementations, and also provide ToString(FormatProvider) methods so that people can get a culture-insensitive string for use in log files etc. But that seems dumb as it's just forcing developers to write more code to get the culture-insensitive string that I'm guessing they'll want (whether they've considered it or not).

The bottom line is that a string like ObjectName[value=12.234, date=2011-10-01] shouldn't ever appear in a user interface, so why would a programmer ever want it to be localized?

I've been reading the advice in Framework Design Guidelines on implementing ToString(). Some of the advice seems somewhat contradictory. For example:

I consider ToString an especially dangerous method to provide for UI-generic types, because it's likely to be implemented with some specific UI in mind, making it useless for other UI needs. To avoid tempting myself in this way, I prefer to make my ToString output as geeky as possible to emphasize that the only "humans" that should ever see the output are "developer humans" (a subspecies all their own).

and

The most important value of ToString is that the debugger uses it as the default way of displaying the object.

don't really seem to fit with:

DO string formatting based on the current thread culture when returning culture-dependent information.

and

use the CultureInfo instance returned by a thread's CurrentCulture property to format any numeric or date

I'm all for following the guidelines, and writing APIs that do what programmers expect. But if ToString() is for programmers, then it seems silly to localize it. The C# compiler won't let a programmer write a double literal using a system-dependent decimal separator, so surely ToString() methods written for programmers should behave similarly?

What do you think? When the output from ToString() is not intended for use in a user-interface, should the numbers and dates within it be localized or not?


Update

I did some tests using the DebuggerDisplay attribute, and it looks like, by default, it formats numbers in a culture-insensitive way.

[DebuggerDisplay("[value={value}]")]
class DoubleHolder {
    private double value;
    DoubleHolder(double value) {
        this.value = value;
    }
}

[TestMethod]
public void TestDebuggerValue() {
    DoubleHolder s = new DoubleHolder(12345678.12345);
    string doubleString = TestDouble.ToString();
    CultureInfo current = Thread.CurrentThread.CurrentCulture;
    Thread.CurrentThread.CurrentUICulture = current;
    CultureInfo ui = Thread.CurrentThread.CurrentUICulture;
    Debugger.Break();
}

Run that test in the debugger and, when it breaks you can see that the double contained in DoubleHolder is formatted with a . as a decimal separator.

Then close Visual Studio, change your windows Regional Options for Standards and Formats to French, say, and run the test again. You'll see that doubleString has a , as the decimal separator, but the the debugger still shows the double in DoubleHolder with a .

I would have liked to test this on a proper French version of Windows, with Visual Studio in French. In Visual Studio, if you go to Tools -> Options -> Environment -> International Settings, you can set the Language to "Same as Microsoft Windows". By default on my installation it was set to "English". But to get Visual Studio in French you need to have Windows in French, and my version of Windows seems to be English only. If anyone has a French Windows, or any other locale that uses , as a decimal separator, it'd be great if you could just check whether the debugger uses . or , as the decimal separator in formatted doubles.

I'm wondering if Thread.CurrentThread.CurrentUICulture might make a difference to how the Visual Studio debugger shows things, and I'm not sure that setting it like I do above would be the same as running Visual Studio completely in French.

But from the above it does look like the debugger consistently uses . as the decimal separator. This implies to me that a culture-independent ToString() method is fine, probably preferable, if it's intended for debugging purposes.

回答1:

For debugging you want to look into Debugger Display Attributes, not use ToString().

[DebuggerDisplay("{Name} [date={Date}, value={Value}]")]
public class MyClass {
    // ...
}

As for localization of ToString(), I will pass on the opportunity to comment.



回答2:

IMO the only two important things here are:

  • that you have thought about the intended usage, and are applying culture appropriately
  • that the behaviour is clearly documented

If the intended usage of this is logging, then invariant seems fine. If the intended usage was IDE display, then I'd leave it localized to the current-thread.

If unsure, consider adding a ToString(CultureInfo) method, and then the caller can do as they wish. In that scenario I'd assume tw default is the current-culture, and that the logging code should explicitly request invariant (perhaps also via IFormattable).