Specify a culture in string conversion explicitly - c#

I have a loop in which I call ToString() on the int variable i.
for (int i = 1; i <= n; i++)
{
#Html.ActionLink(#i.ToString(), "Index")
}
Resharper tells me that I should:
Specify a culture in string conversion explicitly.
Why is this?
How many ways are there to convert an int into a string?
I would have thought only 1.

There are different methods of grouping symbols, like 1000; 1 000 and 1'000.
Besides there are different digits used for numbers in different countries
Chinese numerals
You can see different numbers in Control Panel -> region and language -> Formats -> additional settings -> Standart digits. (Win 7)

Generally, it's a good practice to always specify explicitly whether you want to use the current culture or if the data your processing is culture invariant. The problem is that if you are processing data which is only processed by your software and not presented to the user in any way (for example database identifiers), then you might run into problems if the data is different on different machines.
For example a database identifier may be serialized on a machine with some culture and deserialized on a machine with a different culture, and in that case it might be different! If you specify explicitly that the string you're processing is culture-invariant, then it will always be the same, regardless of what culture is set on the machine.
You can read more about this topic on MSDN code analysis documentation:
CA1305: Specify IFormatProvider

Related

How to force .Net ToString to use given Globalization / CultureInfo

I'm working with a piece of .Net Core 6 library (dll), in which, amongst other functionality, numbers and date/time information is outputted as strings and this code is required to be globalized ie. to be culture-aware. The library is for a document producing system, so in the same application session, users would produce documents for de-CH, de-FR, en-US etc. So basically, We would like to have the library culture-aware, but the culture outputted should have it's "standard" formats, not the ones customised in Operating system level or user level.
For example for decimal values, the library code is using these methods to output stuff:
public string ToString();
public string ToString(string? format);
public string ToString(IFormatProvider? provider)
public string ToString(string? format, IFormatProvider? provider)
So calling these overloads by different users in different computers having different culture-settings, will format the output differently: decimal separator can be comma or point and the thousands grouping delimiter might be non-braking-space or ’ etc.
However, when provider argument is passed explicitly by using an instance of a specific culture for example "fr-CH" (Swiss-French) and with CultureInfo.UseUserOverride == false, I was expecting always the same output string, independent of user and/or computer. So, I was thinking, that .Net would have "built-in" values for all the properties of CultureInfo-instances for all culture-types and with argument useUserOverride: false I could force the formatting to use these "Built-in" values and hence would output always the same string.
But this seems not be the case!
The code:
CultureInfo cultInfo = new CultureInfo(name: "fr-CH", useUserOverride: false) //* fr-CH" is Swiss-French
Decimal myDec = -123456789123456.987654321M;
String output = myDec.ToString(format: "N4", provider: cultInfo); // I was expecting always same output independent, which user is calling it in which computer, but this is not the case!
This example code was compiled with .Net Core 6 as Console application and I run it on a) a Windows 10 Pro PC and b) on a Windows Server 2012R2 and they had having different outputs.
Fr-CH/Windows Pro 10 PC: -123 456 789 123 456,9877
Fr-CH/Windows Server 2012R2: -123'456'789'123'456.9877
The same by setting CultureInfo cultInfo = CultureInfo.InvariantCulture: Then the result look always the same, which is expected.
I also tried setting Thread.CurrentThread.CurrentCulture and Thread.CurrentThread.CurrentUICulture using the cultInfo-variable the above and then calling ToString(format?, provider?), but there was no effect: the results were exactly like without setting the CurrentThread. PS. I actually learned from here, here and here that setting Thread.CurrentThread.CurrentUICulture has nothing to do with formatting (my case), but with translation (resx stuff).
I'm aware of this vaste piece of Microsoft documentation about CultureInfo-class, but I cannot see the tree from the forest there.
My questions are as follows:
Has any version of .Net Core (or any version .Net Framework) a "built-in values" for all CultureInfo-properties at all?
How could I force my code to use always exactly the culture given culture independent of the environment?
This would be very important in order to be able to unit-test the library output in a reliable way.
Likely important this would be, when reading back (utilizing System.Convert(value, provider?) the values outputted by any user in any computer (just knowing the culture they were outputted).
You can set up a custom culture and configure that one according to your needs.
Start from an existing neutral one, like the InvariantCulture.
Make a clone in order to keep that InvariantCulture as-is.
var clone = CultureInfo.InvariantCulture.Clone() as CultureInfo;
Then make the required changes upon that clone - e.g.:
clone.NumberFormat.NumberDecimalSeparator = ",";
clone.NumberFormat.NumberGroupSeparator = ".";
Optionally but advisable, you might want to make a readonly version of it.
var customCulture = CultureInfo.ReadOnly(clone);
Use that culture wherever you need a fixed controlled output - e.g.:
var number = -123456789123456.987654321M;
var formattedNumber = number.ToString("N4", customCulture)); // -123.456.789.123.456,9877
Apply that same culture when parsing the formatted string version back to a decimal - e.g.:
var parsedNumber = decimal.Parse(formattedNumber, customCulture);
If you are interested in how a CultureInfo is set up have a look at the source code.
In short the InvariantCulture is one with fixed settings, whereas the others rely on the operating system and optional user overrides.
From the documentation:
.NET derives its cultural data from a one of a variety of sources,
depending on implementation, platform, and version:
In .NET Framework 3.5 and earlier versions, cultural data is provided by both the Windows operating system and .NET Framework.
In .NET Framework 4 and later versions, cultural data is provided by the Windows operating system.
In all versions of .NET Core running on Windows, cultural data is provided by the Windows operating system.
Because of this, a culture available on a particular .NET
implementation, platform, or version may not be available on a
different .NET implementation, platform, or version.
Some CultureInfo objects differ depending on the underlying platform.
Set it on the thead for it to work and if you don't want it to repeat in every place:
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("fr-CH");
.....
Decimal myDec = -123456789123456.987654321M;
String output = myDec.ToString(format: "N4");
Console.WriteLine(output);
when you run the code on other computers, try to check the cultureinfo and see what it will return. I bet it is not setting it to fr-CH:
CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
Console.WriteLine(currentCulture.Name);

