2013-08-13T23:29:23の更新内容

programming/netfx/tips/encoding_fallback/index.wiki.txt

current previous
1,370 0,0
+
${smdncms:title,文字化けが起きたときの動作(フォールバック)を変更する}
+
${smdncms:header_title,文字化けが起きたときの動作(フォールバック)を変更する (EncoderFallback, DecoderFallback)}
+
${smdncms:keywords,C#,VB.NET,Encoding.GetEncoding,EncoderFallback,DecoderFallback,EncoderReplacementFallback,DecoderReplacementFallback,EncoderExceptionFallback,DecoderExceptionFallback}
+
${smdncms:tags,api/.net,lang/c#}
+

          
+
&msdn(netfx,type,System.Text.Encoding){Encodingクラス};では、エンコード・デコードできない文字があった場合は代替文字への置き換えが行われる。 &msdn(netfx,member,System.Text.Encoding.GetEncoding){GetEncodingメソッド};の引数で&msdn(netfx,type,System.Text.EncoderFallback){EncoderFallback};・&msdn(netfx,type,System.Text.DecoderFallback){DecoderFallback};を指定すると、エンコード・デコードできない文字があった場合の動作(フォールバック)を変更することができる。
+

          
+
-関連するページ
+
--[[programming/netfx/tips/unicode_encoding_bom]]
+
--[[programming/netfx/tips/determine_shiftjis_eucjp]]
+
--[[programming/netfx/tips/check_ngchar]]
+

          
+
#googleadunit
+

          
+
*デフォルトの動作
+
デフォルトでは、Encodingクラスはエンコード・デコードできない文字を代替文字に置き換える。 UTF-8やUTF-16へのエンコード/からのデコードでは``�``(U+FFFD REPLACEMENT CHARACTER)が、ASCIIやShift JISなどへのエンコードでは``?``(クエスチョンマーク)が代替文字として使用される。
+

          
+
#code(cs,エンコード時の文字化け){{
+
using System;
+
using System.Text;
+

          
+
class Sample {
+
  static void Main()
+
  {
+
    var shiftjis = Encoding.GetEncoding("Shift_JIS");
+
    var text = "⑳㉑"; // '㉑'はShift JISには存在しないのでエンコードの際に文字化けする
+

          
+
    // バイト配列に変換
+
    // (ここで文字'㉑'はエンコードされず代替文字'?'に置き換えられる)
+
    var bytes = shiftjis.GetBytes(text);
+

          
+
    // 再び文字列に戻して表示する
+
    Console.WriteLine(shiftjis.GetString(bytes));
+
   }
+
}
+
}}
+

          
+
#prompt(実行結果){{
+
⑳?
+
}}
+

          
+
#code(cs,デコード時の文字化け){{
+
using System;
+
using System.Text;
+

          
+
class Sample {
+
  static void Main()
+
  {
+
    var utf8 = Encoding.GetEncoding("UTF-8");
+
    var bytes = new byte[] {0x93, 0xFA, 0x96, 0x7B, 0x8C, 0xEA}; // Shift JISでの文字列 "日本語"
+

          
+
    // バイト配列を文字列に変換
+
    // (ここでデコードできない文字は代替文字'�'(U+FFFD)に置き換えられる)
+
    Console.WriteLine(utf8.GetString(bytes));
+
  }
+
}
+
}}
+

          
+
#prompt(実行結果){{
+
���{��
+
}}
+

          
+
*代替文字列への置き換え (EncoderReplacementFallback/DecoderReplacementFallback)
+
&msdn(netfx,type,System.Text.EncoderReplacementFallback){EncoderReplacementFallbackクラス};・&msdn(netfx,type,System.Text.DecoderReplacementFallback){DecoderReplacementFallbackクラス};を使うと、エンコード・デコード時に使用する代替文字列を指定することができる。
+

          
+
&msdn(netfx,member,System.Text.Encoding.GetEncoding){GetEncodingメソッド};は引数&var{encoderFallback};と&var{decoderFallback};で任意の&msdn(netfx,type,System.Text.EncoderFallback){EncoderFallback};・&msdn(netfx,type,System.Text.DecoderFallback){DecoderFallback};を指定できるようになっている。 この引数にEncoderReplacementFallback・DecoderReplacementFallbackを渡すことでエンコード・デコード時に使用する代替文字列を指定することができるようになる。
+

          
+
#code(cs,エンコード時の代替文字列を指定する例){{
+
using System;
+
using System.Text;
+

          
+
class Sample {
+
  static void Main()
+
  {
+
    // エンコードできない文字を下駄記号'〓'に置き換えるフォールバックを作成
+
    var encoderFallback = new EncoderReplacementFallback("〓");
+

          
+
    // フォールバックを指定してEncodingを取得
+
    var shiftjis = Encoding.GetEncoding("Shift_JIS",
+
                                        encoderFallback,
+
                                        DecoderFallback.ReplacementFallback /*定義済みの置換フォールバック*/);
+

          
+
    var text = "⑳㉑"; // '㉑'はShift JISには存在しないのでエンコードの際に文字化けする
+

          
+
    // バイト配列に変換
+
    // (ここで文字'㉑'はエンコードされず下駄記号'〓'に置き換えられる)
+
    var bytes = shiftjis.GetBytes(text);
+

          
+
    // 再び文字列に戻して表示する
+
    Console.WriteLine(shiftjis.GetString(bytes));
+
  }
+
}
+
}}
+

          
+
#prompt(実行結果){{
+
⑳〓
+
}}
+

          
+
#code(cs,デコード時の代替文字列を指定する例){{
+
using System;
+
using System.Text;
+

          
+
class Sample {
+
  static void Main()
+
  {
+
    // デコードできない文字を下駄記号'〓'に置き換えるフォールバックを作成
+
    var decoderFallback = new DecoderReplacementFallback("〓");
+

          
+
    // フォールバックを指定してEncodingを取得
+
    var utf8 = Encoding.GetEncoding("UTF-8",
+
                                    EncoderFallback.ReplacementFallback /*定義済みの置換フォールバック*/,
+
                                    decoderFallback);
+

          
+
    var bytes = new byte[] {0x93, 0xFA, 0x96, 0x7B, 0x8C, 0xEA}; // Shift JISでの文字列 "日本語"
+

          
+
    // バイト配列を文字列に変換
+
    // (ここでデコードできない文字は下駄記号'〓'に置き換えられる)
+
    Console.WriteLine(utf8.GetString(bytes));
+
  }
+
}
+
}}
+

          
+
#prompt(実行結果){{
+
〓〓〓{〓〓
+
}}
+

          
+
なお、EncoderReplacementFallback・DecoderReplacementFallbackには代替文字列として空文字(String.Empty)を指定することもできるため、エンコード・デコードできない文字を無視して消去することもできる。
+

          
+
**定義済みのフォールバック
+
GetEncodingメソッドは引数&var{encoderFallback};・&var{decoderFallback};にnullを指定するとArgumentNullExceptionをスローする。 nullの代わりに、以下のような定義済みのフォールバックを指定することができる。
+

          
+
-&msdn(netfx,member,System.Text.EncoderFallback.ReplacementFallback){EncoderFallback.ReplacementFallback};
+
-&msdn(netfx,member,System.Text.EncoderFallback.ExceptionFallback){EncoderFallback.ExceptionFallback};
+
-&msdn(netfx,member,System.Text.DecoderFallback.ReplacementFallback){DecoderFallback.ReplacementFallback};
+
-&msdn(netfx,member,System.Text.DecoderFallback.ExceptionFallback){DecoderFallback.ExceptionFallback};
+

          
+
**EncoderFallbackプロパティ・DecoderFallbackプロパティ
+
Encodingクラスの&msdn(netfx,member,System.Text.Encoding.EncoderFallback){EncoderFallbackプロパティ};・&msdn(netfx,member,System.Text.Encoding.DecoderFallback){DecoderFallbackプロパティ};を参照するとインスタンスに設定されているフォールバックを取得することができる。
+

          
+
このプロパティ自体は読み取り専用ではなく設定も可能となっているが、GetEncodingメソッドなどから取得したインスタンスでは読み取りとなっていて、プロパティを設定しようとすると実行時に例外InvalidOperationExceptionがスローされる。
+

          
+
#code(cs,EncoderFallbackプロパティを設定する例){{
+
using System;
+
using System.Text;
+

          
+
class Sample {
+
  static void Main()
+
  {
+
    var shiftjis = Encoding.GetEncoding("Shift_JIS");
+

          
+
    // EncoderFallbackをセットしようとすると実行時にInvalidOperationExceptionがスローされる
+
    shiftjis.EncoderFallback = new EncoderReplacementFallback("〓");
+

          
+
    // Encodingクラスのプロパティで取得できるEncodingでも同様に例外がスローされる
+
    //Encoding.UTF8.EncoderFallback = new EncoderReplacementFallback("〓");
+
  }
+
}
+
}}
+

          
+
#prompt(実行結果){{
+
System.InvalidOperationException: インスタンスは読み取り専用です。
+
   at System.Text.Encoding.set_EncoderFallback(EncoderFallback value)
