首页 > 编程语言 >关于C#里IFormatProvider与IFormattable的一些思考

关于C#里IFormatProvider与IFormattable的一些思考

时间:2023-08-24 19:44:45浏览次数:49  
标签:IFormattable string format C# Format IFormatProvider ToString public

一,从时间(DateTime)出发

先上一段处理时间格式化的代码。该代码在Net6.0框架下运行。

            var time = new DateTime(2023, 8, 24);

            Console.WriteLine(time);
            
            Console.WriteLine(string.Format("{0:yyyyMMdd}", time));//写法1
            
            Console.WriteLine("Time is {0:yyyyMMdd}", time);//写法2
                                                            
            Console.WriteLine(time.ToString("yyyyMMdd"));//写法3

            //2023 / 8 / 24 0:00:00
            //20230824
            //Time is 20230824
            //20230824

以上代码通过不同的方式将时间格式化,在“yyyyMMdd”的格式要求下,都输出了同样的“20230824”。在DateTime格式化的时候又是怎样识别到“yyyyMMdd”并返回对应的结果?

二,初探源码

        public static string Format(string format, object? arg0)
        {
            return FormatHelper(null, format, new ParamsArray(arg0));
        }
Format
        private static string FormatHelper(IFormatProvider? provider, string format, ParamsArray args)
        {
            if (format == null)
                throw new ArgumentNullException(nameof(format));

            var sb = new ValueStringBuilder(stackalloc char[256]);
            sb.EnsureCapacity(format.Length + args.Length * 8);
            sb.AppendFormatHelper(provider, format, args);
            return sb.ToString();
        }
FormatHelper

 通过string.Fomat的源代码往上找,可以看到实际调用的ValueStringBuilder的AppendFormatHelper方法后,后返回ToString()。分别查找AppendFormatHelperToString的实现做了什么。

 1 internal void AppendFormatHelper(IFormatProvider? provider, string format, ReadOnlySpan<object?> args)
 2         {
 3             
 4             //省略部分代码...
 5 
 6             // Query the provider (if one was supplied) for an ICustomFormatter.  If there is one,
 7             // it needs to be used to transform all arguments.
 8             ICustomFormatter? cf = (ICustomFormatter?)provider?.GetFormat(typeof(ICustomFormatter));
 9 
10             // Repeatedly find the next hole and process it.
11             int pos = 0;
12             char ch;
13             while (true)
14             {
15 
16                 ReadOnlySpan<char> itemFormatSpan = default; // used if itemFormat is null
17 
18                 string? s = null;
19                 string? itemFormat = null;
20 
21 
22                 if (cf != null)
23                 {
24                     if (!itemFormatSpan.IsEmpty)
25                     {
26                         itemFormat = new string(itemFormatSpan);
27                     }
28 
29                     s = cf.Format(itemFormat, arg, provider);
30                 }
31 
32                 if (s == null)
33                 {
34                     // Otherwise, fallback to trying IFormattable or calling ToString.
35                     if (arg is IFormattable formattableArg)
36                     {
37                         if (itemFormatSpan.Length != 0)
38                         {
39                             itemFormat ??= new string(itemFormatSpan);
40                         }
41                         s = formattableArg.ToString(itemFormat, provider);
42                     }
43                     else
44                     {
45                         s = arg?.ToString();
46                     }
47 
48                     s ??= string.Empty;
49                 }
50 
51                 //省略部分代码...
52             }
53         }
AppendFormatHelper

可以看到如果提供IFormatProvider,则可以通过自定义的ICustomFormatter.Format获取对应的格式。而如果参数继承了IFormattable,则调用IFormattable.ToString(string? format, IFormatProvider? formatProvider)方法。

        public readonly partial struct DateTime : IComparable, ISpanFormattable, IConvertible, IComparable<DateTime>, IEquatable<DateTime>, ISerializable

        public string ToString(string? format)
        {
            return DateTimeFormat.Format(this, format, null);
        }

        public string ToString(string? format, IFormatProvider? provider)
        {
            return DateTimeFormat.Format(this, format, provider);
        }
