如何:定义和使用自定义数值格式提供程序
更新:2007 年 11 月
在 .NET Framework 中,您可以对数值的字符串表示形式施加广泛的控制。它支持用于自定义数值格式的下列功能:
标准数值格式字符串,提供用于将数字转换为其字符串表示形式的预定义格式集。可以将这些字符串与带有 format 参数的任何数值格式化方法(例如 Decimal.ToString(String))一起使用。有关详细信息,请参见标准数字格式字符串。
自定义数值格式字符串,提供可通过组合来定义自定义数值格式说明符的符号集。它们也可以与带 format 参数的任何数值格式化方法(例如 Decimal.ToString(String))一起使用。有关详细信息,请参见自定义数字格式字符串。
自定义 CultureInfo 或 NumberFormatInfo 对象,用于定义显示数值的字符串表示形式时使用的符号和格式模式。可以将这些字符串与带有 provider 参数的任何数值格式化方法(例如 ToString)一起使用。通常情况下,provider 参数用于指定区域性特定的格式。
在某些情况下(例如当应用程序必须显示已格式化的帐号、标识号或邮政编码时),这三种方法都不适用。在 .NET Framework 中,还可以定义既非 CultureInfo 又非 NumberFormatInfo 对象的格式化对象,以确定数值如何格式化。本主题将分步说明如何实现此类对象,并提供格式化电话号码的示例。
定义自定义格式提供程序
定义一个实现 IFormatProvider 和 ICustomFormatter 接口的类。
实现 IFormatProvider.GetFormat 方法。GetFormat 是一个回调方法,格式化方法(例如 String.Format(IFormatProvider, String, array<Object[]) 方法)会调用该方法来检索真正负责执行自定义格式化的对象。典型的 GetFormat 实现可执行下列任务:
确定以方法参数的形式传递的 Type 对象是否表示 ICustomFormatter 接口。
如果该参数的确表示 ICustomFormatter 接口,GetFormat 将返回一个实现负责提供自定义格式化的 ICustomFormatter 接口的对象。通常情况下,自定义格式化对象会返回自身。
如果该参数不表示 ICustomFormatter 接口,GetFormat 将返回 null。
实现 Format 方法。此方法由 String.Format(IFormatProvider, String, array<Object[]) 方法调用,它负责返回数字的字符串表示形式。实现该方法通常涉及以下过程:
(可选)通过检查 provider 参数确保该方法要以合法的方式提供格式化服务。对于同时实现 IFormatProvider 和 ICustomFormatter 的格式化对象,这涉及到测试 provider 参数与当前的格式化对象是否相等。
确定格式化对象是否应支持自定义格式说明符。(例如,格式说明符“N”可能指示应以 NANP 格式输出美国电话号码,而“I”可能指示输出应采用 ITU-T 建议的 E.123 格式。) 如果使用了格式说明符,该方法就应处理特定的格式说明符。格式说明符将以 format 参数的形式传递给该方法。如果不存在说明符,format 参数的值将为 String.Empty。
检索以 arg 参数的形式传递给该方法的数值。执行将该参数转换为字符串表示形式所需的任何操作。
返回 arg 参数的字符串表示形式。
使用自定义数值格式化对象
创建自定义格式化类的一个新实例。
调用 String.Format(IFormatProvider, String, array<Object[]) 格式化方法,并向其传递自定义格式化对象、格式化说明符(如果未使用说明符,则为 String.Empty)以及要格式化的数值。
示例
下面的示例定义一个名为 TelephoneFormatter 的自定义数值格式提供程序,此提供程序可将表示美国电话号码的数字转换为其 NANP 或 E.123 格式。该方法要处理两个格式说明符:“N”(输出 NANP 格式)和“I”(输出国际 E.123 格式)。
Public Class TelephoneFormatter : Implements IFormatProvider, ICustomFormatter
Public Function GetFormat(formatType As Type) As Object _
Implements IFormatProvider.GetFormat
If formatType Is GetType(ICustomFormatter) Then
Return Me
Else
Return Nothing
End If
End Function
Public Function Format(fmt As String, arg As Object, _
formatProvider As IFormatProvider) As String _
Implements ICustomFormatter.Format
' Check whether this is an appropriate callback
If Not Me.Equals(formatProvider) Then Return Nothing
' Set default format specifier
If String.IsNullOrEmpty(fmt) Then fmt = "N"
Dim numericString As String = arg.ToString
If fmt = "N" Then
Select Case numericString.Length
Case <= 4
Return numericString
Case 7
Return Left(numericString, 3) & "-" & Mid(numericString, 4)
Case 10
Return "(" & Left(numericString, 3) & ") " & _
Mid(numericString, 4, 3) & "-" & Mid(numericString, 7)
Case Else
Throw New FormatException( _
String.Format("'{0}' cannot be used to format {1}.", _
fmt, arg.ToString()))
End Select
ElseIf fmt = "I" Then
If numericString.Length < 10 Then
Throw New FormatException(String.Format("{0} does not have 10 digits.", arg.ToString()))
Else
numericString = "+1 " & Left(numericString, 3) & " " & Mid(numericString, 4, 3) & " " & Mid(numericString, 7)
End If
Else
Throw New FormatException(String.Format("The {0} format specifier is invalid.", fmt))
End If
Return numericString
End Function
End Class
Public Module TestTelephoneFormatter
Public Sub Main
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 0))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 911))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 8490216))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 4257884748))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 0))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 911))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 8490216))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 4257884748))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0:I}", 4257884748))
End Sub
End Module
using System;
using System.Globalization;
public class TelephoneFormatter : IFormatProvider, ICustomFormatter
{
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
return this;
else
return null;
}
public string Format(string format, object arg, IFormatProvider formatProvider)
{
// Check whether this is an appropriate callback
if (! this.Equals(formatProvider))
return null;
// Set default format specifier
if (string.IsNullOrEmpty(format))
format = "N";
string numericString = arg.ToString();
if (format == "N")
{
if (numericString.Length <= 4)
return numericString;
else if (numericString.Length == 7)
return numericString.Substring(0, 3) + "-" + numericString.Substring(3, 4);
else if (numericString.Length == 10)
return "(" + numericString.Substring(0, 3) + ") " +
numericString.Substring(3, 3) + "-" + numericString.Substring(6);
else
throw new FormatException(
string.Format("'{0}' cannot be used to format {1}.",
format, arg.ToString()));
}
else if (format == "I")
{
if (numericString.Length < 10)
throw new FormatException(string.Format("{0} does not have 10 digits.", arg.ToString()));
else
numericString = "+1 " + numericString.Substring(0, 3) + " " + numericString.Substring(3, 3) + " " + numericString.Substring(6);
}
else
{
throw new FormatException(string.Format("The {0} format specifier is invalid.", format));
}
return numericString;
}
}
public class TestTelephoneFormatter
{
public static void Main()
{
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 0));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 911));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 8490216));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 4257884748));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 0));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 911));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 8490216));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 4257884748));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:I}", 4257884748));
}
}
自定义数值格式提供程序只能结合 String.Format(IFormatProvider, String, array<Object[]) 方法使用。带有 IFormatProvider 类型参数的数值格式化方法(例如 ToString)的其他重载都会向 IFormatProvider.GetFormat 实现传递一个表示 NumberFormatInfo 类型的 Type 对象。反过来,它们期望该方法返回 NumberFormatInfo 对象。如果未返回该对象,将忽略自定义数值格式提供程序,并改用当前区域性的 NumberFormatInfo 对象。在该示例中,TelephoneFormatter.GetFormat 方法会处理被不当传递给数值格式化方法的可能性,其方式是检查方法参数,如果方法参数表示的是 ICustomFormatter 以外的类型,它将返回 null。
如果自定义数值格式提供程序支持一组格式说明符,则应确保提供当 String.Format(IFormatProvider, String, array<Object[]) 方法调用中使用的格式项中未提供格式说明符时的默认行为。在该示例中,“N”是默认的格式说明符。这样,通过提供显式的格式说明符,即可将数字转换为格式化的电话号码。下面的示例阐释了这样一个方法调用。
Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 4257884748))
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 4257884748));
但是,即使未提供格式说明符,它也允许进行转换。下面的示例阐释了这样一个方法调用。
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 4257884748))
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 4257884748));
如果未定义默认的格式说明符,ICustomFormatter.Format 方法的实现就应包括诸如以下内容的代码,以便 .NET Framework 可以提供您的代码所不支持的格式化。
If TypeOf(arg) Is IFormattable Then
s = DirectCast(arg, IFormattable).ToString(fmt, formatProvider)
ElseIf arg IsNot Nothing Then
s = arg.ToString()
End If
if (arg is IFormattable)
s = ((IFormattable)arg).ToString(format, formatProvider);
else if (arg != null)
s = arg.ToString();
在此示例中,实现 ICustomFormatter.Format 的方法将用作 String.Format(IFormatProvider, String, array<Object[]) 方法的回调方法。因此,它将检查 formatProvider 参数,以确定其是否包含对当前 TelephoneFormatter 对象的引用。但是,您也可以在代码中直接调用该方法。在这种情况下,可以使用 formatProvider 参数来提供 CultureInfo 或 NumberFormatInfo 对象,以提供区域性特定的格式化信息。
编译代码
在命令行处使用 csc.exe 或 vb.exe 编译代码。若要在 Visual Studio 中编译代码,请将代码置于控制台应用程序项目模板中。