C# 自定義類型經過實現IFormattable接口,來輸出指定的格式和語言文化的字符串(例:DateTime)

常規的調用ToString()方法,存在兩個問題.編程

(1)、調用者沒法控制字符串的格式ide

(2)、調用者不能方便的選擇一種特定的語言文化來格式化字符串.工具

 

在開發一些國際化的應用時,應用程序須要調用與當前線程不一樣的語言文化來格式化字符串.ui

 

so,爲了對字符串進行更多的控制,你重寫的的ToString()方法應該容許指定具體的格式和語言文化信息.this

爲了能使調用者在調用對象實例的ToString()方法的時候,選擇格式和語言文化,該對象應該實現System.IFormattable接口,接口代碼以下:spa

    //
    // 摘要:
    //     提供一種功能,用以將對象的值格式化爲字符串表示形式。
    [ComVisible(true)]
    public interface IFormattable
    {
        //
        // 摘要:
        //     使用指定格式對當前實例的值設置格式。
        //
        // 參數:
        //   format:
        //     要使用的格式。 - 或 - null 引用(在 Visual Basic 中爲 Nothing),用於使用爲 System.IFormattable 實現的類型定義的默認格式。
        //
        //   formatProvider:
        //     要用於對值設置格式的提供程序。 - 或 - null 引用(在 Visual Basic 中爲 Nothing),用於從操做系統的當前區域設置獲取數字格式信息。
        //
        // 返回結果:
        //     採用指定格式的當前實例的值。
        string ToString(string format, IFormatProvider formatProvider);
}

注:操作系統

format參數,至關於一個字符串模板,它會解析裏面的字母,並對其進行相應的轉換.如:g表明常規線程

formatProvider參數:指定對應類型的格式化信息,通常和語言文化類型有關3d

 

FCL(Framework Common Language)中的全部基類型(Byte,SByte,Int16/UInt16,Int32/Uint32,Int64/Uint64,Single,Double,Decimal和Datetime)都實現了這個接口,這些基類型調用ToString方法以後,返回的都是字面值的字符串形式,此外FCL中還有一些類型實現了這個接口.code

一、Guid,Guid的ToString代碼以下所示:

Guid是實現IFormattable接口,具體的實現以下:

public unsafe string ToString(string format, IFormatProvider provider)
{
    string str;
    if ((format == null) || (format.Length == 0))
    {
        format = "D";
    }
    int offset = 0;
    bool flag = true;
    bool flag2 = false;
    if (format.Length != 1)
    {
        throw new FormatException(Environment.GetResourceString("Format_InvalidGuidFormatSpecification"));
    }
    char ch = format[0];
    switch (ch)
    {
        case 'D':
        case 'd':
            str = string.FastAllocateString(0x24);
            break;

        case 'N':
        case 'n':
            str = string.FastAllocateString(0x20);
            flag = false;
            break;

        case 'B':
        case 'b':
            str = string.FastAllocateString(0x26);
            fixed (char* str2 = ((char*) str))
            {
                char* chPtr = str2;
                if (chPtr != null)
                {
                    chPtr += RuntimeHelpers.OffsetToStringData;
                }
                chPtr[offset++] = '{';
                chPtr[0x25] = '}';
            }
            break;

        case 'P':
        case 'p':
            str = string.FastAllocateString(0x26);
            fixed (char* str3 = ((char*) str))
            {
                char* chPtr2 = str3;
                if (chPtr2 != null)
                {
                    chPtr2 += RuntimeHelpers.OffsetToStringData;
                }
                chPtr2[offset++] = '(';
                chPtr2[0x25] = ')';
            }
            break;

        default:
            if ((ch != 'X') && (ch != 'x'))
            {
                throw new FormatException(Environment.GetResourceString("Format_InvalidGuidFormatSpecification"));
            }
            str = string.FastAllocateString(0x44);
            fixed (char* str4 = ((char*) str))
            {
                char* chPtr3 = str4;
                if (chPtr3 != null)
                {
                    chPtr3 += RuntimeHelpers.OffsetToStringData;
                }
                chPtr3[offset++] = '{';
                chPtr3[0x43] = '}';
            }
            flag = false;
            flag2 = true;
            break;
    }
    fixed (char* str5 = ((char*) str))
    {
        char* guidChars = str5;
        if (guidChars != null)
        {
            guidChars += RuntimeHelpers.OffsetToStringData;
        }
        if (flag2)
        {
            guidChars[offset++] = '0';
            guidChars[offset++] = 'x';
            offset = HexsToChars(guidChars, offset, this._a >> 0x18, this._a >> 0x10);
            offset = HexsToChars(guidChars, offset, this._a >> 8, this._a);
            guidChars[offset++] = ',';
            guidChars[offset++] = '0';
            guidChars[offset++] = 'x';
            offset = HexsToChars(guidChars, offset, this._b >> 8, this._b);
            guidChars[offset++] = ',';
            guidChars[offset++] = '0';
            guidChars[offset++] = 'x';
            offset = HexsToChars(guidChars, offset, this._c >> 8, this._c);
            guidChars[offset++] = ',';
            guidChars[offset++] = '{';
            offset = HexsToChars(guidChars, offset, this._d, this._e, true);
            guidChars[offset++] = ',';
            offset = HexsToChars(guidChars, offset, this._f, this._g, true);
            guidChars[offset++] = ',';
            offset = HexsToChars(guidChars, offset, this._h, this._i, true);
            guidChars[offset++] = ',';
            offset = HexsToChars(guidChars, offset, this._j, this._k, true);
            guidChars[offset++] = '}';
        }
        else
        {
            offset = HexsToChars(guidChars, offset, this._a >> 0x18, this._a >> 0x10);
            offset = HexsToChars(guidChars, offset, this._a >> 8, this._a);
            if (flag)
            {
                guidChars[offset++] = '-';
            }
            offset = HexsToChars(guidChars, offset, this._b >> 8, this._b);
            if (flag)
            {
                guidChars[offset++] = '-';
            }
            offset = HexsToChars(guidChars, offset, this._c >> 8, this._c);
            if (flag)
            {
                guidChars[offset++] = '-';
            }
            offset = HexsToChars(guidChars, offset, this._d, this._e);
            if (flag)
            {
                guidChars[offset++] = '-';
            }
            offset = HexsToChars(guidChars, offset, this._f, this._g);
            offset = HexsToChars(guidChars, offset, this._h, this._i);
            offset = HexsToChars(guidChars, offset, this._j, this._k);
        }
    }
    return str;
}

