一. 国际化-数字格式化为字符串示例
以前对付数字一般直接ToString()一下就完了,但遇到需要国际化的软件,就不能这么简单了,比如有的国家的金额千分位不是逗号而是句号,小数点不是句号而是逗号,因此,为了将数字以正确的字符串形式展现在该国人面前,就需要用明确的方法。其实数字的ToString()方法有多个重载,用来实现国际化下的各国数字正确格式化。
1.先看一个简单的程序Demousing System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Globalization;namespace 国际化类库测试
{ class Program { static void Main(string[] args) { decimal input = 12345678; CultureInfo culture = new CultureInfo("km-KH"); //C或c代表货币 Console.WriteLine(input.ToString("C", culture)); //12,345,678.00? //N或n代表数字 Console.WriteLine(input.ToString("n", culture)); //12345,678.00 Console.ReadKey(); } }}2.数字格式化为字符串的标准
C, c代表货币,N,n代表数字。“C”或“c” Currency 结果:货币值。123.456 ("C", en-US) -> $123.46123.456 ("C", fr-FR) -> 123,46 €123.456 ("C", ja-JP) -> ¥123-123.456 ("C3", en-US) -> ($123.456)-123.456 ("C3", fr-FR) -> -123,456 €-123.456 ("C3", ja-JP) -> -¥123.456 “D”或“d” Decimal 结果:整型数字,负号可选。受以下类型支持:仅整型。1234 ("D") -> 1234-1234 ("D6") -> -001234 3.反编译Decimal类的ToString()方法,看源代码。3.1 Decimal类ToString()的几个重载方法在mscorlib.dll库中。源代码如下:public override string ToString(){ return Number.FormatDecimal(this, null, NumberFormatInfo.CurrentInfo);}public string ToString(string format){ return Number.FormatDecimal(this, format, NumberFormatInfo.CurrentInfo);}public string ToString(IFormatProvider provider){ return Number.FormatDecimal(this, null, NumberFormatInfo.GetInstance(provider));}public string ToString(string format, IFormatProvider provider){ return Number.FormatDecimal(this, format, NumberFormatInfo.GetInstance(provider));}public static NumberFormatInfo GetInstance(IFormatProvider formatProvider){ NumberFormatInfo numInfo; CultureInfo info2 = formatProvider as CultureInfo; if ((info2 != null) && !info2.m_isInherited) { numInfo = info2.numInfo; if (numInfo != null) { return numInfo; } return info2.NumberFormat; } numInfo = formatProvider as NumberFormatInfo; if (numInfo != null) { return numInfo; } if (formatProvider != null) { numInfo = formatProvider.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo; if (numInfo != null) { return numInfo; } } return CurrentInfo;}
3.2 IFormatProvider接口
[ComVisible(true)]public interface IFormatProvider{ // Methods object GetFormat(Type formatType);} 3.3 CultureInfo里的IFormatProviderpublic class CultureInfo : ICloneable, IFormatProvider{public virtual object GetFormat(Type formatType)
{ if (formatType == typeof(NumberFormatInfo)) { return this.NumberFormat; } if (formatType == typeof(DateTimeFormatInfo)) { return this.DateTimeFormat; } return null; }}3.4 NumberFormatInfo里的IFormatProvider
[Serializable, ComVisible(true)]public sealed class NumberFormatInfo : ICloneable, IFormatProvider{ public object GetFormat(Type formatType){ if (formatType != typeof(NumberFormatInfo)) { return null; } return this;} }3.5 Number里的FormatDecimal
internal class Number{ // Fields private const int Int32Precision = 10; private const int Int64Precision = 0x13; private const int NumberMaxDigits = 50; private const int UInt32Precision = 10; private const int UInt64Precision = 20;// Methods
private Number(); [MethodImpl(MethodImplOptions.InternalCall)] public static extern string FormatDecimal(decimal value, string format, NumberFormatInfo info);[MethodImpl(MethodImplOptions.InternalCall)]3.5.1 C# extern修饰符是什么意思?
C# extern修饰符用于声明 由程序集外部实现的成员函数经常用于系统API函数的调用(通过 DllImport )。注意,C# extern修饰符和DllImport一起使用时要加上 static 修饰符也可以用于对于同一程序集不同版本组件的调用(用 extern 声明别名) 不能与 abstract 修饰符同时使用 。DllImport("avifil32.dll")]
private static extern void AVIFileInit();也就是说这个方法是放在申明的类之外的类中实现的. 3.6 NumberFormatInfo全部成员列表[Serializable, ComVisible(true)]public sealed class NumberFormatInfo : ICloneable, IFormatProvider{ // Fields internal string ansiCurrencySymbol; internal int currencyDecimalDigits; internal string currencyDecimalSeparator; internal string currencyGroupSeparator; internal int[] currencyGroupSizes; internal int currencyNegativePattern; internal int currencyPositivePattern; internal string currencySymbol; [OptionalField(VersionAdded=2)] internal int digitSubstitution; private const NumberStyles InvalidNumberStyles = ~(NumberStyles.HexNumber | NumberStyles.AllowCurrencySymbol | NumberStyles.AllowExponent | NumberStyles.AllowThousands | NumberStyles.AllowDecimalPoint | NumberStyles.AllowParentheses | NumberStyles.AllowTrailingSign | NumberStyles.AllowLeadingSign); private static NumberFormatInfo invariantInfo; internal bool isReadOnly; internal int m_dataItem; internal bool m_useUserOverride; internal string nanSymbol; [OptionalField(VersionAdded=2)] internal string[] nativeDigits; internal string negativeInfinitySymbol; internal string negativeSign; internal int numberDecimalDigits; internal string numberDecimalSeparator; internal string numberGroupSeparator; internal int[] numberGroupSizes; internal int numberNegativePattern; internal int percentDecimalDigits; internal string percentDecimalSeparator; internal string percentGroupSeparator; internal int[] percentGroupSizes; internal int percentNegativePattern; internal int percentPositivePattern; internal string percentSymbol; internal string perMilleSymbol; internal string positiveInfinitySymbol; internal string positiveSign; internal bool validForParseAsCurrency; internal bool validForParseAsNumber;// Methods
public NumberFormatInfo(); internal NumberFormatInfo(CultureTableRecord cultureTableRecord); internal void CheckGroupSize(string propName, int[] groupSize); public object Clone(); public object GetFormat(Type formatType); public static NumberFormatInfo GetInstance(IFormatProvider formatProvider); [OnDeserialized] private void OnDeserialized(StreamingContext ctx); [OnDeserializing] private void OnDeserializing(StreamingContext ctx); [OnSerializing] private void OnSerializing(StreamingContext ctx); public static NumberFormatInfo ReadOnly(NumberFormatInfo nfi); internal static void ValidateParseStyleFloatingPoint(NumberStyles style); internal static void ValidateParseStyleInteger(NumberStyles style); private void VerifyDecimalSeparator(string decSep, string propertyName); private void VerifyDigitSubstitution(DigitShapes digitSub, string propertyName); private void VerifyGroupSeparator(string groupSep, string propertyName); private void VerifyNativeDigits(string[] nativeDig, string propertyName); private void VerifyWritable();// Properties
public int CurrencyDecimalDigits { get; set; } public string CurrencyDecimalSeparator { get; set; } public string CurrencyGroupSeparator { get; set; } public int[] CurrencyGroupSizes { get; set; } public int CurrencyNegativePattern { get; set; } public int CurrencyPositivePattern { get; set; } public string CurrencySymbol { get; set; } public static NumberFormatInfo CurrentInfo { get; } [ComVisible(false)] public DigitShapes DigitSubstitution { get; set; } public static NumberFormatInfo InvariantInfo { get; } public bool IsReadOnly { get; } public string NaNSymbol { get; set; } [ComVisible(false)] public string[] NativeDigits { get; set; } public string NegativeInfinitySymbol { get; set; } public string NegativeSign { get; set; } public int NumberDecimalDigits { get; set; } public string NumberDecimalSeparator { get; set; } public string NumberGroupSeparator { get; set; } public int[] NumberGroupSizes { get; set; } public int NumberNegativePattern { get; set; } public int PercentDecimalDigits { get; set; } public string PercentDecimalSeparator { get; set; } public string PercentGroupSeparator { get; set; } public int[] PercentGroupSizes { get; set; } public int PercentNegativePattern { get; set; } public int PercentPositivePattern { get; set; } public string PercentSymbol { get; set; } public string PerMilleSymbol { get; set; } public string PositiveInfinitySymbol { get; set; } public string PositiveSign { get; set; }}
这里面有数字千分位分隔符NumberGroupSeparator,小数位分隔符NumberDecimalSeparator,货币小数位分隔符及千分位分隔符CurrencyDecimalSeparator,CurrencyGroupSeparator等重要属性。
4.对前面Demo程序的解析
IFormatProvider接口中就一个GetFormat方法。当执行 Console.WriteLine(input.ToString("C", culture)); //12,345,678.00?
时,执行下面这个方法:
public string ToString(string format, IFormatProvider provider){ return Number.FormatDecimal(this, format, NumberFormatInfo.GetInstance(provider));}然后又执行GetInstance方法得到NumberFormatInfo对象。
public static NumberFormatInfo GetInstance(IFormatProvider formatProvider){ NumberFormatInfo numInfo; CultureInfo info2 = formatProvider as CultureInfo; if ((info2 != null) && !info2.m_isInherited) { numInfo = info2.numInfo; if (numInfo != null) { return numInfo; } return info2.NumberFormat; } numInfo = formatProvider as NumberFormatInfo; if (numInfo != null) { return numInfo; } if (formatProvider != null) { numInfo = formatProvider.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo; if (numInfo != null) { return numInfo; } } return CurrentInfo;}最后执行Number.FormatDecimal(this, format, NumberFormatInfo.GetInstance(provider))
即,待格式化的数值,格式化标识符C或N,及NumberFormat (这里面有数字千分位分隔符NumberGroupSeparator,小数位分隔符NumberDecimalSeparator,货币小数位分隔符及千分位分隔符CurrencyDecimalSeparator,CurrencyGroupSeparator)
不过,Number.FormatDecimal是外部的函数,找不到具体实现,只能跟到这儿了。
5.小结
数字如果想转化为理想的格式,除了传入C或N,也要传入文化,如果不传,系统也会给你用工作线程中默认的文化。
二. 国际化-字符串转化为数字示例
一般来说,对一个表示数字的字符串,使用Convert.ToDecimal或Decimal.TryParse就可以将它转为数字类型了,但是,如果遇到象越南,德国这种国家,他们的数字千分位居然用句号,小数点用逗号,因此,这种字符串如果想直接用ToDecimal转为数字就有点麻烦了。例如:一个越南的表示数字的字符串"333.444.555,333",实际上它是数字333444555.333,如何把它还原为正确的数字呢?
1. 程序Demostring inputString = "333.444.555,333";decimal decimalInput = Convert.ToDecimal(inputString);结果:Decimal.TryParse=333444,555.00系统会报一场:不正确的字符串格式。
2. 解决方法
使用ToDecimal的重载方法:public static decimal ToDecimal(string value, IFormatProvider provider)string inputString = "333.444.555,333";
decimal decimalInput = Convert.ToDecimal(inputString, new CultureInfo("vi-VN"));Console.WriteLine("Convert.ToDecimal=" + decimalInput.ToString("n", culture));结果是:333.444.555,33
3. 更好的方法是用Decimal.TryParse,它转换失败时不会报异常。
if (Decimal.TryParse(inputString, NumberStyles.Number, new CultureInfo("vi-VN"), out decimalInput)){ Console.WriteLine("Decimal.TryParse=" + decimalInput.ToString("n", culture)); }4. 小结
字符串(一般来说,有可能带着格式,如千分位,小数点)如果想正确转化为数字,也需要传入文化。
三.总结
对于国际化软件中,数字与字符串之间的格式化和转化处理,都需要刻意加上文化CultureInfo的维度,没有国际化需求的软件,看似没有此维度,其实不然,都有默认的文化参与在其中,只不过没有注意到它的存在罢了。