+
   at Sample.Main(String[] args)
+
}}
+

          
+
インスタンスのクローンを作成すると、クローンは読み取り専用ではなくなりプロパティの設定ができるようになる。
+

          
+
#code(cs){{
+
using System;
+
using System.Text;
+

          
+
class Sample {
+
  static void Main()
+
  {
+
    var shiftjis = Encoding.GetEncoding("Shift_JIS");
+

          
+
    // インスタンスのクローンを作成する
+
    shiftjis = (Encoding)shiftjis.Clone();
+

          
+
    // 作成したクローンのフォールバックを設定する
+
    shiftjis.EncoderFallback = new EncoderReplacementFallback("〓");
+

          
+

          
+
    var utf8 = (Encoding)Encoding.UTF8.Clone();
+

          
+
    utf8.EncoderFallback = new EncoderReplacementFallback("〓");
+
    utf8.DecoderFallback = new DecoderReplacementFallback("〓");
+
  }
+
}
+
}}
+

          
+

          
+
*例外のスロー (EncoderExceptionFallback/DecoderExceptionFallback)
+
&msdn(netfx,type,System.Text.EncoderExceptionFallback){EncoderExceptionFallbackクラス};・&msdn(netfx,type,System.Text.DecoderExceptionFallback){DecoderExceptionFallbackクラス};を使うと、エンコード・デコードできない文字があった場合に例外をスローさせるようにすることができる。
+

          
+
このフォールバックを指定すると、エンコードできない文字があった場合には例外&msdn(netfx,type,System.Text.EncoderFallbackException){EncoderFallbackException};が、デコードできない場合には例外&msdn(netfx,type,System.Text.DecoderFallbackException){DecoderFallbackException};がそれぞれスローされるようになる。
+

          
+
#code(cs,エンコードできない文字があった場合に例外をスローさせる例){{
+
using System;
+
using System.Text;
+

          
+
class Sample {
+
  static void Main()
+
  {
+
    // エンコードできない文字があったら例外をスローするフォールバックを作成
+
    var encoderFallback = new EncoderExceptionFallback();
+

          
+
    // フォールバックを指定してEncodingを取得
+
    var shiftjis = Encoding.GetEncoding("Shift_JIS",
+
                                        encoderFallback,
+
                                        DecoderFallback.ReplacementFallback /*定義済みの置換フォールバック*/);
+

          
+
    var text = "⑳㉑"; // '㉑'はShift JISには存在しないのでエンコードの際に文字化けする
+

          
+
    // バイト配列に変換
+
    // (ここで文字'㉑'はエンコードされず例外がスローされる)
+
    var bytes = shiftjis.GetBytes(text);
+

          
+
    // 再び文字列に戻して表示する
+
    Console.WriteLine(shiftjis.GetString(bytes));
+
  }
+
}
+
}}
+

          
+
#prompt(実行結果){{
+
ハンドルされていない例外: System.Text.EncoderFallbackException: インデックス 1 にある Unicode 文字 \u3251 を指定されたコード ページに変換できません。
+
   場所 System.Text.EncoderExceptionFallbackBuffer.Fallback(Char charUnknown, Int32 index)
