Share via


書式付きデータの表示と保持に関するベスト プラクティス

この記事では、数値データや日時データなどの書式付きデータを、表示および格納のために処理する方法について説明します。

.NET で開発するとき、数値、日付など、文字列以外のデータをユーザー インターフェイスに表示するには、カルチャに依存する書式設定を使用します。 文字列以外のデータを文字列形式で保持するには、インバリアント カルチャを使用する書式設定を使用します。 数値データや日時データを文字列形式で保持する場合は、カルチャに依存する書式設定を使用しないでください。

書式設定されたデータを表示する

数値、日時など、文字列以外のデータをユーザーに表示するには、ユーザーのカルチャ設定を使用して書式設定します。 既定では、以下のすべてで、書式設定操作での現在のカルチャが使用されます。

  • C#Visual Basic のコンパイラでサポートされている挿入文字列。
  • C# または Visual Basic の連結演算子を使用または String.Concat メソッドを直接呼び出す文字列連結操作。
  • String.Format メソッド。
  • 数値型と日時型の ToString メソッド。

指定されたカルチャまたはインバリアント カルチャの規則を使用することで文字列を書式設定することを明示的に指定するには、次を行うことができます。

  • String.Format メソッドと ToString メソッドを使用している場合、provider パラメーター (String.Format(IFormatProvider, String, Object[]) または DateTime.ToString(IFormatProvider) など) を持つオーバー ロードを呼び出し、それに CultureInfo.CurrentCulture プロパティ、目的のカルチャを表す CultureInfo インスタンス、または CultureInfo.InvariantCulture プロパティを渡します。

  • 文字列連結の場合、コンパイラに暗黙の変換の実行を許可しないでください。 代わりに、provider パラメーターを持つ ToString オーバーロードを呼び出すことで、明示的な変換を実行します。 たとえば、次のコードでは、Double 値を文字列に変換するときに、コンパイラでは現在のカルチャが暗黙的に使用されます。

    string concat1 = "The amount is " + 126.03 + ".";
    Console.WriteLine(concat1);
    
    Dim concat1 As String = "The amount is " & 126.03 & "."
    Console.WriteLine(concat1)
    

    代わりに、次のコードのように、Double.ToString(IFormatProvider) メソッドを呼び出して、変換に使用する書式指定規則を持つカルチャを明示的に指定することができます。

    string concat2 = "The amount is " + 126.03.ToString(CultureInfo.InvariantCulture) + ".";
    Console.WriteLine(concat2);
    
    Dim concat2 As String = "The amount is " & 126.03.ToString(CultureInfo.InvariantCulture) & "."
    Console.WriteLine(concat2)
    
  • 文字列補間の場合、挿入文字列を String インスタンスに割り当てるのではなく、FormattableString に割り当てます。 その後、その FormattableString.ToString() メソッドを呼び出して、現在のカルチャの規則を反映する結果の文字列を生成することも、FormattableString.ToString(IFormatProvider) メソッドを呼び出して、指定したカルチャの規則を反映する結果の文字列を生成することもできます。

    また、書式設定可能な文字列を静的 FormattableString.Invariant メソッドに渡して、インバリアント カルチャの規則を反映する結果の文字列を生成することもできます。 このアプローチの例を次に示します。 (この例の出力には en-US の現在のカルチャが反映されます)。

    using System;
    using System.Globalization;
    
    class Program
    {
        static void Main()
        {
            Decimal value = 126.03m;
            FormattableString amount = $"The amount is {value:C}";
            Console.WriteLine(amount.ToString());
            Console.WriteLine(amount.ToString(new CultureInfo("fr-FR")));
            Console.WriteLine(FormattableString.Invariant(amount));
        }
    }
    // The example displays the following output:
    //    The amount is $126.03
    //    The amount is 126,03 €
    //    The amount is ¤126.03
    
    Imports System.Globalization
    
    Module Program
        Sub Main()
            Dim value As Decimal = 126.03
            Dim amount As FormattableString = $"The amount is {value:C}" 
            Console.WriteLine(amount.ToString())
            Console.WriteLine(amount.ToString(new CultureInfo("fr-FR")))
            Console.WriteLine(FormattableString.Invariant(amount))
        End Sub
    End Module
    ' The example displays the following output:
    '    The amount is $126.03
    '    The amount is 126,03 €
    '    The amount is ¤126.03
    

    注意

    C# を使用し、インバリアント カルチャを使用して書式設定している場合は、String.Create(IFormatProvider, DefaultInterpolatedStringHandler) を呼び出して最初のパラメータに CultureInfo.InvariantCulture を渡す方がパフォーマンスが高くなります。 詳細については、「C# 10 および .NET 6 での文字列補間」を参照してください。

