Procedura: definire e utilizzare provider di formati numerici personalizzati

.NET Framework offre un ampio controllo sulla rappresentazione di stringa dei valori numerici e supporta le funzionalità seguenti per la personalizzazione del formato di tali valori:

  • Stringhe in formato numerico standard, che forniscono un insieme predefinito di formati per la conversione dei numeri nella rispettiva rappresentazione di stringa. È possibile usarle con qualsiasi metodo di formattazione numerica, ad esempio Decimal.ToString(String), che include un parametro format. Per informazioni dettagliate, vedere Stringhe di formato numerico standard.

  • Stringhe in formato numerico personalizzato che forniscono un insieme di simboli che possono essere combinati per definire identificatori di formato numerico personalizzato. È anche possibile usarle con qualsiasi metodo di formattazione numerica, ad esempio Decimal.ToString(String), che include un parametro format. Per informazioni dettagliate, vedere Stringhe in formato numerico personalizzato.

  • Oggetti CultureInfo o NumberFormatInfo personalizzati, che definiscono i simboli e i modelli di formato usati per la visualizzazione delle rappresentazioni di stringa di valori numerici. È possibile usarle con qualsiasi metodo di formattazione numerica, ad esempio ToString, che include un parametro provider. In genere, il parametro provider viene usato per specificare un formato specifico delle impostazioni cultura.

In alcuni casi, ad esempio quando un'applicazione deve visualizzare un numero di account formattato, un numero di identificazione o un codice postale, queste tre tecniche non sono adatte. .NET consente anche di definire un oggetto di formattazione diverso da un oggetto CultureInfo o NumberFormatInfo per determinare come viene formattato un valore numerico. In questo argomento sono contenute istruzioni dettagliate per l'implementazione di tale oggetto ed è anche riportato un esempio di formattazione di numeri di telefono.

Definire un provider di formato personalizzato

  1. Definire una classe che implementa le interfacce IFormatProvider e ICustomFormatter.

  2. Implementa il metodo IFormatProvider.GetFormat. GetFormat è un metodo di callback richiamato dal metodo di formattazione, ad esempio il metodo String.Format(IFormatProvider, String, Object[]), per recuperare l'oggetto effettivamente responsabile della formattazione personalizzata. Una tipica implementazione di GetFormat esegue le operazioni seguenti:

    1. Determina se l'oggetto Type passato come parametro del metodo rappresenta un'interfaccia ICustomFormatter.

    2. Se il parametro non rappresenta l'interfaccia ICustomFormatter, GetFormat restituisce un oggetto che implementa l'interfaccia ICustomFormatter responsabile della formattazione personalizzata. In genere, l'oggetto di formattazione personalizzata restituisce se stesso.

    3. Se il parametro non rappresenta l'interfaccia ICustomFormatter, GetFormat restituisce null.

  3. Implementa il metodo Format. Questo metodo viene chiamato dal metodo String.Format(IFormatProvider, String, Object[]) ed è responsabile della restituzione della rappresentazione di stringa di un numero. L'implementazione del metodo in genere implica le attività seguenti:

    1. Facoltativamente, è possibile verificare che il metodo sia legittimamente responsabile di fornire servizi di formattazione esaminando il parametro provider. Per la formattazione di oggetti che implementano sia IFormatProvider sia ICustomFormatter, questa operazione comporta l'esecuzione di test sul parametro provider per verificarne l'uguaglianza all'oggetto di formattazione corrente.

    2. Stabilire se l'oggetto di formattazione deve supportare identificatori di formato personalizzato. Ad esempio, un identificatore di formato "N" potrebbe indicare che un numero di telefono degli Stati Uniti deve essere restituito in formato NANP e un identificatore di formato "I" potrebbe indicare l'output in formato ITU-T Recommendation E.123. Se si usano identificatori di formato, il metodo deve gestire l'identificatore di formato specifico. Quest'ultimo viene passato al metodo nel parametro format. Se non è disponibile alcun identificatore, il valore del parametro format è String.Empty.

    3. Recuperare il valore numerico passato al metodo come parametro arg. Eseguire le eventuali modifiche necessarie per convertirlo nella relativa rappresentazione di stringa.

    4. Restituire la rappresentazione di stringa del parametro arg.

Usare un oggetto di formattazione numerica personalizzato

  1. Creare una nuova istanza della classe di formattazione personalizzata.

  2. Chiamare il metodo di formattazione String.Format(IFormatProvider, String, Object[]), passandovi l'oggetto di formattazione personalizzato, l'identificatore di formattazione (o String.Empty se non è specificato alcun identificatore) e il valore numerico da formattare.

Esempio

Nell'esempio seguente viene definito un provider di formato numerico personalizzato denominato TelephoneFormatter che converte un numero che rappresenta un numero telefonico degli Stati Uniti nel suo formato NANP oppure E.123. Il metodo gestisce due identificatori di formato, "N" (che restituisce il formato NANP) e "I" (che restituisce il formato E.123 internazionale).

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

Il provider di formati numerici personalizzati può essere usato solo con il metodo String.Format(IFormatProvider, String, Object[]). Gli altri overload di metodi di formattazione numerica, come ToString, che includono un parametro di tipo IFormatProvider, passano tutti l'implementazione IFormatProvider.GetFormat di un oggetto Type che rappresenta il tipo NumberFormatInfo. In cambio, si aspettano che il metodo restituisca un oggetto NumberFormatInfo. In caso contrario, il provider di formati numerici personalizzati viene ignorato e al suo posto viene usato l'oggetto NumberFormatInfo per le impostazioni cultura correnti. Nell'esempio il metodo TelephoneFormatter.GetFormat gestisce la possibilità di essere passato erroneamente a un metodo di formattazione numerica esaminando il parametro del metodo e restituendo null se rappresenta un tipo diverso da ICustomFormatter.

Se un provider di formati numerici personalizzati supporta un set di identificatori di formato, assicurarsi di specificare un comportamento predefinito, nel caso in cui non venga fornito alcun identificatore di formato nell'elemento di formato usato nella chiamata al metodo String.Format(IFormatProvider, String, Object[]). Nell'esempio "N" è l'identificatore di formato predefinito. È pertanto possibile convertire un numero in un numero di telefono formattato specificando un identificatore di formato esplicito. Nell'esempio riportato di seguito viene illustrata questa chiamata al metodo.

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

L'esecuzione della conversione è possibile anche in assenza di identificatori di formato. Nell'esempio riportato di seguito viene illustrata questa chiamata al metodo.

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

Se non è definito alcun identificatore di formato predefinito, l'implementazione del metodo ICustomFormatter.Format deve includere codice come quello riportato di seguito in modo da permettere a .NET di fornire la formattazione non supportata dal codice.

if (arg is IFormattable)
   s = ((IFormattable)arg).ToString(format, formatProvider);
else if (arg != null)
   s = arg.ToString();
If TypeOf (arg) Is IFormattable Then
    s = DirectCast(arg, IFormattable).ToString(fmt, formatProvider)
ElseIf arg IsNot Nothing Then
    s = arg.ToString()
End If

In questo esempio il metodo che implementa ICustomFormatter.Format ha lo scopo di fungere da metodo di callback per il metodo String.Format(IFormatProvider, String, Object[]). Di conseguenza, il metodo esamina il parametro formatProvider per determinare se contiene un riferimento all'oggetto TelephoneFormatter corrente. Il metodo può tuttavia essere chiamato anche direttamente dal codice. In questo caso, è possibile usare il parametro formatProvider per specificare un oggetto CultureInfo o NumberFormatInfo che fornisce informazioni di formattazione specifiche delle impostazioni cultura.