+
   場所 System.Text.EncoderFallbackBuffer.InternalFallback(Char ch, Char*& chars)
+
   場所 System.Text.DBCSCodePageEncoding.GetByteCount(Char* chars, Int32 count, EncoderNLS encoder)
+
   場所 System.Text.EncodingNLS.GetByteCount(String s)
+
   場所 System.Text.Encoding.GetBytes(String s)
+
   場所 Sample.Main()
+
}}
+

          
+
#code(cs,デコードできない文字があった場合に例外をスローさせる例){{
+
using System;
+
using System.Text;
+

          
+
class Sample {
+
  static void Main()
+
  {
+
    // デコードできない文字があったら例外をスローするフォールバックを作成
+
    var decoderFallback = new DecoderExceptionFallback();
+

          
+
    // フォールバックを指定してEncodingを取得
+
    var utf8 = Encoding.GetEncoding("UTF-8",
+
                                    EncoderFallback.ReplacementFallback /*定義済みの置換フォールバック*/,
+
                                    decoderFallback);
+

          
+
    var bytes = new byte[] {0x93, 0xFA, 0x96, 0x7B, 0x8C, 0xEA}; // Shift JISでの文字列 "日本語"
+

          
+
    // バイト配列を文字列に変換
+
    // (ここでデコードできない文字が見つかった時点で例外がスローされる)
+
    Console.WriteLine(utf8.GetString(bytes));
+
  }
+
}
+
}}
+

          
+
#prompt(実行結果){{
+
ハンドルされていない例外: System.Text.DecoderFallbackException: インデックス -1 にあるバイト [93] を指定されたコード ページから Unicode へ変換できません。
+
   場所 System.Text.DecoderExceptionFallbackBuffer.Throw(Byte[] bytesUnknown, Int32 index)
+
   場所 System.Text.DecoderExceptionFallbackBuffer.Fallback(Byte[] bytesUnknown, Int32 index)
+
   場所 System.Text.DecoderFallbackBuffer.InternalFallback(Byte[] bytes, Byte* pBytes)