書式設定されたデータを保持する

文字列以外のデータは、バイナリ データまたは書式付きデータとして保持できます。 書式付きデータとして保存するには、provider パラメーターを含む書式指定メソッドのオーバーロードを呼び出し、そのパラメーターを CultureInfo.InvariantCulture プロパティに渡す必要があります。 インバリアント カルチャは、カルチャとコンピューターに依存しない書式付きデータに一貫した書式を提供します。 これに対し、インバリアント カルチャ以外のカルチャを使用して書式設定するデータの保持には、さまざまな制限があります。

  • カルチャが異なるシステムでデータを取得したり、現在のシステムのユーザーが現在のカルチャを変更してデータを取得しようとしたりすると、そのデータは使用できない可能性があります。
  • 特定のコンピューターのカルチャのプロパティは、その標準の値とは異なる場合があります。 常に、ユーザーはカルチャに依存した表示設定をカスタマイズする可能性があります。 このため、ユーザーがカルチャの設定をカスタマイズすると、システムに保存されている書式付きデータを読み取ることができなくなる場合があります。 コンピューター間の書式付きデータの移植性がさらに制限される可能性があります。
  • 数値や日時の書式設定を制御する国際的、地域的、または国内の標準は時間と共に変化するため、これらの変化は Windows オペレーティング システムの更新プログラムに組み込まれています。 書式設定の規則が変わると、以前の規則に従って書式設定されたデータを読み取ることができなくなる場合があります。

次に、カルチャに依存する書式設定を使用してデータを保持すると移植性が制限される例を示します。 この例では、日時の値の配列をファイルに保存します。 これらは、英語 (米国) のカルチャの規則を使用して書式設定されています。 現在のカルチャがフランス語 (スイス) に変更されると、アプリケーションは現在のカルチャの書式設定規則を使用して保存された値を読み取ることを試みます。 2 つのデータ項目の読み取りを試みると、FormatException 例外がスローされます。日付の配列には、MinValue に等しい 2 つの間違った要素が含まれることになります。

using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;

public class Example
{
   private static string filename = @".\dates.dat";

   public static void Main()
   {
      DateTime[] dates = { new DateTime(1758, 5, 6, 21, 26, 0),
                           new DateTime(1818, 5, 5, 7, 19, 0),
                           new DateTime(1870, 4, 22, 23, 54, 0),
                           new DateTime(1890, 9, 8, 6, 47, 0),
                           new DateTime(1905, 2, 18, 15, 12, 0) };
      // Write the data to a file using the current culture.
      WriteData(dates);
      // Change the current culture.
      Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH");
      // Read the data using the current culture.
      DateTime[] newDates = ReadData();
      foreach (var newDate in newDates)
         Console.WriteLine(newDate.ToString("g"));
   }

   private static void WriteData(DateTime[] dates)
   {
      StreamWriter sw = new StreamWriter(filename, false, Encoding.UTF8);
      for (int ctr = 0; ctr < dates.Length; ctr++) {
         sw.Write("{0}", dates[ctr].ToString("g", CultureInfo.CurrentCulture));
         if (ctr < dates.Length - 1) sw.Write("|");
      }
      sw.Close();
   }