DateTime

其实’string.Format‘(写法1)和 ’time.ToString("yyyyMMdd")‘(写法2)都是调用到DateTimeFormat.Format(this, format, null);

而关于 DateTimeFormat.Format 的具体实现这里不详细说明,感兴趣者可点击链接跳转源码自行研究。而本文着重于IFormatProvider与IFormattable。

DateTime实现了接口ISpanFormattable,该接口也继承自IFormattable。当DateTime需要进行字符串格式化时,会调用到IFormattable.ToString(string? format, IFormatProvider? formatProvider)。format传入格式,而IFormatProvider可以对不同的参数类型

提供不同的自定义的ICustomFormatter对格式进行不同的处理。

    public interface IFormattable
    {
        string ToString(string? format, IFormatProvider? formatProvider);
    }

    public interface IFormatProvider
    {
        object? GetFormat(Type? formatType);
    }
    public interface ICustomFormatter
    {

        string Format(string? format, object? arg, IFormatProvider? formatProvider);
    }
IFormattable,IFormatProvider,ICustomFormatter

 

三,拓展

 那么,如果想对某个类做到类似于time.ToString("yyyyMMdd")同等的效果,该如何改造呢?先查看以下例子。

        void Test()
        {
            //example
            var p = new Person();
            p.Name = "路人甲";
            //输出Name
            Console.WriteLine("{0:N}", p);

            Console.WriteLine(p.ToString("N"));
        }
        internal class Person
        {
            public string Name { get; set; }
            public int Age { get; set; }
            public DateTime Birthdate { get; set; }
        }

 

Person类有属性Name。当Person实例进行字符串格式化输出,以“N”为格式,输出Name属性。当然编译器不会让上述代码通过编译,因为会报出错误CS1501:“ToString”方法没有采用 1 个参数的重载

那么可以分两种情况进行修改,

1,有修改Person类的权限,可以重新编译Person类所在的项目并生成新的程序集

     1)让Person类继承IFormattable并实现其方法。

    internal class Person:IFormattable
    {
        public string Name { get; set; }

        public string ToString(string? format, IFormatProvider? formatProvider=null)
        {
            switch (format)
            {
                case "N":
                    return Name;      
                default:
                    return this.ToString();
            }
        }
    }

2,没有修改Person类的权限

  1)通过扩展方法实现类似于time.ToString("yyyyMMdd")同等的效果

        public static class PersonExtenion
        {
            public static string ToString(this Person person2, string? format, IFormatProvider? formatProvider)
            {
                if (formatProvider == null)
                {
                    switch (format)
                    {
                        case "N":
                            return person2.Name;
                        default:
                            return person2.ToString();
                    }
                }
                else
                {
                    var custom = formatProvider.GetFormat(person2.GetType());
                    return ((ICustomFormatter)custom).Format(format, person2, formatProvider);
                }
            }
        }