+
   場所 System.Text.UTF8Encoding.FallbackInvalidByteSequence(Byte* pSrc, Int32 ch, DecoderFallbackBuffer fallback)
+
   場所 System.Text.UTF8Encoding.GetCharCount(Byte* bytes, Int32 count, DecoderNLS baseDecoder)
+
   場所 System.Text.UTF8Encoding.GetString(Byte[] bytes, Int32 index, Int32 count)
+
   場所 Sample.Main()
+
}}
+

          
+

          
+
*独自のフォールバックの定義
+
&msdn(netfx,type,System.Text.EncoderFallback){EncoderFallbackクラス};・&msdn(netfx,type,System.Text.DecoderFallback){DecoderFallbackクラス};を継承して適切に実装することで独自のフォールバックを定義することができる。
+

          
+
EncoderFallback・DecoderFallbackでは、エンコード・デコード時に代替文字列の生成を行う&msdn(netfx,type,System.Text.EncoderFallbackBuffer){EncoderFallbackBuffer};・&msdn(netfx,type,System.Text.DecoderFallbackBuffer){DecoderFallbackBuffer};を取得できるように実装する。 EncoderFallbackBuffer・DecoderFallbackBufferでは、エンコード・デコードできない文字があった場合に代替文字列の生成(もしくはEncoderFallbackExceptionなどのスロー)を行うように実装する。 EncoderFallbackBufferが生成する代替文字列は、対象のエンコーディングで使用できる文字のみで構成されている必要がある。
+

          
+
次の例では、EncoderFallbackクラス・EncoderFallbackBufferクラスを継承し、エンコードできない文字をUnicodeスカラ値表記(U+XXXX)に変換したものを代替文字列として生成するようにしている。
+

          
+
#code(cs,エンコード時のフォールバックを定義する例){{
+
using System;
+
using System.Text;
+

          
+
// エンコードできない文字をUnicodeスカラ値表記(U+XXXX)に置き換えるEncoderFallback
+
class EncoderScalarValueFallback : EncoderFallback {
+
  // 置き換えられる代替文字列の最大文字数
+
  public override int MaxCharCount {
+
    get { return 6; /* "U+XXXX".Length */ }
+
  }
+

          
+
  public override EncoderFallbackBuffer CreateFallbackBuffer()
+
  {
+
    // EncoderFallbackBufferのインスタンスを作成して返す
+
    return new EncoderScalarValueFallbackBuffer();
+
  }
+
}
+

          
+
// 代替文字列の生成を行うクラス
+
class EncoderScalarValueFallbackBuffer : EncoderFallbackBuffer {
+
  private char[] alternative;
+
  private int offset;
+

          
+
  // 代替文字列の残り文字数
+
  public override int Remaining {
+
    get { return alternative.Length - offset; }
+
  }
+

          
+
  // エンコードできない文字が見つかった場合に呼び出されるメソッド
+
  public override bool Fallback(char charUnknown, int index)
+
  {
+
    // エンコードできない文字をUnicodeスカラ値表記にした文字列を代替文字列とする
+
    alternative = string.Format("U+{0:X4}", (int)charUnknown).ToCharArray();
+
    offset = 0;
+

          
+
    return true;
+
  }
+

          
+
  public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index)
+
  {
+
    // サロゲートペアのフォールバックの実装は省略
+
    throw new NotImplementedException();
+
  }
+

          
+
  // 代替文字列の次の文字を取得するメソッド
+
  public override char GetNextChar()
+
  {
+
    if (alternative.Length <= offset)
+
      // MaxCharCountやRemainingプロパティの値は考慮されない(?)ようなので、
+
      // 代替文字列の末尾に到達したらchar.MinValueを返す必要がある
+
      return char.MinValue;
+
    else
+
      return alternative[offset++];
+
  }
+

          
+
  public override bool MovePrevious()
+
  {
+
    // 実装は省略
+
    throw new NotImplementedException();
+
  }
+

          
+
  public override void Reset()
+
  {
+
    // 実装は省略
+
    throw new NotImplementedException();
+
  }
+
}
+

          
+
class Sample {
+
  static void Main()
+
  {
+
    // 独自に実装したフォールバックを指定してEncodingを取得
+
    var shiftjis = Encoding.GetEncoding("Shift_JIS",
+
                                        new EncoderScalarValueFallback(),
+
                                        DecoderFallback.ReplacementFallback);
+

          
+
    var text = "⑳㉑";
+
    var bytes = shiftjis.GetBytes(text);
+

          
+
    // 再び文字列に戻して表示する
+
    Console.WriteLine(shiftjis.GetString(bytes));
+
  }
+
}
+
}}
+

          
+
#prompt(実行結果){{
+
⑳U+3251
+
}}
+

          
+

          
+

          
+

          
+