   private static DateTime[] ReadData()
   {
      bool exceptionOccurred = false;

      // Read file contents as a single string, then split it.
      StreamReader sr = new StreamReader(filename, Encoding.UTF8);
      string output = sr.ReadToEnd();
      sr.Close();

      string[] values = output.Split( new char[] { '|' } );
      DateTime[] newDates = new DateTime[values.Length];
      for (int ctr = 0; ctr < values.Length; ctr++) {
         try {
            newDates[ctr] = DateTime.Parse(values[ctr], CultureInfo.CurrentCulture);
         }
         catch (FormatException) {
            Console.WriteLine("Failed to parse {0}", values[ctr]);
            exceptionOccurred = true;
         }
      }
      if (exceptionOccurred) Console.WriteLine();
      return newDates;
   }
}
// The example displays the following output:
//       Failed to parse 4/22/1870 11:54 PM
//       Failed to parse 2/18/1905 3:12 PM
//
//       05.06.1758 21:26
//       05.05.1818 07:19
//       01.01.0001 00:00
//       09.08.1890 06:47
//       01.01.0001 00:00
//       01.01.0001 00:00
Imports System.Globalization
Imports System.IO
Imports System.Text
Imports System.Threading

Module Example
    Private filename As String = ".\dates.dat"

    Public Sub Main()
        Dim dates() As Date = {#5/6/1758 9:26PM#, #5/5/1818 7:19AM#, _
                                #4/22/1870 11:54PM#, #9/8/1890 6:47AM#, _
                                #2/18/1905 3:12PM#}
        ' Write the data to a file using the current culture.
        WriteData(dates)
        ' Change the current culture.
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH")
        ' Read the data using the current culture.
        Dim newDates() As Date = ReadData()
        For Each newDate In newDates
            Console.WriteLine(newDate.ToString("g"))
        Next
    End Sub

    Private Sub WriteData(dates() As Date)
        Dim sw As New StreamWriter(filename, False, Encoding.Utf8)
        For ctr As Integer = 0 To dates.Length - 1
            sw.Write("{0}", dates(ctr).ToString("g", CultureInfo.CurrentCulture))
            If ctr < dates.Length - 1 Then sw.Write("|")
        Next
        sw.Close()
    End Sub

    Private Function ReadData() As Date()
        Dim exceptionOccurred As Boolean = False

        ' Read file contents as a single string, then split it.
        Dim sr As New StreamReader(filename, Encoding.Utf8)
        Dim output As String = sr.ReadToEnd()
        sr.Close()

        Dim values() As String = output.Split({"|"c})
        Dim newDates(values.Length - 1) As Date
        For ctr As Integer = 0 To values.Length - 1
            Try
                newDates(ctr) = DateTime.Parse(values(ctr), CultureInfo.CurrentCulture)
            Catch e As FormatException
                Console.WriteLine("Failed to parse {0}", values(ctr))
                exceptionOccurred = True
            End Try
        Next
        If exceptionOccurred Then Console.WriteLine()
        Return newDates
    End Function
End Module
' The example displays the following output:
'       Failed to parse 4/22/1870 11:54 PM
'       Failed to parse 2/18/1905 3:12 PM
'       
'       05.06.1758 21:26
'       05.05.1818 07:19
'       01.01.0001 00:00
'       09.08.1890 06:47
'       01.01.0001 00:00
'       01.01.0001 00:00
'

しかし、DateTime.ToString(String, IFormatProvider) および DateTime.Parse(String, IFormatProvider) の呼び出しで、CultureInfo.CurrentCulture プロパティを CultureInfo.InvariantCulture に置き換えると、保持されている日時データは正常に復元されます。次にその出力を示します。

06.05.1758 21:26
05.05.1818 07:19
22.04.1870 23:54
08.09.1890 06:47
18.02.1905 15:12