前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅谈.Net Framework中string.Format原理

浅谈.Net Framework中string.Format原理

作者头像
小蜜蜂
发布2019-07-15 16:09:44
7610
发布2019-07-15 16:09:44
举报
文章被收录于专栏:明丰随笔明丰随笔

日常写代码的过程中,我们经常会使用string.Format来返回一段字符串:

代码语言:javascript
复制
var name = "nestor";
var msg = string.Format("Hello, I am {0:XXX}, today is {1:yyyy-MM-dd}.", name, DateTime.Now);

运行结果:Hello, I am nestor, today is 2019-07-08.

在.Net Framework中,{}已经被定义为了特殊的标记,如果我们想输出{},可以这样:

代码语言:javascript
复制
var msg2 = string.Format("Hello {{}}, I am {0}.", name);

运行结果:Hello {}, I am nestor.

我们先看一下string.Format方法签名:

代码语言:javascript
复制
static string Format(string format, params object[] args);

调用这个方法的规则是,根据参数format里面定义的序号,使用后面的args参数填充。

format里面定义的序号的格式分两种:

第一种是简单格式符:{0}这样的不带有特殊格式符的。

第二种是特殊格式符:{1:yyyy-MM-dd}带有特殊格式符的则继续分解,将冒号后面的特殊格式符分解出来,并且会作为参数去调用IFormattable里面的ToString()。

接口IFormattable的ToString方法签名

代码语言:javascript
复制
string ToString(string format, IFormatProvider formatProvider);

所以,我们想要支持带有特殊格式符的序号的话,后面传入的填充对象必须要实现IFormattable接口。

定义Person类,并实现IFormattable接口:

代码语言:javascript
复制
public class Person : IFormattable
{
  public string Name { get; set; }
  public override string ToString()
  {
    return Name;
  }
  public string ToString(string format, IFormatProvider formatProvider)
  {
    if (string.IsNullOrEmpty(format))
      return this.ToString();
    switch (format)
    {
      case "UPP":
        return Name.ToUpper();
      case "LOW":
        return Name.ToLower();
      default:
        return this.ToString();
    }
  }
}

我们使用Person对象参数传入:

代码语言:javascript
复制
var p = new Person() { Name = "nestor" };
var msg3 = string.Format("Hellos, I am {0:UPP}, today is {1:yyyy-MM-dd}."
  , p, DateTime.Now);

运行的结果:Hellos, I am NESTOR, today is 2019-07-08.

我们上面的结果,我们可以看到,我们使用了特殊格式符的序号,并且生效了。针对IFormattable接口,我们总结一下:

对于两种序号,不管是哪种格式的序号:

1. 如果填充的参数没有实现IFormattable接口,特殊格式符和简单格式符一样,都调用填充对象自己的ToString()方法。

2. 如果填充的参数实现IFormattable接口,即使是简单格式符也和特殊格式符一样,会调用填充对象实现的IFormattable接口的ToString方法。

.Net Framework中的源码逻辑:

代码语言:javascript
复制
IFormattable formattable = item as IFormattable;
if (formattable != null)
{
  if (str == null && stringBuilder != null)
  {
    str = stringBuilder.ToString();
  }
  empty = formattable.ToString(str, provider);
}
else if (item != null)
{
  empty = item.ToString();
}

目前为止,想要实现序号带有特殊格式符来控制输出的内容的话,必须要求填充的参数类型实现IFormattable接口才可以做到。

但是如果填充的参数类型是.net自带的类,或是第三方dll提供的类,我们并不能修改这些类型,那怎么办?

我们使用string.Format重载方法:

代码语言:javascript
复制
static string Format(IFormatProvider provider, string format, params object[] args);

我们可以通过传入IFormatProvider接口来控制输出格式,.Net Framework中的源码逻辑:

代码语言:javascript
复制
if (provider != null)
{
    customFormatter = (ICustomFormatter)provider.GetFormat(typeof(ICustomFormatter));
}
string empty = null;
if (customFormatter != null)
{
    empty = customFormatter.Format(str, item, provider);
}

这里涉及到IFormatProvider接口和ICustomFormatter接口两个接口。

IFormatProvider接口签名:

代码语言:javascript
复制
object GetFormat(Type formatType);

ICustomFormatter接口签名:

代码语言:javascript
复制
string Format(string format, object arg, IFormatProvider formatProvider);

对于IFormatProvider接口里面的GetFormat方法返回值类型是object,其实是不准确的,严格来说应该返回ICustomFormatter接口类型,如果返回的对象没有实现ICustomFormatter接口,会报异常。

如果返回null, .Net Framework会认为提供的IFormatProvider provider参数无效,接下来会当成调用下面的方法来处理。

代码语言:javascript
复制
static string Format(string format, params object[] args);

.Net Framework的源码大致如下:

代码语言:javascript
复制
if (customFormatter != null)
{
    if (stringBuilder != null)
    {
        str = stringBuilder.ToString();
    }
    empty = customFormatter.Format(str, item, provider);
}
if (empty == null)
{
    IFormattable formattable = item as IFormattable;
    if (formattable != null)
    {
        if (str == null && stringBuilder != null)
        {
            str = stringBuilder.ToString();
        }
        empty = formattable.ToString(str, provider);
    }
    else if (item != null)
    {
        empty = item.ToString();
    }
}

好,到这里我们知道,如果我们想要准确调用:

代码语言:javascript
复制
static string Format(IFormatProvider provider, string format, params object[] args);

我们需要实现IFormatProvider接口和ICustomFormatter接口

定义MyFormatProvider类实现IFormatProvider接口:

代码语言:javascript
复制
public class MyFormatProvider : IFormatProvider
{
  public object GetFormat(Type formatType)
  {
    return new MyCustomFormatter();
  }
}

定义MyCustomFormatter类实现ICustomFormatter接口:

代码语言:javascript
复制
public class MyCustomFormatter : ICustomFormatter
{
  public string Format(string format, object arg, IFormatProvider formatProvider)
  {
    var str = arg.ToString();
    switch (format)
    {
      case "UPP":
        str = str.ToUpper();
        break;
      case "LOW":
        str = str.ToLower();
        break;
      case "APPEND":
        str = str + "~";
        break;
      default:
        break;
    }
    return "Custom " + str;
  }
}

显式提供MyFormatProvider对象,并出入string.Format:

代码语言:javascript
复制
var msg4 = string.Format(new MyFormatProvider(), "{0:APPEND}", new Person() { Name = "nestor" });

运行的结果:Custom nestor~

经过一番研究,虽然这两个接口定义的有点不太友好,使用起来也很别扭,但是至少是可以工作的,也是.Net Framework提供的一种方案。

最后对这篇文章进行总结:

.Net Framework提供的string.Format()方法可以控制填入的参数最后字符串返回的格式,并提供了两种形式的控制:简单格式符和特殊格式符。

1. 重写填入的参数object.ToString()方法,但是只能满足简单格式符应用场景。

2. 填入的参数实现IFormattable接口,可以满足特殊格式符应用场景,但是必须要修改定义参数的源码来完成。

3. 实现IFormatProvider接口和ICustomFormatter接口,并显式传入string.Format()方法,实现方式复杂,但是好处是不需要修改填入的参数的源码。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 明丰随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档