Runtime: Allow custom format strings for boolean values

Created on 27 Apr 2016  路  29Comments  路  Source: dotnet/runtime

This is moved from User Voice https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/5748267-allow-custom-format-strings-for-boolean-values?tracking_code=59376d71f1665e5476be1026a8c3318e

I would like to be able to specify how string.Format displays the True and False values of Boolean variables.

For example I would like the following C# snippet to output "Boiler: On" rather than "Boiler: True":

bool boilerIsOn = true;
Console.WriteLine("Boiler: {0:On:Off}", boilerIsOn );

api-needs-work area-System.Runtime

Most helpful comment

My main reason for raising this in the first place was that I wanted to get the format string from a localized resource file. The resource files in the company I was working with at the time get sent off to third parties for translation. If you don't allow the formatting of the True/False value in the format string then we need to create three resource entries rather than one. Once this is scaled to a few thousand strings it becomes a bit of a bug bear.

All 29 comments

Is a custom format specifier the best solution here? Why not just an extension method, something like boolValue.ToOnOffString()?

It felt like it's missing. Creating an extension method is far more verbose than simply writing "On:Off" or "Yes:No".
As far as I recall Boolean.ToString has an overloaded implementation that accepts an IFormatProvider but does nothing with it.
The idea was that we could use the IFormatProvider to translate the boolean value into something meaningful.
So the string formatter will call ToString like so: arg.ToString(new BooleanFormatProvider(true_string, false_string).

As far as I recall Boolean.ToString has an overloaded implementation that accepts an IFormatProvider but does nothing with it.

That's due to bool implementing IConvertible. I fully agree with with @mellinoe though. I don't see the value in supporting custom formatters.

So the string formatter will call ToString like so: arg.ToString(new BooleanFormatProvider(true_string, false_string).

How is that an improvement over arg ? true_string : false_string?

It's a bit shorter I guess and it's consistent. If you allow other variables to be formatted, I see no reason why booleans shouldn't be as well.

My main reason for raising this in the first place was that I wanted to get the format string from a localized resource file. The resource files in the company I was working with at the time get sent off to third parties for translation. If you don't allow the formatting of the True/False value in the format string then we need to create three resource entries rather than one. Once this is scaled to a few thousand strings it becomes a bit of a bug bear.

I guess this depends on your workflow, how you are passing things around, and how your resources are stored, but wouldn't you only need two string resources for this, an "On" and an "Off" resource?

bool boilerIsOn = true; 
Console.WriteLine($"Boiler: {boilerIsOn.ToString(Strings.BoolOn, Strings.BoolOff)}");

(Assuming you write an extension ToString method)

Wouldn't it be easier to have the "On" and "Off" strings be separated anyways, for translating?

@mellinoe

Wouldn't it be easier to have the "On" and "Off" strings be separated anyways, for translating?

Depending on the localized strings and the languages you're localizing into, that might not be possible. For example, consider my native language, Czech:

| English | Czech |
| --- | --- |
| The boiler is on. | Kotel je zapnut媒. |
| The boiler is off. | Kotel je vypnut媒. |
| The pump is on. | Pumpa je zapnut谩. |
| The pump is off | Pumpa je vypnut谩. |

Notice that the terms for "on" and "off" are slightly different depending on the subject.

This means that something like Localize("The boiler is {0:on:off}.", boilerIsOn) would work (the localized string would be "Kotel je {0:zapnut媒:vypnut媒}."), but Localize("The boiler is {0}.", Localize(boilerIsOn ? "on" : "off")) wouldn't.

Good information, I can definitely see how that is preferable. @terrajobst How do you feel about this one?

Just a quick comment on the seperator for the True and False values - I think this should be a ; not a : as a parts seperator similar to numbers. e.g. Yes;No or On;Off

bool boilerIsOn = true;
Console.WriteLine("Boiler: {0:On;Off}", boilerIsOn );

and perhaps for the nullable case

bool? boilerIsOn = null;
Console.WriteLine("Boiler: {0:On;Off;Unknown}", boilerIsOn );

This would be similar to the existing https://msdn.microsoft.com/en-us/library/0c899ak8.aspx#SectionSeparator for numbers but with one section being True value only (False=>String.Empty), two sections being True Value;False Value and three sections being True Value;Flase Value;Null Value

Does this mean that you are going to have the localized values in the code?

I am not seeing a lot of value having this inside the string identifier. There will not be any intellisense for it there allowing you to easily make mistakes.

I would rather have an extension method that you control and lets you do what you need versus introducing this into the platform as a general purpose feature.

@terrajobst what do you think?

Another benefit of putting it in the platform is that reporting software like DevExpress' relies heavily on .NET format strings and I can see so many use cases from day one. This exact feature is something I have wished for.

To quote @jods4:

In particular, many apps (notably LOB) are meant for a single audience and not globalized. In that context one could use a string format like Qq yyyy and get Q4 2016. That's immensely helpful in contexts where you can only use a string format to control the output (such as many reporting and charting controls).

Solution should support the following styles also.
{0:On;Off;Unknown}
{True:Yes;False:No}
{true:Yes;yes:No}
{True:Yes;False:No;null:N/A} // ??? How to specify nullable Bool?

We have no plans to add custom format strings for bool into the core platform. Thank you for the suggestion, though.

I still believe it will be useful. :confused:

@stephentoub what does "have no plans" mean exactly? Why wouldn't we just go and file another case with this same suggestion in it?

@stephentoub Maybe a short sentence to explain why this is closed and why people shouldn't open it again?

Having format strings for bools:

  • Can be convienent;
  • Is consistent with most other primitive types having formatters;
  • @martingbrown gives an example where it helps with localization;
  • @jnm2 gives an example where it helps with libraries such as reporting;
  • Another library where it would help: structured logging such as Seq (to format messages that includes bools).

I can understand this is not a priority.
But closing means you don't want to add the feature in the platform, ever.
Care to explain why it's such a bad idea? It seems ok to me...

It's been open for years with very little desire expressed for it, there have been very few requests for such functionality in two decades, the entire decision can be expressed in a ternary, a function can be used to implement the transform to make it reusable, string interpolation can be used to simplify the syntax, there are many different things that a bool can be used to indicate and we wouldn't be able to correctly capture all of them in a built-in set of format strings, etc.

It's been open for years with very little desire expressed for it, there have been very few requests for such functionality in two decades

Judging by upvotes (7) it's on _open issues_ page 5 out of 102. At the same count you can close the request for a primitive color type and tar support.

The entire decision can be expressed in a ternary, a function can be used to implement the transform to make it reusable, string interpolation can be used to simplify the syntax,

no, no, and no.
As explained above.

By the same arguments we don't need format strings at all: neither for dates nor numbers.

EDIT of course we need format strings for dates and numbers. The key point I wanted to emphasize here -- and the reason why ternary or a function doesn't cut it -- is that built-in format strings can be embedded into a placeholder, like so: {0:N3}. This is what makes it work with libs like Serilog, Reporting, localized strings... /EDIT

I'm sure the demand for formatting bools is a lot less than dates but the reason why it's needed is the same. Even if you don't plan on doing it in the current iteration I think it's valuable to keep it open. Maybe someone will pick it up one day because he wants it badly.

By the same arguments we don't need format strings at all: neither for dates nor numbers.

reductio ad absurdum.

There are well-established, standardized formats for formatting numbers and date times. That is not the case for Boolean, which represents a simple choice in arbitrary domains. How is string.Format or Serilog or whatever going to know that I want my Boolean formatted as "employee"/"customer", "paid"/"late", "dog"/"cat", "a"/"b", "X"/"Y", "up"/"down", "malignant"/"benign", and on for literally thousands upon thousands of possible translations of true and false? And in the end it's really Boolean that would need to know about this, as it would be an implementation of IFormattable on Boolean that would implement the translation.

But you seem to be very passionate about this, so please share the concrete proposal for the feature (which has not been provided in years, another reason I closed it) that addresses all of that, and we can re-evaluate. I don't see a path to success here, though.

@stephentoub You missed the edit I made in my previous comment for clarity.
Yes, the formats are well-known and it would be silly not using them to format dates or numbers.
The important part is that _you can use them in placeholders_.

How would Serilog know? The same way it knows if I want 3 or 4 digits in my numbers. I tell it.

bool isRunning;
Log.Information("The service status is now {IsRunning:running;stopped}", isRunning);

I am not passionate about this because it only pops up in my code once in a blue moon (we're in agreement here).
But in a perfect world, I think it would be better if it never popped up so I'm not sure why closing is the right call.

Concrete proposal?
Sure, I think it's simple enough and I'm fine with what has been proposed in this thread before:

Formattable Boolean

Implement IFormattable on System.Boolean, adding bool.ToString(string format, IFormatProvider provider).
Add convenience bool.ToString(string format).

public struct Boolean : IFormattable
{
  public string ToString(string format, IFormatProvider provider) => ToString(format);
  public string ToString(string format); // implement format described below.
}

Format string must be trueText ; falseText.
; is the standard section separator.
\ is used to escape characters (e.g. ;).
Hopefully you can reuse the section parsing code from numbers.

Bad format handling:

  • if the format does not include a section separator, it is interpreted as trueText;, i.e. false value is blank.
  • if the format contains more than 1 section separators, further sections are ignored.

Example usage:
string.Format("The kettle is {0:on;off}", kettle)

I will re-open this, mark it as api-ready-for-review, and expect it will be closed for good when it's reviewed by the whole review team. This is not how any other format strings work in .NET, the benefit here is super low, will slightly negatively impact the perf of formatting bools today (with string.Format now having to go through the IFormattable code paths even in the vast majority case where no format string is provided), adds an inconsistency with TryFormat, increases binary size even when this functionality isn't used because the linker can't tell whether an object whose IFormattable implementation is used is actually a bool, and for an example like:
```C#
string.Format("The kettle is {0:on;off}", kettle)

you can do the same with:
```C#
string.Format($"The kettle is {(kettle ? "on" : "off")}").

@stephentoub once more before this is reviewed and closed for good:

Why the alternatives are not great

Would you format all your numbers by calling .ToString("N2")?
No -- for the same reasons:

1. Format strings are localizable.
Interpolation is not. If you want to localize your example, at best you can do:
string.Format("The kettle is {0}", kettle ? "on" : "off")
And then localize the 3 strings.
That is not perfect yet because localization migth be dependent on context (see examples from @svick above) and quite honestly localizing a single string is simpler.

2. Working with 3rd party libs, format strings is sometimes your only option
When you create some reports or charts with tools like Syncfusion, passing a format string is sometimes your only option to change the way some data is rendered.
In my experience, the only way to work-around those cases is to inject a transformation that you control between the data source and the rendering (e.g. a Select if you have a chain of IEnumerable) and that can be quite a burden... just to format booleans.

3. Representation and data are different things
There is a conceptual difference between rendering and data; I think all primitive .net types can be formatted (except bool).
Go back to the Serilog example:

Log.Information("The kettle is {IsOn:on;off}", isKettleOn);

You could do:

Log.Information("The kettel is {IsOn}", isKettleOn ? "on" : "off");

but you have changed the behavior.
Serilog is structured logging: the textual representation (e.g. on the console or in txt files) is the same; but the captured values (e.g. for consumption with tools, Seq, ElasticSearch) has changed. The former captures a bool; the latter a string.

4. Convenience
Not critical but hey

Oppositions

This is not how any other format strings work in .NET

Not sure why you say that. You can put literal text in dates and numbers. Numbers have sections already.
If you want to do positive;negative;zero it would work (modulo escaping).

will slightly negatively impact the perf of formatting bools today (with string.Format now having to go through the IFormattable code paths even in the vast majority case where no format string is provided)

Is that really going to have a noticeable impact? I mean with string.Format you have allocations going on, bools are boxed, lots of characters shuffling around.
Compared to formatting a number this is nothing, and probably much less frequent.
It's really just an added indirect call, then you fall back on the same path as before.

adds an inconsistency with TryFormat,

Good point, I forgot the latest additions to .net.
For consistency TryFormat should work the same as Format.

increases binary size even when this functionality isn't used

As much as I really would love having tiny .net exes that are just 100Ko, is this really going to be significant? Esp. if you share the section parsing code that already exists.

To justify the l10n argument, do we currently have any other context-sensitive format strings? This seems like something that we could take very far and may be better suited for another larger library.

Ignoring that, I don't see a significant benefit over a ternary.

@scalablecory It's not a format string, but there is DateTimeFormatInfo.MonthGenitiveNames as separate from MonthNames, which can help you write a string that contains a date correctly, depending on the context.

(Though it's not quite enough, for example, to correctly translate $"I'll see you in {monthName}." to Czech. For that, you would need the dative case.)

I think the reason why bool is special is that bool values often have domain-specific meaning and effectively serve as a replacement for a two-value enum. That does not happen with any other built-in type, or least not nearly as often.

And in fact, extending this to enums could also make some sense: doorState.ToString("open;closed;locked"). And again, this is more useful in languages other than English. For example, $"The door is {doorState:open;closed;locked}, the gate is {gateState:open;closed;locked}." would be translated to Czech as $"Dve艡e jsou {doorState:otev艡en茅;zav艡en茅;zam膷en茅}, br谩na je {gateState:otev艡en谩;zav艡en谩;zam膷en谩}.". (I'm using interpolation for clarity, for actual localization, a format string would likely be used instead.)

Not sure if that's an argument for or against this feature.

Video

Honestly, while I agree with that being useful, it's trivial for people to define helper methods:

```C#
public static string ToState(this bool value, string trueValue, string falseValue)
=> value ? trueValue : falseValue;
public static string ToState(this bool? value, string trueValue, string falseValue, string nullValue)
=> value is null ? nullValue : value.Value ? trueValue : nullValue;

For `bool`:

```C#
string.Format($"The kettle is {kettle.ToState("on", "off")}");

For bool?:

C# string.Format($"The kettle is {kettle.ToState("on", "off", "Unknown")}");

Using the formatting APIs seems way too complex for this case.

It's sad to see that this proposal was closed. The helper methods aren't useful for the "translation" scenario.

The helper methods aren't useful for the "translation" scenario.

There is no reason that they can't be:

C# string.Format($"The kettle is {kettle.ToState(Resources.KettleOn, Resources.KettleOff, Resources.KettleNull)}");

I'm not an expert for localization edge cases, which means that I cannot be sure that the following situation can/cannot happen, but It may (IMHO) happen that you need different sentences depending on the value.

The point that @terrajobst reponse misses is contexts where you _have_ to use format strings. (Example: reports).
By the team argument, IFormattable should not exist nor be a feature as you can do
$"The Temperature is {temperature.ToString("N0")}, which is {TempEnum.Hot.ToName()}".
instead of
$"The Temperature is {temperature:N0}, which is {TempEnum.Hot:F}".

But that ship has sailed.
.net team was nice enough to examine the feature again, they decided not to do it.
Let's move on.

Was this page helpful?
0 / 5 - 0 ratings