C# String comparisons: Difference between CurrentCultureIgnoreCase and InvariantCultureIgnoreCase - c#

When doing a string comparison in C#, what is the difference between doing a
string test = "testvalue";
test.Equals("TESTVALUE", StringComparison.CurrentCultureIgnoreCase);
and
string test = "testvalue";
test.Equals("TESTVALUE", StringComparison.InvariantCultureIgnoreCase);
... and is it important to include that extra parameter, anyway?

The other posts have given good advice, but I thought it might be nice to show an example of where it definitely makes a difference:
using System;
using System.Globalization;
using System.Threading;
class Test
{
static void Main()
{
CultureInfo turkish = CultureInfo.CreateSpecificCulture("tr");
Thread.CurrentThread.CurrentCulture = turkish;
// In Turkey, "i" does odd things
string lower = "i";
string upper = "I";
// Prints False
Console.WriteLine(lower.Equals(upper,
StringComparison.CurrentCultureIgnoreCase));
// Prints True
Console.WriteLine(lower.Equals(upper,
StringComparison.InvariantCultureIgnoreCase));
}
}
(There are no doubt many other cases - this was just the first one I thought of.)

Microsoft gives some decent guidance for when to use the InvariantCulture property:
MSDN: CultureInfo.InvariantCulture Property
... an application should use the
invariant culture only for processes
that require culture-independent
results, such as formatting and
parsing data that is persisted to a
file. In other cases, it produces
results that might be linguistically
incorrect or culturally inappropriate.
Security Considerations
If a security decision will be made
based on the result of a string
comparison or case change, your
application should use an ordinal
comparison that ignores case instead
of using InvariantCulture. [...]
String Operations
If your application needs to perform a
culture-sensitive string operation
that is not affected by the value of
CurrentCulture, it should use a method
that accepts a CultureInfo parameter.
[...]
Persisting Data
The InvariantCulture property is
useful for storing data that will not
be displayed directly to users.
Storing data in a culture-independent
format guarantees a known format that
does not change. When users from
different cultures access the data, it
can be formatted appropriately based
on specific user. [...]

Related

C# Percentage Format Specifier gives different result and make assertion to fail when running in GitHub Actions

