• C# 自定义类型通过实现IFormattable接口,来输出指定的格式和语言文化的字符串(例:DateTime)


    常规的调用ToString()方法,存在两个问题.

    (1)、调用者无法控制字符串的格式

    (2)、调用者不能方便的选择一种特定的语言文化来格式化字符串.

    在开发一些国际化的应用时,应用程序需要调用与当前线程不同的语言文化来格式化字符串.

    so,为了对字符串进行更多的控制,你重写的的ToString()方法应该允许指定具体的格式和语言文化信息.

    为了能使调用者在调用对象实例的ToString()方法的时候,选择格式和语言文化,该对象应该实现System.IFormattable接口,接口代码如下:

        //
        // 摘要:
        //     提供一种功能,用以将对象的值格式化为字符串表示形式。
        [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参数:指定对应类型的格式化信息,一般和语言文化类型有关

    FCL(Framework Common Language)中的所有基类型(Byte,SByte,Int16/UInt16,Int32/Uint32,Int64/Uint64,Single,Double,Decimal和Datetime)都实现了这个接口,这些基类型调用ToString方法之后,返回的都是字面值的字符串形式,此外FCL中还有一些类型实现了这个接口.

    1、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"));

    2、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
            }

    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"));

    4、IFormattable接口实现方法参数解析

    (1)、IFormatProvider参数

    DateTime默认的ToString()方法

    DateTimeFormatInfo.CurrentInfo代码如下:

    可以,看出,不给ToString()方法传递IFormatProvider参数,CLR会默认采用当前线程的DateTimeFormatInfo对象实例.

    注:FCL中实现IFormatProvider的接口只有三个,分别是

    这些类中存在一些构造并格式化字符串时,必要的属性信息(按语言区分).

    5、输出一个德国的时间字符串

    var now = DateTime.Now;
    //按照德文输出当前时间 g-代表公元开始时间
    Console.WriteLine(now.ToString("gyyyy:MM:dd HH:mm:ss",new CultureInfo("de-DE")));

  • 相关阅读:
    Ubuntu 安装和使用 Supervisor(进程管理)
    Ubuntu查看端口占用及关闭
    Ubuntu 上安装 SQL Server 并创建数据库
    Kafka常用命令
    sql bug
    TiDB之PCTP(数据库专家)
    04 MySQL之函数
    05 MySQL之查询、插入、更新与删除
    03 MySQL之数据类型和运算符
    06 MySQL之索引
  • 原文地址:https://www.cnblogs.com/GreenLeaves/p/9142755.html
Copyright © 2020-2023  润新知