C# float to string separator

I am new to C # and I am currently having problems with the following. In C #, I have a Pi floating point number and I want to convert it to a string using the ToString() method. But the conversion gives a string result with a comma "3,1415". On another machine, the same gives the string result with the dot "3.1415". What is the reason for this and what should I do to get a dotted string result?
EDIT: The problem is, I can't change the code, but I can install and uninstall .Net frameworks, change my OS settings, etc.
Edit: if you can't change the code. Change the language/localization of the system to one which uses dot as decimal separator. In Control Panel or Settings.
You should look at internationalization and localization in the System.Globalization namespace.
The advice here is to use one CultureInfo specific for parsing numbers or writing numbers to string.
var flt = 232.23f;
var str = flt.ToString(CultureInfo.InvariantCulture); //for example, you can use CultureInfo.CurrentCulture
This allows you to keep the ThreadCulture without change it.
But take a look at this link https://learn.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo?view=net-5.0 .Take your time, is dense.
I would just set the current culture at the entry point of your program.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
However I would also check the regional settings and so forth on the machine where the comma separator appears.
It is related with current culture info. You can specify the culture info in ToString method as a parameter like;
var convertedFloat = floatVariable.ToString(new CultureInfo("en-GB"));
Thanks to GSerg, for comment about Windows regional settings. That solves my problem. In the Windows Control panel enter Region and Language. In the Formats tab click Additional Settings and in the Decimal symbol field specify what decimal separator must be used when converting a floating point number to a string.

Get Culture by currency