PersonExtenion

  2)在调用string.Format时传入自定义的IFormatProvider和ICustomFormatter

    internal class PersonFormatProvider : IFormatProvider
    {
        public object? GetFormat(Type? formatType)
        {
            return new PersonDefaultFormatter<Person>();
        }
    }
    public class PersonDefaultFormatter<T> : ICustomFormatter where T: Person
    {
        public string Format(string? format, object? arg, IFormatProvider? formatProvider)
        {

            switch (format)
            {
                case "N":
                    return ((T)arg).Name;

                default:
                    return arg.ToString();
            }
        }
PersonFormatProvider,PersonDefaultFormatter

当然,上述代码依然有瑕疵。当传入多个相同类型或不同参数时(如下),因为同一字符串共用同一个IFormatProvider,则传入args的类型作多次判断分开处理。

 Console.WriteLine(string.Format(new PersonFormatProvider(),
                "{0:N}'s birthday is {1:yyyyMMdd}", person,person.Birthdate ));

此时建议选择StringBuilder实例的appendFomat或append方法将字符串分开处理。

 

标签:IFormattable,string,format,C#,Format,IFormatProvider,ToString,public
From: https://www.cnblogs.com/mikodopants/p/17654540.html

相关文章

  • Ubuntu22隐藏鼠标的指针(cursor)
    目标:一段时间鼠标没有移动,则隐藏游标(cursor)1.安装unclutter-xfixes(unclutter的修复版)$sudoapt-getupdate$sudoapt-getinstallunclutter-xfixes2.启动unclutter-xfixes(一般启动)#5秒钟没有移动鼠标,则cursor消失$unclutter--timeout53.启动unclutter-xfixes(......
  • 芯片设计中的ECO是什么? ------ 转载
    本文转载自: 芯片设计中的ECO是什么?-腾讯云开发者社区-腾讯云(tencent.com) 如标题所写,我们今天聊一聊IC设计种的ECO。在展开关于ECO的概念之前,我们先大致捋下数字IC设计的流程,有助于我们后面的讨论。数字IC设计流程简述1、确定项目需求根据市场或者芯片功能要求,设计芯片的......
  • 切换Mac后maven项目无法启动报错
    `/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/bin/java-agentlib:jdwp=transport=dt_socket,address=127.0.0.1:53666,suspend=y,server=n-XX:TieredStopAtLevel=1-noverify-Dspring.output.ansi.enabled=always-Dcom.sun.management.jmxremote-Dspri......
  • C++对象的创建和销毁过程分析
    对象的创建和销毁过程分析1、对象的创建过程①给对象划分内存空间(栈、堆)②执行初始化列表根据继承表的顺序调用父类的无参构造或有参构造通过:父类(val)调用父类的有参构造根据成员变量的定义顺序调用类类型成员的无参构造或有参构造通过:类类型成员名(val)调用类类型成员......
  • C++面向对象、类和对象、访问控制限定符
    面向对象和面向过程面向过程:关注如何解决问题,以及解决问题的步骤面向对象:关注的解决问题的"人"即"对象",以及实现能解决问题的"对象"注意:面向对象的细节的本质上还是面向过程,因此面向对象不是解决问题的捷径,而是以更高的维度去思考问题面向对象的四个特性:抽象:先找出(想象)......
  • 认识微服务-SpringCloud
        ......
  • Winter '24发布在即,Salesforce Flow中的最热功能不容错过!
    FlowBuilder作为自动化领域的新秀,它在功能方面已经远远超过WorkflowRules和ProcessBuilder,随着WorkflowRules和ProcessBuilder的退役,目前所有自动化都需要迁移到Flow。Winter'24发布在即,Flow中的亮点功能不容错过!一起来先睹为快吧~01在Record-TriggeredFlow中创建自定......
  • ocr 文字识别 服务
    ocr文字识别服务利用百度开源模型:地址:https://github.com/PaddlePaddle/PaddleHub/tree/develop/modules/image/text_recognition/chinese_ocr_db_crnn_mobile一个开发web服务demofromflaskimportFlask,render_template,request,jsonifyimportrequests,jsonapp=......
  • 八月更新 | CI 构建计划触发机制升级、制品扫描 SBOM 分析功能上线!
    点击链接了解详情这个八月,腾讯云CODINGDevOps对持续集成、制品管理、项目协同、平台权限等多个产品模块进行了升级改进,为用户提供更灵活便捷的使用体验。以下是CODING新功能速递,快来看看是否有您期待已久的功能特性:01CI构建计划触发机制升级在原有代码变更及合并请求......
  • 【LeetCode1】统计参与通信的服务器
    【题目】这里有一幅服务器分布图,服务器的位置标识在m*n的整数矩阵网格grid中,1表示单元格上有服务器,0表示没有。如果两台服务器位于同一行或者同一列,我们就认为它们之间可以进行通信。请你统计并返回能够与至少一台其他服务器进行通信的服务器的数量。【示例一】......