• 软件国际化总结之一:数字与字符串之间的格式化和转化处理


    一. 国际化-数字格式化为字符串示例


    以前对付数字一般直接ToString()一下就完了,但遇到需要国际化的软件,就不能这么简单了,比如有的国家的金额千分位不是逗号而是句号,小数点不是句号而是逗号,因此,为了将数字以正确的字符串形式展现在该国人面前,就需要用明确的方法。 

    其实数字的ToString()方法有多个重载,用来实现国际化下的各国数字正确格式化。

    1.先看一个简单的程序Demo

    using 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.46
    123.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里的IFormatProvider
    public 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. 程序Demo
    string 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的维度,没有国际化需求的软件,看似没有此维度,其实不然,都有默认的文化参与在其中,只不过没有注意到它的存在罢了。

    作者:BobLiu
    邮箱:lzd_ren@hotmail.com
    出处:http://www.cnblogs.com/liuzhendong
    本文版权归作者所有,欢迎转载,未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    C语言指向指针的指针
    C语言注意事项
    C语言指针
    C语言字符串
    C语言数组
    C语言交换两个数的值
    C语言位运算符
    C语言各种进制输出
    C语言中各种进制的表示
    C 语言sizeof运算符
  • 原文地址:https://www.cnblogs.com/liuzhendong/p/2732682.html
Copyright © 2020-2023  润新知