Well... i search the web and found many solutions for the other way, but none for these.
I have a application which gets different currencys by the user. I dont know the currencys in before, it could be everything (russian rubels, usd, €, Yen...)
I need to convert the amount into a decimal, for that i need the current culture. My current solution is very bad 8and incomplete, cause i cant cover all cultures that way), it just checks the currency sign.
if (currency.Contains("zł"))
{
cult = CultureInfo.GetCultureInfo("PL-pl")
}
else if (currency.Contains("$"))
{
//blah blah blah
}
Is there a possiblility to get Culture base on the currency sign. Another maybe difficult thing is, that i dont know if the currency symbol is before or beyond the amount (varys by culture i.E: $45.00 <-> 45.00€)
Create a lookup once and use it for fast access. Notice that a particular currency symbol may be used by multiple cultures:
ILookup<string, CultureInfo> cultureByCurrency =
CultureInfo.GetCultures(CultureTypes.AllCultures)
.ToLookup(_ => _.NumberFormat.CurrencySymbol);
Then to lookup $ for example:
IEnumerable<CultureInfo> culturesThatUseDollar = cultureByCurrency["$"];
There is no exact mapping from a currency code or symbol to a culture. Consider basic examples like EUR (€), which is used as the official currency in 18 countries. There are many issues arising from this mere fact, like whether the symbol is placed before or after the value etc. You should ask the user about the specific formatting to use instead of trying to deduce it from the currency symbol.
Also, a single currency symbol is used for many currencies. Consider that $ can denote both USD, CAD, AUD and other currencies that call themselves as 'dollars'. You should use currency codes if you want an exact specification of a currency.
It is not possible.
EUR for example would map to de-DE, fr-FR, nl-NL and other countries.
There is no mapping from Currency to culture, because multiple countries share currencies
In your else if block, which culture would you assign after finding the $? en-US? fr-CA?
I would suggest a different approach that would remove any sort of ambiguity. Have the user specify their nationality before entering this chunk of code. Consider having the culture information given to you instead of attempting to guess it.
return CultureInfo.GetCultures(CultureTypes.AllCultures).Where(c => c.NumberFormat.CurrencySymbol.Equals("$"));

Ignoring country-specific decimal separator

I'm currently doing an app, that needs to be able to work with the US number layout (123,456.78) as well as with the German layout (123.456,78).
Now my approach is to use NumberFormatInfo.InvariantInfo about like this:
temp = temp.ToString(NumberFormatInfo.InvariantInfo);
this works great when for example reading a number from a textbox. When System is set to English format it will take the . as separator, when it's set to German it will use the ,.
So far so good....but here's the problem: I have a device that returns info in the American format, and that won't change (transmitted via RS232). So I receive something like 10.543355E-00.
Now when on German setting the . will be discarded since it's just the group separator
and the number I will end up with is 10543355....which is a lot more :)
I tried with the same technique thinking this would make the whole thing kind of 'cultureless' to be able to process it independently from the system language but it didn't work :)
I hope you can maybe help me here...I'd love to use a way without having to implement the whole culture stuff etc since all I need here is really numbers that get calculated the right way.
You should use CultureInfo.InvariantCulture when parsing strings from the device. This will cause it to use the invariant culture, which has the US rules for decimal separation.
Edit in response to comments:
The issue is not when you call .ToString(), but rather when you read the string from the device, and convert it to a number:
string inputFromRS232Device = GetDeviceInput();
double value;
// You need this when converting to the double - not when calling ToString()
bool success = double.TryParse(
inputFromRS232Device,
NumberStyles.Float,
CultureInfo.InvariantCulture,
out value);

How to make doubles always contain a . character?

I noticed my doubles contain different precision characters depending on regional settings.
For example:
3,47 or 3.45
How can I enforce the double should always contain a . precision character?
Problem is once I serialize the class containing this double to XML, it gets shipped to other systems expecting a standard result.
So after reading your responses (and thanks), do you guys recommend changing the property to a string, (making the replacements in a string), so that it serializes with the string value (not the double)?
You need to put the double to string using the Invariant Culture.
double d = 3.47;
Console.WriteLine(d.ToString(CultureInfo.InvariantCulture));
You will need to format with the InvariantCulture.
Note that the "." and "," formatting characters are interpreted according to the culture settings.
This little tutorial will be the answer you need, I expect:
http://www.java2s.com/Tutorial/CSharp/0440__I18N-Internationalization/Parsingnumberswithcultureinvariantparsing.htm
double.Parse(numberString, CultureInfo.InvariantCulture);
The double itself doesn't include a "." or a ",", only the print out of the representation does. You can read up on custom formats here.
[Update according to OP]
I don't know exactly what your design looks like, but it would probably be smart to create a string property on your DTO, which would output the formatted string of your double, and then mark your double property as not serializable.
When you need to do this for all numbers in your current applicaton, you can use the following to set it application-wide (new threads will inherit this setting):
// use this statement to force the default:
Application.CurrentCulture = CultureInfo.InvariantCulture;
string s = myNumber.ToString();
// for one number you have to remember to use:
string s = myNumber.ToString(CultureInfo.InvariantCulture);
Note: by default, your application, whether ASP.NET or WinForms, will use the culture settings of the system it is running on (in ASP.NET, you can set the culture globally in the web.config).

Categories

Resources