I have a report record with TaxPercentage as string property.
public record SaleReportDto
{
public string TaxPercentage { get; set; } = string.Empty;
}
I'm formatting the tax percentage using C# Percentage Format Specifier.
var report = new SaleReportDto()
{
TaxPercentage = $"{0.18:P2}")
};
I'm asserting the above value in my test as follows,
report.TaxPercentage.Should().Be($"{0.18:P2});
The test passes in my windows machine. When I run the same in my GitHub Actions, I get a different result and test assertion fails with the following error message,
Expected report.TaxPercentage to be "18.00 %" with a length of 7, but "18.00%" has a length of 6, differs near "%" (index 5)
So to temporarily make test pass, I have changed my assertion as follows
report.TaxPercentage.Contains("18");
Why the values differs between assignment and assertion within same machine? I use $"{0.18:P2}" to assign and assert. Then this should generate same value in both case. But it is not.
Any hint on why this happens? I initially thought this might be because of Culture, but in both places while assigning and asserting I don't use any specific Culture. So both places should return same value. But not sure why it doesn't work that way in GitHub Actions.
I don't use any specific Culture. So both places should return same
value.
That's not correct. String interpolation by default uses "current" culture for formatting, and that depends on the environment you run your program at. So on different machines it's totally possible for it to return different results. For example, on my machine your string returns third variant: 18,00 %.
If you want to have same results on all machines this code can run at, use either invariant culture like this:
TaxPercentage = FormattableString.Invariant($"{0.18:P2}")
Or if that is not what you want - figure out which culture is used for the result you want and then explicitly use it for formatting, without relying on it to be current culture.

How to convert sql type double-precision in c#

I have a database access, and one of this field is double-precision.
Normally if i set in the textbox 1.71 or 1,71 the field in database should contain 1.71.
But if i execute query the value of acces'field is 171 !!.
public const string QUERY =
#"UPDATE TBLART
SET TBLART.COST = #cost
WHERE TBLART.CODE= '1'";
var param = new DynamicParameters();
var cost = totalCost.Replace(',', '.'); //totalCost is a textbox
param.Add("cost", Double.Parse(cost), DbType.Double);
gsmconn.Execute(QUERY, param);
What i wrong ? Thanks.
double.Parse will use the current thread's culture by default. I suspect your current culture uses "." as a grouping separator.
Two options:
Continue to use Replace as you are already doing, but then specify CultureInfo.InvariantCulture when parsing
Remove the replacement, and just use the current thread's culture when parsing the original value. This relies on the culture being appropriate for the user, but is probably a better solution when that's the case. (Otherwise someone entering "1,234.56" will get a parse error when they expected a value of "just a bit more than 1234".)
If I remember correctly in windows forms you can bind an double property to a textbox and it will automatically take care of parsing and converting. You should not manually do parsing from string to double.
That problem is already solved fro you by the .NET framework.
Another suggestion, your data access code should not do any parsing. That should be done in some higher layer. But better leave it to the framework.

How to prohibit comma as decimal separator

I'm writing code with german culture settings
Nevertheless I would like to force the user to use the point as a decimal separator.
My piece of test code outputs the wrong value.
How do I detect the "wrong" comma ?(throw an exception)
string nok_str = "14,9";
string ok_str = "14.9";
double nok_Var1 = double.Parse(nok_str, CultureInfo.InvariantCulture.NumberFormat); // outputs 149.0
double nok_Var2 =Convert.ToDouble(nok_str, CultureInfo.InvariantCulture.NumberFormat); // outputs 149.0
First off, and please forgive me, Iā€™d like to question your design decision:
How is this enhancing the user experience? The application should rather try to accept all unambiguous user input, not reject theoretically sound input.
That said, a number such as ā€œ19,2ā€ will be interpreted, with an invariant culture, as having a thousands separator (which is simply discarded). This is why your code silently produces bad values. If you want to explicitly forbid this input, the easiest way to achieve this is an explicit test:
if (nok_str.Contains(","))
throw new FormatException(ā€¦);
As an alternative, you can try modifying the NumberFormatInfo.NumberGroupSeparator property of a custom NumberFormatInfo object that you pass to the Parse method.
Basically the default is to include AllowThousands in the number style. If you specify the number style you want, you can prohibit this:
using System;
using System.Globalization;
class Test
{
static void Main(string[] args)
{
string text = "19,2";
double value;
bool valid = double.TryParse(text, NumberStyles.Float,
CultureInfo.InvariantCulture,
out value);
Console.WriteLine(valid); // Prints false
}
}
Note that NumberStyles.Float is a composite style for AllowLeadingWhite, AllowTrailingWhite, AllowLeadingSign, AllowDecimalPoint, and AllowExponent - but not AllowThousands.
I am not sure what is the source of your input.
If it comes from user it also depends... If it is GUI application, you may think of restricting the input to certain possible keys, excluding comma. If it is a console app, you can try regular expressions to pre-validate input strings.
If it comes from various sources (i.e. web service) maybe simply brute-force string replace will do the trick?
Last, but not least: there are reasons for parsing to be culture-sensitive and if I were you, I would encourage users to enter valid regional number format instead forcing them to provide incorrect one.

String.Format vs ToString and using InvariantCulture

I am a little confused here.
What should I use
Console.WriteLine((val/1085).ToString("N"));
VS
Console.WriteLine(String.Format("{0:N}", (val/1085)));
Also how do I fit the InvariantCulture? ANY BEST PRACTICES :)?
Actually I prefer a third form:
Console.WriteLine("{0:N}", val / 1085);
Console.WriteLine can do the String.Format for you.
Console.WriteLine does not allow you to supply a culture. If that is what you want, you will still have to use String.Format. As in:
String.Format(CultureInfo.InvariantCulture, "{0:N}", 123456789);
I do not recommend using that because international users will have trouble reading that. To me 123,456,789.00 looks strange.
For formatting + culture I prefer:
.ToString("####0.00",CultureInfo.InvariantCulture)
or
.ToString("N",CultureInfo.InvariantCulture)
I found an invariant and generic way to solve that as follows:
Syntax:
.ToStringInvariant(format)
.ToStringInvariant()
Technically, it is a generic extension method, defined as follows:
public static class Extensions
{
private static IFormatProvider inv
= System.Globalization.CultureInfo.InvariantCulture.NumberFormat;
public static string ToStringInvariant<T>(this T obj, string format=null)
{
return (format == null) ? System.FormattableString.Invariant($"{obj}")
: String.Format(inv, $"{{0:{format}}}", obj);
}
}
Usage is simple, just use .ToStringInvariant() instead of .ToString(). The advantage is that it works for any data type.
Optionally, you can pass a format too, like for example .ToStringInvariant("N"), just as you are used to to it with .ToString("N").
Note that in that case the extension method uses String.Format internally.
You can see the difference if you have a different culture for the number format, like in Germany we have comma instead of a decimal point. That means on a PC with German culture settings:
void Main()
{
var val = 2456.5;
Console.WriteLine((val/1085).ToString("N"));
Console.WriteLine((val/1085).ToStringInvariant("N"));
Console.WriteLine((val/1085).ToStringInvariant("0.000"));
Console.WriteLine((val/1085).ToStringInvariant());
}
it would output:
2,26
2.26
2.264
2.26405529953917
which is correct, because .ToString uses the current culture (German settings), and .ToStringInvariant always uses the invariant culture, which is the English number format regardless of the Windows settings.
Note: For date formatting, I have provided a different extension method, which you can find here.
More information:
FormattableString.Invariant(FormattableString) Method
In a datetime it's okay to use both. I rather like to use and see the first solution (ofcourse with missing parenthesis).
The String.Format is much more usefull when you have some string with a gaps for some kind of parameters. Then it's a killer method, which really nicely help you to organize your code.

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