查看源代碼發現,Guid的ToString()方法並無使用IFormatProvider參數,緣由是由於,Guid和語言無關,通常用於內部編程使用,因此不須要這個參數.

調用代碼以下:

            var gid = Guid.NewGuid();
            Console.WriteLine(gid.ToString("d"));
            Console.WriteLine(gid.ToString("n"));
            Console.WriteLine(gid.ToString("b"));
            Console.WriteLine(gid.ToString("p"));
            Console.WriteLine(gid.ToString("x"));

 

二、Enum,Enum重寫的ToString()方法,ToString()方法沒有使用到IFormatProvidedr接口,以下所示:

Enum也實現了IFormattable接口,具體實現以下:

 
 
public string ToString(string format, IFormatProvider provider) => this.ToString(format);
public string ToString(string format)
{
    if ((format == null) || (format.Length == 0))
    {
        format = "G";
    }
    if (string.Compare(format, "G", StringComparison.OrdinalIgnoreCase) == 0)
    {
        return this.ToString();
    }
    if (string.Compare(format, "D", StringComparison.OrdinalIgnoreCase) == 0)
    {
        return this.GetValue().ToString();
    }
    if (string.Compare(format, "X", StringComparison.OrdinalIgnoreCase) == 0)
    {
        return InternalFormattedHexString(this.GetValue());
    }
    if (string.Compare(format, "F", StringComparison.OrdinalIgnoreCase) != 0)
    {
        throw new FormatException(Environment.GetResourceString("Format_InvalidEnumFormatSpecification"));
    }
    return InternalFlagsFormat((RuntimeType) base.GetType(), this.GetValue());
}

查看源代碼發現,Enum的ToString()方法並無使用IFormatProvider參數,緣由是由於,Enum和語言無關,通常用於內部編程使用,因此不須要這個參數.

調用代碼以下:

        static void Main(string[] args)
        {
            var a = Type.a;
            //返回常規的字符串,也就是a的字符串形式,輸出:a
            Console.WriteLine(a.ToString("G"));
            //返回a的枚舉值,輸出:1
            Console.WriteLine(a.ToString("D"));
            //返回a的十六進制表現形式,輸出:00000001
            Console.WriteLine(a.ToString("X"));
            //返回a的字符串形式,輸出:a
            Console.WriteLine(a.ToString("F"));
            Console.ReadKey();
        }
        enum Type
        {
            a = 1,
            b = 2,
            c = 3
        }

 

三、DateTime類型的字符串輸出

由於,不一樣國家的時間展現不同,因此DateTime的字符串輸出必須使用到IFormatProvider參數

DateTime實現了IFormattable接口,因此它能夠自定義地構造咱們想要的DateTime字符串,具體實現以下:

第一步:

DateTimeFormatInfo類實現了IFormatProvider接口.下面是其靜態方法GetInstance()方法的明細:

該方法獲取了傳入IFormatProvider參數的對應語言文化的時間格式化信息(DateTimeFormatInfo)實例.

 

第二步:

