Cómo: Definir y usar proveedores de formato numérico personalizado

Actualización: noviembre 2007

.NET Framework proporciona un amplio control sobre la representación de cadena de los valores numéricos. Admite las características siguientes para personalizar el formato de los valores numéricos:

  • Cadenas de formato numérico estándar, que proporcionan un conjunto predefinido de formatos para convertir los números en su representación de cadena. Pueden utilizarse con cualquier método de formato numérico, como Decimal.ToString(String), que tiene un parámetro format. Para obtener información más detallada, vea Cadenas con formato numérico estándar.

  • Cadenas de formato numérico personalizadas, que proporcionan un conjunto de símbolos que pueden combinarse para definir los especificadores de formato numérico personalizados. También pueden utilizarse con cualquier método de formato numérico, como Decimal.ToString(String), que tiene un parámetro format. Para obtener información más detallada, vea Cadenas con formato numérico personalizado.

  • Objetos CultureInfo o NumberFormatInfo, que definen los símbolos y los modelos de formato que se utilizan para mostrar las representaciones de cadena de los valores numéricos. Pueden utilizarse con cualquier método de formato numérico, como ToString, que tiene un parámetro provider. Normalmente, el parámetro provider se utiliza para determinar el formato específico de la referencia cultural.

En algunos casos (como cuando una aplicación debe mostrar un número de cuenta, un número de identificación o un código postal con formato) estas tres técnicas no resultan apropiadas. .NET Framework también permite definir un objeto de formato que no es ni un objeto CultureInfo ni un objeto NumberFormatInfo para determinar el modo en que debe aplicarse formato a un valor numérico. En este tema se proporcionan instrucciones paso a paso para implementar este tipo de objetos y se incluye un ejemplo en el que se da formato a números de teléfono.

Para definir un proveedor de formato personalizado

  1. Defina una clase que implemente las interfaces IFormatProvider e ICustomFormatter.

  2. Implemente el método IFormatProvider.GetFormat. GetFormat es un método de devolución de llamada al que invoca el método de formato (por ejemplo, el método String.Format(IFormatProvider, String, array<Object[])) para recuperar el objeto que es realmente responsable de realizar la operación de formato personalizado. Una implementación típica de GetFormat hace lo siguiente:

    1. Determina si el objeto Type pasado como parámetro del método representa una interfaz ICustomFormatter.

    2. Si el parámetro representa la interfaz ICustomFormatter, GetFormat devuelve un objeto que implementa la interfaz ICustomFormatter que es responsable de proporcionar el formato personalizado. Normalmente, el objeto de formato personalizado se devuelve a sí mismo.

    3. Si el parámetro no representa la interfaz ICustomFormatter, GetFormat devuelve null.

  3. Implemente el método Format. El método String.Format(IFormatProvider, String, array<Object[]) invoca a este método, que es responsable de devolver la representación de cadena de un número. Normalmente, la implementación del método conlleva las operaciones siguientes:

    1. De manera opcional, se examina el parámetro provider para asegurarse de que el método realmente se haya diseñado para proporcionar servicios de formato. En el caso de los objetos de formato que implementan IFormatProvider e ICustomFormatter, esto implica probar la igualdad del parámetro provider con el objeto de formato actual.

    2. Se determina si el objeto de formato debe admitir especificadores de formato personalizados. (Por ejemplo, un especificador de formato de "N" podría indicar que un número de teléfono de Estados Unidos debería generarse con el formato NANP, mientras que un especificador de formato "I" podría indicar que el resultado debería tener el formato de la recomendación E.123 de ITU-T). Si se utilizan especificadores de formato, el método debe controlar el especificador de formato indicado. Se pasa al método en el parámetro format. Si no hay ningún especificador presente, el valor del parámetro format es String.Empty.

    3. Se recupera el valor numérico que se pasó al método como parámetro arg. Se llevan a cabo todas las manipulaciones necesarias para convertirlo en su representación de cadena.

    4. Se devuelve la representación de cadena del parámetro arg.

Para utilizar un objeto de formato numérico personalizado

  1. Cree una nueva instancia de la clase de formato personalizado.

  2. Llame al método de formato String.Format(IFormatProvider, String, array<Object[]) pasándole el objeto de formato personalizado, el especificador de formato (o String.Empty, si no se va a usar ninguno) y el valor numérico al que se va a dar formato.

Ejemplo

En el ejemplo siguiente se define un proveedor de formato numérico personalizado denominado TelephoneFormatter que convierte un número que representa un número de teléfono de Estados Unidos en su formato NANP o E.123. El método controla dos especificadores de formato, "N" (que ofrece el resultado en formato NANP) e "I" (que ofrece el resultado en formato E.123 internacional).

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

El proveedor de formato numérico personalizado sólo puede utilizarse con el método String.Format(IFormatProvider, String, array<Object[]). Todas las demás sobrecargas de los métodos de formato numérico (como ToString) que tienen un parámetro de tipo IFormatProvider pasan a la implementación de IFormatProvider.GetFormat un objeto Type que representa el tipo NumberFormatInfo. A cambio, esperan que el método devuelva un objeto NumberFormatInfo. Si no lo hace, el proveedor de formato numérico personalizado se omite y, en su lugar, se utiliza el objeto NumberFormatInfo de la referencia cultural actual. En el ejemplo, el método TelephoneFormatter.GetFormat controla la posibilidad de que se pueda haber pasado por error en un método de formato numérico; para ello, examina el parámetro y devuelve null si representa un tipo distinto de ICustomFormatter.

Si un proveedor de formato numérico personalizado admite un conjunto de especificadores de formato, asegúrese de que proporcione un comportamiento predeterminado si no se proporciona ningún especificador de formato en el elemento de formato utilizado en la llamada al método String.Format(IFormatProvider, String, array<Object[]). En el ejemplo, "N" es el especificador de formato predeterminado. De este modo, un número puede convertirse en un número de teléfono con formato al proporcionar un especificador de formato explícito. En el siguiente ejemplo se ilustra este tipo de llamada a un método.

Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 4257884748))
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 4257884748));

Pero también permite que se realice la conversión si no hay ningún especificador de formato presente. En el siguiente ejemplo se ilustra este tipo de llamada a un método.

Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 4257884748))
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 4257884748));

Si no se define ningún especificador de formato predeterminado, la implementación del método ICustomFormatter.Format debe incluir código como el siguiente para que .NET Framework pueda proporcionar el formato que su código no admite.

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

En el caso de este ejemplo, se pretende que el método que implementa ICustomFormatter.Format actúe como un método de devolución de llamada para el método String.Format(IFormatProvider, String, array<Object[]). Por lo tanto, examina el parámetro formatProvider para determinar si contiene una referencia al objeto TelephoneFormatter actual. Sin embargo, también se puede llamar al método directamente desde el código. En ese caso, puede utilizar el parámetro formatProvider para proporcionar un objeto CultureInfo o NumberFormatInfo que proporcione información de formato específica de la referencia cultural.

Compilar el código

Compile el código en la línea de comandos utilizando csc.exe o vb.exe. Para compilar el código en Visual Studio, colóquelo en una plantilla de proyecto de aplicación de consola.

Vea también

Conceptos

Temas "Cómo..." sobre formatos