軟體國際化總結之一:數字與字串之間的格式化和轉化處理

來源:互聯網
上載者:User

一. 國際化-數字格式化為字串樣本

以前對付數字一般直接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的維度,沒有國際化需求的軟體,看似沒有此維度,其實不然,都有預設的文化參與在其中,只不過沒有注意到它的存在罷了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.