彻底学通string.Format以及IFormattable,IFormatProvider,ICustomFormatter
[2] 彻底学通string.Format以及IFormattable,IFormatProvider,ICustomFormatter
[3] 彻底学通string.Format以及IFormattable,IFormatProvider,ICustomFormatter
4.继续了解 IFormatProvider 和 ICustomFormatter 接口
到这里为止,应该说灵活应用string.Format()已经没什么多大的问题了,但是也还是存在一些问题,比如我们必须得为每个类单独去实现IFormattable接口才能实现自定义的格式化参数。在一些场后还是觉得不太方便或者说代码冗余。
.net的string.Format静态方法还提供了重载方法,具体签名如下:public static string Format(IFormatProvider provider,string format,params Object[] args)
这个方法比起原来使用的方法最前面增加了 IFormatProvider类型参数。使用此方法的优点是不需要为后面的参数对象实现 IFormattable 接口就可以使用自定义的格式化参数。既然这样的话也就解决了第4部分开头提到的问题了。
还是用例子说话,下面是正方形类 :
public class Square
{
public string Name { get; set; }
/// <summary>
/// 边长
/// </summary>
public double Side { get; set; }
public override string ToString()
{
return string.Format("{0}(Side:{1})",Name, Side);
}
}
下面是长方形类:
public class Rectangle
{
public string Name { get; set; }
/// <summary>
/// 宽
/// </summary>
public double Width { get; set; }
/// <summary>
/// 高
/// </summary>
public double Height { get; set; }
public override string ToString()
{
return string.Format("{0}(Width:{1},Height:{2})",Name, Width, Height);
}
}
两个类都重写了ToString方法,定义MyHelloFormatProvider类,该类从名称上就可以看出是格式化的提供者。
{
#region IFormatProvider Members
public object GetFormat(Type formatType)
{
return new MyHelloFormatter();
}
#endregion
}
该类实现了 IFormatProvider 接口,接口只有一个唯一的方法需要实现,GetFormat返回的是真正进行格式化操作的类,这里很像是工厂模式。
返回 MyHelloFormatter 对象之后,在MyHelloFormatter 中具体进行格式化操作。
{
#region ICustomFormatter Members
public string Format(string format, object arg, IFormatProvider formatProvider)
{
var t = "Hello ";
switch (format)
{
case "UPP":
t = t.ToUpper();
break;
case "LOW":
t = t.ToLower();
break;
default:
break;
}
return t + arg.ToString();
}
#endregion
}
MyHelloFormatter 实现了ICustomFormatter接口,该接口也只有一个唯一的方法,即实际执行格式化的方法。
如果不使用格式化参数或者格式化参数不匹配,情况会怎么样?
var msg15 = string.Format(new MyHelloFormatProvider(), "{0} {1}", new Rectangle() { Name = "MyRectangle", Width = 14.3, Height = 10 }, new Square() { Name = "MySquare", Side = 24.2 });
Console.WriteLine(msg15);
var msg16 = string.Format(new MyHelloFormatProvider(), "{0} {1}", new Rectangle() { Name = "MyRectangle", Width = 14.3, Height = 10 }.ToString(), new Square() { Name = "MySquare", Side = 24.2 }.ToString());
Console.WriteLine(msg16);
var msg17 = string.Format(new MyHelloFormatProvider(), "{0:AAA} {1:BBB}", new Rectangle() { Name = "MyRectangle", Width = 14.3, Height = 10 }, new Square() { Name = "MySquare", Side = 24.2 });
Console.WriteLine(msg17);
以上输出都是一样的: Hello MyRectangle(Width:14.3,Height:10) Hello MySquare(Side:24.2)
上面的运行结果表明,如果提供了new MyHelloFormatProvider() ,那么执行过程过是: 根据MyHelloFormatProvider 对象得到 MyHelloFormatter 对象,利用MyHelloFormatter 对象的Format方法进行格式化。
这里还有一个问题,如果 MyHelloFormatProvider 的 GetFormat返回的不是一个实现了 ICustomFormatter 接口的对象又会是什么情况呢?
答案是会报异常。 那么如果返回的是 null 呢? 答案是直接调用了对象的ToString()方法了。如果返回null,则运行结果如下:
MyRectangle(Width:14.3,Height:10) MySquare(Side:24.2)
带上格式化参数的运行结果:
Console.WriteLine(msg18);
HELLO MyRectangle(Width:14.3,Height:10) hello MySquare(Side:24.2)
通过上面的例子我们知道如果我们需要定义一种通用的格式化方式的话,不需要让类实现 IFormattable 接口,可以通过定义实现 IFormatProvider,ICustomFormatter接口的类去做,上面的无论是正方形还是长方形类都需要在前面加上 Hello 进行格式化,可以是普通的,小写的,大写的等等,不需要两个类单独去实现了,就选以后增加了圆形,三角形等等,也都能用我们已经定义好的 MyHelloFormatProvider 和 MyHelloFormatter 去进行格式化。
使用这种方式还能解决另外一个问题,假如我们已经为圆形类实现了 IFormattable 接口,并且已经实现了{0:UPP}格式化参数,但是实现的方法中没有加{0:LOW}格式化参数,而且这个类我们又不能更改(可能是.net自带的类,可能是第三方dll提供的类等等),那该怎么办呢? 显然已经不可能靠IFormattable 接口来解决了。
使用这节讲的方法就可以实现我们要求了。以下是具体实现:
public class Circle : IFormattable
{
public string Name { get; set; }
/// <summary>
/// 半径
/// </summary>
public double Radius { get; set; }
public override string ToString()
{
return string.Format("{0}(Radius:{1})", Name, Radius);
}
#region IFormattable Members
public string ToString(string format, IFormatProvider formatProvider)
{
if (string.IsNullOrEmpty(format))
return ToString();
var t = "Hello ";
switch (format)
{
case "UPP":
t = t.ToUpper();
break;
default:
break;
}
return t + Name;
}
#endregion
}
该类可以实现UPP格式化参数的格式化。
Console.WriteLine(msg19);
var msg20 = string.Format("Test: {0:UPP}", new Circle() {Name = "MyCircle", Radius = 10});
Console.WriteLine(msg20);
运行上面的代码得到:
Test: MyCircle(Radius:10)
Test: HELLO MyCircle
第一个无格式化参数,实际调用ToString()方法得到,由代码 if (string.IsNullOrEmpty(format))决定。
第二个带UPP格式化参数,也得到了预期的结果。
现在需要实现LOW的格式化参数。
Console.WriteLine(msg21);
在不修改Circle类并且不重新定义其他类的情况下就可以达到我们的要求了。
显示结果如下: Test: hello MyCircle(Radius:10) hello已经是全部小写了。
写了这么多,感觉有些乱七八糟了,发现还有很多没有提到,很多都讲重复了。本人也难得写博客,文字水平表达能力欠佳,还望阅者理解。