在獲取完對應語言文化的(DateTimeFormatInfo實例)以後,將全部的參數將給DateTimeFormat工具類來處理.其靜態方法Format方法以下:

internal static string Format(DateTime dateTime, string format, DateTimeFormatInfo dtfi, TimeSpan offset)
{
    if ((format == null) || (format.Length == 0))
    {
        bool flag = false;
        if (dateTime.Ticks < 0xc92a69c000L)
        {
            switch (dtfi.Calendar.ID)
            {
                case 0x16:
                case 0x17:
                case 3:
                case 4:
                case 6:
                case 8:
                case 13:
                    flag = true;
                    dtfi = DateTimeFormatInfo.InvariantInfo;
                    break;
            }
        }
        if (offset == NullOffset)
        {
            if (flag)
            {
                format = "s";
            }
            else
            {
                format = "G";
            }
        }
        else if (flag)
        {
            format = "yyyy'-'MM'-'ddTHH':'mm':'ss zzz";
        }
        else
        {
            format = dtfi.DateTimeOffsetPattern;
        }
    }
    if (format.Length == 1)
    {
        format = ExpandPredefinedFormat(format, ref dateTime, ref dtfi, ref offset);
    }
    return FormatCustomized(dateTime, format, dtfi, offset);
}

該方法將傳入的format進行生成規則的匹配,而後結合語言文化,和日期值,返回一個指望的字符串

 

(1)、當傳入的format參數只有一個時候:

CLR是這麼處理的,根據傳入的參數獲取對應的日期字符串格式,全部的單個format參數以下:

internal static string GetRealFormat(string format, DateTimeFormatInfo dtfi)
{
    switch (format[0])
    {
        case 'D':
            return dtfi.LongDatePattern;

        case 'F':
            return dtfi.FullDateTimePattern;

        case 'G':
            return dtfi.GeneralLongTimePattern;

        case 'M':
        case 'm':
            return dtfi.MonthDayPattern;

        case 'O':
        case 'o':
            return "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK";

        case 'R':
        case 'r':
            return dtfi.RFC1123Pattern;

        case 'T':
            return dtfi.LongTimePattern;

        case 'U':
            return dtfi.FullDateTimePattern;

        case 'd':
            return dtfi.ShortDatePattern;

        case 'f':
            return (dtfi.LongDatePattern + " " + dtfi.ShortTimePattern);

        case 'g':
            return dtfi.GeneralShortTimePattern;

        case 'Y':
        case 'y':
            return dtfi.YearMonthPattern;

        case 's':
            return dtfi.SortableDateTimePattern;

        case 't':
            return dtfi.ShortTimePattern;

        case 'u':
            return dtfi.UniversalSortableDateTimePattern;
    }
    throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
}

根據傳入的單個參數,CLR獲取其對應的日期格式展現參數,

最後將其和日期值結合,生成對應的StringBuilder對象,並對其進行輸出,後續的代碼由於太長,因此不展現原理就是如此,隨後返回一個指望的字符串值.

調用代碼以下:

        static void Main(string[] args)
        {
            var dateFlag = new String[] { "G", "d", "D", "g", "M", "m", "s", "T", "t", "u", "U" , "Y" , "r" , "R" , "o" , "O" , "F" , "f" };
            var now = DateTime.Now;
            for (var i = 0; i < dateFlag.Length; i++)
            {
                var flag = dateFlag[i];
                Console.WriteLine(flag+"   對應的日期生成規則的輸出是:{0}", now.ToString(flag));
            }
            Console.ReadKey();
        }

 

 (2)、當傳入的format參數是個字符串的時候

CLR會根據傳入的參數值逐個解析,可是遵循如下規則:

yyyy-表明年份

dd-表明日

MM-表明月份

HH:表明當前小時

mm:表明當前分鐘

ss:表明當前秒

g:表明公元

這些標誌會被CLR正確解析成對應的字段,其他的字符會被CLR當作分隔符留用,代碼以下:

var now = DateTime.Now;
Console.WriteLine(now.ToString("gyyyy分MM隔HH:mm:ss"));

 

四、IFormattable接口實現方法參數解析

(1)、IFormatProvider參數

DateTime默認的ToString()方法

DateTimeFormatInfo.CurrentInfo代碼以下:

能夠,看出,不給ToString()方法傳遞IFormatProvider參數,CLR會默認採用當前線程的DateTimeFormatInfo對象實例.

注:FCL中實現IFormatProvider的接口只有三個,分別是

這些類中存在一些構造並格式化字符串時,必要的屬性信息(按語言區分).

 

五、輸出一個德國的時間字符串

var now = DateTime.Now;
//按照德文輸出當前時間 g-表明公元開始時間
Console.WriteLine(now.ToString("gyyyy:MM:dd HH:mm:ss",new CultureInfo("de-DE")));

相關文章
相關標籤/搜索