.NET Core / .NET 5以降での文字セット変換

.NET Frameworkのときは、Shift-JISからUnicodeなどへの文字コード変換が既定で利用可能でした。しかし、.NET Core 3.1まで、および .NET 5以降では、既定のままでは利用できません。変換の前におまじないが必要です。

Shift-JISのEnconderがないと例外が発生する

.NET Framework 4.7 などで、Shift-JISで保存されたテキストファイルを一行ずつ読み込みたいときは、下記のコードなどを使います。

try
{
    var encoding = Encoding.GetEncoding("Shift_JIS");
    using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
    using (var reader = new StreamReader(stream, encoding , true))
    {
        var line = reader.ReadLine();
        ...

このコードは、.NET Framework 4.7 などでは正常に動作します。しかし、.NET Core 3.1までおよび、.NET 5以降では正常に動作しません。

var encoding = Encoding.GetEncoding("Shift_JIS");

上記の部分で ArgumentException の例外が発生します。

System.ArgumentException: 'Shift_JIS' is not a supported encoding name. For information on defining a custom encoding, see the documentation for the Encoding.RegisterProvider method.

日本語訳にすると以下の通り。

System.ArgumentException: 'Shift_JIS' はサポートされているエンコーディング名ではありません。カスタムエンコーディングの定義については、Encoding.RegisterProvider メソッドのドキュメントを参照してください。

例外は、Shift-JIS(シフトJIS)の文字コードが使えないといっています。

.NET Frameworkと.NET Core/.NET で既定でサポートされるエンコーディング

.NET Framework と .NET Core/.NET で、既定でサポートされるエンコーディングは何なのでしょうか?

System.Text.Encodingクラスには、既定で利用できるEncodingを取得するメソッド Encoding.GetEncodings()があります。これを使って調べています。

using System.Diagnostics;
using System.Text;
void PrintEncodings()
{
    var encodings = Encoding.GetEncodings();
    int index = 0;
    foreach (var item in encodings)
    {
        Debug.WriteLine($"{index++:D3}:CodePage = {item.CodePage}, Name = '{item.Name}', DisplayName = '{item.DisplayName}'");
    }
}

このメソッドを、ターゲットを.NET Core 3.1と.NET Framework 4.7.2に設定してビルドしたアプリで実行してみました。

.NET Core 3.1 でのEncodings

まずは、.NET Core 3.1 をターゲットとしてビルドしたアプリです。結果は以下の通りです。エンコーディングの数は、8個です。

000:CodePage = 1200, Name = 'utf-16', DisplayName = 'Unicode'
001:CodePage = 1201, Name = 'utf-16BE', DisplayName = 'Unicode (Big-Endian)'
002:CodePage = 12000, Name = 'utf-32', DisplayName = 'Unicode (UTF-32)'
003:CodePage = 12001, Name = 'utf-32BE', DisplayName = 'Unicode (UTF-32 Big-Endian)'
004:CodePage = 20127, Name = 'us-ascii', DisplayName = 'US-ASCII'
005:CodePage = 28591, Name = 'iso-8859-1', DisplayName = 'Western European (ISO)'
006:CodePage = 65000, Name = 'utf-7', DisplayName = 'Unicode (UTF-7)'
007:CodePage = 65001, Name = 'utf-8', DisplayName = 'Unicode (UTF-8)'

既定で含まれているのはUnicode系(UTF系)とUS-Ascii、Latin-1だけです。日本語系やアジア言語系の言語用のエンコーディングは全く含まれていません。

.NET Framework 4.7.2でのEncodings

つぎに、 .NET Framework 4.7.2 をターゲットとしてビルドしたアプリです。 結果は以下の通りです。エンコーディングの数は、140個です。

000:CodePage = 37, Name = 'IBM037', DisplayName = 'IBM EBCDIC (US - カナダ)'
001:CodePage = 437, Name = 'IBM437', DisplayName = 'OEM アメリカ合衆国'
002:CodePage = 500, Name = 'IBM500', DisplayName = 'IBM EBCDIC (インターナショナル)'
003:CodePage = 708, Name = 'ASMO-708', DisplayName = 'アラビア語 (ASMO 708)'
004:CodePage = 720, Name = 'DOS-720', DisplayName = 'アラビア語 (DOS)'
005:CodePage = 737, Name = 'ibm737', DisplayName = 'ギリシャ語 (DOS)'
006:CodePage = 775, Name = 'ibm775', DisplayName = 'バルト言語 (DOS)'
007:CodePage = 850, Name = 'ibm850', DisplayName = '西ヨーロッパ言語 (DOS)'
008:CodePage = 852, Name = 'ibm852', DisplayName = '中央ヨーロッパ言語 (DOS)'
009:CodePage = 855, Name = 'IBM855', DisplayName = 'OEM キリル'
010:CodePage = 857, Name = 'ibm857', DisplayName = 'トルコ語 (DOS)'
011:CodePage = 858, Name = 'IBM00858', DisplayName = 'OEM マルチリンガル ラテン I'
012:CodePage = 860, Name = 'IBM860', DisplayName = 'ポルトガル語  (DOS)'
013:CodePage = 861, Name = 'ibm861', DisplayName = 'アイスランド語 (DOS)'
014:CodePage = 862, Name = 'DOS-862', DisplayName = 'ヘブライ語 (DOS)'
015:CodePage = 863, Name = 'IBM863', DisplayName = 'フランス語 (カナダ) (DOS)'
016:CodePage = 864, Name = 'IBM864', DisplayName = 'アラビア語 (864)'
017:CodePage = 865, Name = 'IBM865', DisplayName = '北欧 (DOS)'
018:CodePage = 866, Name = 'cp866', DisplayName = 'キリル言語 (DOS)'
019:CodePage = 869, Name = 'ibm869', DisplayName = 'ギリシャ語, Modern (DOS)'
020:CodePage = 870, Name = 'IBM870', DisplayName = 'IBM EBCDIC (多国語ラテン 2)'
021:CodePage = 874, Name = 'windows-874', DisplayName = 'タイ語 (Windows)'
022:CodePage = 875, Name = 'cp875', DisplayName = 'IBM EBCDIC (ギリシャ語 Modern)'
023:CodePage = 932, Name = 'shift_jis', DisplayName = '日本語 (シフト JIS)'
024:CodePage = 936, Name = 'gb2312', DisplayName = '簡体字中国語 (GB2312)'
025:CodePage = 949, Name = 'ks_c_5601-1987', DisplayName = '韓国語'
026:CodePage = 950, Name = 'big5', DisplayName = '繁体字中国語 (Big5)'
027:CodePage = 1026, Name = 'IBM1026', DisplayName = 'IBM EBCDIC (トルコ語ラテン 5)'
028:CodePage = 1047, Name = 'IBM01047', DisplayName = 'IBM ラテン-1'
029:CodePage = 1140, Name = 'IBM01140', DisplayName = 'IBM EBCDIC (US - カナダ - ヨーロッパ)'
030:CodePage = 1141, Name = 'IBM01141', DisplayName = 'IBM EBCDIC (ドイツ - ヨーロッパ)'
031:CodePage = 1142, Name = 'IBM01142', DisplayName = 'IBM EBCDIC (デンマーク - ノルウェー - ヨーロッパ)'
032:CodePage = 1143, Name = 'IBM01143', DisplayName = 'IBM EBCDIC (フィンランド - スウェーデン - ヨーロッパ)'
033:CodePage = 1144, Name = 'IBM01144', DisplayName = 'IBM EBCDIC (イタリア - ヨーロッパ)'
034:CodePage = 1145, Name = 'IBM01145', DisplayName = 'IBM EBCDIC (スペイン - ヨーロッパ)'
035:CodePage = 1146, Name = 'IBM01146', DisplayName = 'IBM EBCDIC (UK - ヨーロッパ)'
036:CodePage = 1147, Name = 'IBM01147', DisplayName = 'IBM EBCDIC (フランス - ヨーロッパ)'
037:CodePage = 1148, Name = 'IBM01148', DisplayName = 'IBM EBCDIC (インターナショナル - ヨーロッパ)'
038:CodePage = 1149, Name = 'IBM01149', DisplayName = 'IBM EBCDIC (アイスランド語 - ヨーロッパ)'
039:CodePage = 1200, Name = 'utf-16', DisplayName = 'Unicode'
040:CodePage = 1201, Name = 'utf-16BE', DisplayName = 'Unicode (Big-Endian)'
041:CodePage = 1250, Name = 'windows-1250', DisplayName = '中央ヨーロッパ言語 (Windows)'
042:CodePage = 1251, Name = 'windows-1251', DisplayName = 'キリル言語 (Windows)'
043:CodePage = 1252, Name = 'Windows-1252', DisplayName = '西ヨーロッパ言語 (Windows)'
044:CodePage = 1253, Name = 'windows-1253', DisplayName = 'ギリシャ語 (Windows)'
045:CodePage = 1254, Name = 'windows-1254', DisplayName = 'トルコ語 (Windows)'
046:CodePage = 1255, Name = 'windows-1255', DisplayName = 'ヘブライ語 (Windows)'
047:CodePage = 1256, Name = 'windows-1256', DisplayName = 'アラビア語 (Windows)'
048:CodePage = 1257, Name = 'windows-1257', DisplayName = 'バルト言語 (Windows)'
049:CodePage = 1258, Name = 'windows-1258', DisplayName = 'ベトナム語 (Windows)'
050:CodePage = 1361, Name = 'Johab', DisplayName = '韓国語 (Johab)'
051:CodePage = 10000, Name = 'macintosh', DisplayName = '西ヨーロッパ言語 (Mac)'
052:CodePage = 10001, Name = 'x-mac-japanese', DisplayName = '日本語 (Mac)'
053:CodePage = 10002, Name = 'x-mac-chinesetrad', DisplayName = '繁体字中国語 (Mac)'
054:CodePage = 10003, Name = 'x-mac-korean', DisplayName = '韓国語 (Mac)'
055:CodePage = 10004, Name = 'x-mac-arabic', DisplayName = 'アラビア語 (Mac)'
056:CodePage = 10005, Name = 'x-mac-hebrew', DisplayName = 'ヘブライ語 (Mac)'
057:CodePage = 10006, Name = 'x-mac-greek', DisplayName = 'ギリシャ語 (Mac)'
058:CodePage = 10007, Name = 'x-mac-cyrillic', DisplayName = 'キリル言語 (Mac)'
059:CodePage = 10008, Name = 'x-mac-chinesesimp', DisplayName = '簡体字中国語 (Mac)'
060:CodePage = 10010, Name = 'x-mac-romanian', DisplayName = 'ルーマニア語 (Mac)'
061:CodePage = 10017, Name = 'x-mac-ukrainian', DisplayName = 'ウクライナ語 (Mac)'
062:CodePage = 10021, Name = 'x-mac-thai', DisplayName = 'タイ語 (Mac)'
063:CodePage = 10029, Name = 'x-mac-ce', DisplayName = '中央ヨーロッパ言語 (Mac)'
064:CodePage = 10079, Name = 'x-mac-icelandic', DisplayName = 'アイスランド語 (Mac)'
065:CodePage = 10081, Name = 'x-mac-turkish', DisplayName = 'トルコ語 (Mac)'
066:CodePage = 10082, Name = 'x-mac-croatian', DisplayName = 'クロアチア語 (Mac)'
067:CodePage = 12000, Name = 'utf-32', DisplayName = 'Unicode (UTF-32)'
068:CodePage = 12001, Name = 'utf-32BE', DisplayName = 'Unicode (UTF-32 ビッグ エンディアン)'
069:CodePage = 20000, Name = 'x-Chinese-CNS', DisplayName = '繁体字中国語 (CNS)'
070:CodePage = 20001, Name = 'x-cp20001', DisplayName = 'TCA 台湾'
071:CodePage = 20002, Name = 'x-Chinese-Eten', DisplayName = '繁体字中国語 (Eten)'
072:CodePage = 20003, Name = 'x-cp20003', DisplayName = 'IBM5550 台湾'
073:CodePage = 20004, Name = 'x-cp20004', DisplayName = 'TeleText 台湾'
074:CodePage = 20005, Name = 'x-cp20005', DisplayName = 'Wang 台湾'
075:CodePage = 20105, Name = 'x-IA5', DisplayName = '西ヨーロッパ言語 (IA5)'
076:CodePage = 20106, Name = 'x-IA5-German', DisplayName = 'ドイツ語 (IA5)'
077:CodePage = 20107, Name = 'x-IA5-Swedish', DisplayName = 'スウェーデン語 (IA5)'
078:CodePage = 20108, Name = 'x-IA5-Norwegian', DisplayName = 'ノルウェー語 (IA5)'
079:CodePage = 20127, Name = 'us-ascii', DisplayName = 'US-ASCII'
080:CodePage = 20261, Name = 'x-cp20261', DisplayName = 'T.61'
081:CodePage = 20269, Name = 'x-cp20269', DisplayName = 'ISO-6937'
082:CodePage = 20273, Name = 'IBM273', DisplayName = 'IBM EBCDIC (ドイツ)'
083:CodePage = 20277, Name = 'IBM277', DisplayName = 'IBM EBCDIC (デンマーク - ノルウェー)'
084:CodePage = 20278, Name = 'IBM278', DisplayName = 'IBM EBCDIC (フィンランド - スウェーデン)'
085:CodePage = 20280, Name = 'IBM280', DisplayName = 'IBM EBCDIC (イタリア)'
086:CodePage = 20284, Name = 'IBM284', DisplayName = 'IBM EBCDIC (スペイン)'
087:CodePage = 20285, Name = 'IBM285', DisplayName = 'IBM EBCDIC (UK)'
088:CodePage = 20290, Name = 'IBM290', DisplayName = 'IBM EBCDIC (日本語カタカナ)'
089:CodePage = 20297, Name = 'IBM297', DisplayName = 'IBM EBCDIC (フランス)'
090:CodePage = 20420, Name = 'IBM420', DisplayName = 'IBM EBCDIC (アラビア語)'
091:CodePage = 20423, Name = 'IBM423', DisplayName = 'IBM EBCDIC (ギリシャ語)'
092:CodePage = 20424, Name = 'IBM424', DisplayName = 'IBM EBCDIC (ヘブライ語)'
093:CodePage = 20833, Name = 'x-EBCDIC-KoreanExtended', DisplayName = 'IBM EBCDIC (韓国語 Extended)'
094:CodePage = 20838, Name = 'IBM-Thai', DisplayName = 'IBM EBCDIC (タイ語)'
095:CodePage = 20866, Name = 'koi8-r', DisplayName = 'キリル言語 (KOI8-R)'
096:CodePage = 20871, Name = 'IBM871', DisplayName = 'IBM EBCDIC (アイスランド語)'
097:CodePage = 20880, Name = 'IBM880', DisplayName = 'IBM EBCDIC (キリル言語 - ロシア語)'
098:CodePage = 20905, Name = 'IBM905', DisplayName = 'IBM EBCDIC (トルコ語)'
099:CodePage = 20924, Name = 'IBM00924', DisplayName = 'IBM ラテン-1'
100:CodePage = 20932, Name = 'EUC-JP', DisplayName = '日本語 (JIS 0208-1990 および 0212-1990)'
101:CodePage = 20936, Name = 'x-cp20936', DisplayName = '簡体字中国語 (GB2312-80)'
102:CodePage = 20949, Name = 'x-cp20949', DisplayName = '韓国語 Wansung'
103:CodePage = 21025, Name = 'cp1025', DisplayName = 'IBM EBCDIC (キリル言語 セルビア - ブルガリア)'
104:CodePage = 21866, Name = 'koi8-u', DisplayName = 'キリル言語 (KOI8-U)'
105:CodePage = 28591, Name = 'iso-8859-1', DisplayName = '西ヨーロッパ言語 (ISO)'
106:CodePage = 28592, Name = 'iso-8859-2', DisplayName = '中央ヨーロッパ言語 (ISO)'
107:CodePage = 28593, Name = 'iso-8859-3', DisplayName = 'ラテン 3 (ISO)'
108:CodePage = 28594, Name = 'iso-8859-4', DisplayName = 'バルト言語 (ISO)'
109:CodePage = 28595, Name = 'iso-8859-5', DisplayName = 'キリル言語 (ISO)'
110:CodePage = 28596, Name = 'iso-8859-6', DisplayName = 'アラビア語 (ISO)'
111:CodePage = 28597, Name = 'iso-8859-7', DisplayName = 'ギリシャ語 (ISO)'
112:CodePage = 28598, Name = 'iso-8859-8', DisplayName = 'ヘブライ語 (ISO-Visual)'
113:CodePage = 28599, Name = 'iso-8859-9', DisplayName = 'トルコ語 (ISO)'
114:CodePage = 28603, Name = 'iso-8859-13', DisplayName = 'エストニア語 (ISO)'
115:CodePage = 28605, Name = 'iso-8859-15', DisplayName = 'ラテン 9 (ISO)'
116:CodePage = 29001, Name = 'x-Europa', DisplayName = 'ヨーロッパ'
117:CodePage = 38598, Name = 'iso-8859-8-i', DisplayName = 'ヘブライ語 (ISO-Logical)'
118:CodePage = 50220, Name = 'iso-2022-jp', DisplayName = '日本語 (JIS)'
119:CodePage = 50221, Name = 'csISO2022JP', DisplayName = '日本語 (JIS 1 バイト カタカナ可)'
120:CodePage = 50222, Name = 'iso-2022-jp', DisplayName = '日本語 (JIS 1 バイト カタカナ可 - SO/SI)'
121:CodePage = 50225, Name = 'iso-2022-kr', DisplayName = '韓国語 (ISO)'
122:CodePage = 50227, Name = 'x-cp50227', DisplayName = '簡体字中国語 (ISO-2022)'
123:CodePage = 51932, Name = 'euc-jp', DisplayName = '日本語 (EUC)'
124:CodePage = 51936, Name = 'EUC-CN', DisplayName = '簡体字中国語 (EUC)'
125:CodePage = 51949, Name = 'euc-kr', DisplayName = '韓国語 (EUC)'
126:CodePage = 52936, Name = 'hz-gb-2312', DisplayName = '簡体字中国語 (HZ)'
127:CodePage = 54936, Name = 'GB18030', DisplayName = '簡体字中国語 (GB18030)'
128:CodePage = 57002, Name = 'x-iscii-de', DisplayName = 'ISCII デバナガリ文字'
129:CodePage = 57003, Name = 'x-iscii-be', DisplayName = 'ISCII ベンガル語'
130:CodePage = 57004, Name = 'x-iscii-ta', DisplayName = 'ISCII タミール語'
131:CodePage = 57005, Name = 'x-iscii-te', DisplayName = 'ISCII テルグ語'
132:CodePage = 57006, Name = 'x-iscii-as', DisplayName = 'ISCII アッサム語'
133:CodePage = 57007, Name = 'x-iscii-or', DisplayName = 'ISCII オリヤー語'
134:CodePage = 57008, Name = 'x-iscii-ka', DisplayName = 'ISCII カナラ語'
135:CodePage = 57009, Name = 'x-iscii-ma', DisplayName = 'ISCII マラヤラム語'
136:CodePage = 57010, Name = 'x-iscii-gu', DisplayName = 'ISCII グジャラート語'
137:CodePage = 57011, Name = 'x-iscii-pa', DisplayName = 'ISCII パンジャブ語'
138:CodePage = 65000, Name = 'utf-7', DisplayName = 'Unicode (UTF-7)'
139:CodePage = 65001, Name = 'utf-8', DisplayName = 'Unicode (UTF-8)'

既定で含まれているのは.NET Core 3.1とは異なり、日本語系やアジア言語系など多岐にわたる言語用のエンコーディングが含まれています。

.NET Core/.NET で既定のエンコーディング以外を扱うには

.NET Core 3.1まで/.NET 5以降では、既定のエンコーディングは、8個しかないことがわかりました。

では、どのようにすれば、Shift-JISなどのエンコーディングを扱うことができるようになるのでしょうか?

対処方法は、例外メッセージに内にあります。

System.ArgumentException: 'Shift_JIS' はサポートされているエンコーディング名ではありません。カスタムエンコーディングの定義については、Encoding.RegisterProvider メソッドのドキュメントを参照してください。

このメッセージ従い、Encoding.RegisterProvider()メソッドを確認します。

RegisterProviderメソッドを使用すると、EncodingProviderから派生したクラスを登録し、他の方法ではサポートされていないプラットフォームで文字エンコーディングを利用できるようにすることができます。エンコーディングプロバイダが登録されると、それがサポートするエンコーディングは、任意の Encoding.GetEncoding オーバーロードを呼び出して取得することができます。複数のエンコーディングプロバイダがある場合、Encoding.GetEncodingメソッドは、最も最近登録されたものから始めて、各プロバイダから指定されたエンコーディングを取得しようとします。

Encoding.RegisterProvider()メソッドでエンコーディングの拡張ができるようです。

- 登録されたプロバイダがCodePagesEncodingProviderである場合、このメソッドは、Windowsオペレーティングシステム上で実行されているときにシステムアクティブコードページに一致するエンコーディングを返します。

Windows OSがサポートしているエンコーディングを追加する場合はCodePagesEncodingProvider.InstanceをProviderとして登録すればよく、Providerを自分で実装する必要はありません。

.NET Framework 4.6以降、.NET FrameworkにはCodePagesEncodingProviderという1つのエンコーディングプロバイダがあり、完全な.NET Frameworkには存在するがユニバーサルWindowsプラットフォームでは使用できないエンコーディングを使用できるようにします。デフォルトでは、ユニバーサルWindows 
プラットフォームは、Unicodeエンコーディング、ASCII、およびコードページ28591のみをサポートしています。

既定で8個のエンコーディングしかないのは、.NET Core / .NETだけでなく、ユニバーサルWindowsプラットフォーム(UWP)でも同様のようです。

なお、CodePagesEncodingProviderは、.NET Frameworkでは利用できません。.NET Framewrokでは、このProviderを登録しなくても、同等のエンコーディングは既定で利用できるためです。

また、CodePagesEncodingProviderは、System.Text.Encoding.CodePages.dllアセンブリに定義されていますが、このアセンブリは、.NET Core/.NET フレームワークの既定のアセンブリとなっているため、アセンブリ参照の追加は必要ありません。

.NET Core/.NET/UWP で.NET Frameworkと同等のエンコーディングを扱うには

.NET Core 3.1まで / .NET 6以降 / UWPアプリ で.NET Frameworkと同等の数のエンコーディングを扱うためには、Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)を呼びだせばよいことがわかりました。

そのため、投稿のはじめに書いたおまじないとは、

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

となります。これは、プロセス内で一度のみ呼び出せばよいです。

また、ドキュメントには以下のように記載されています。

RegisterProvider メソッドの複数の呼び出しで同じエンコーディングプロバイダが使用されている場合、最初のメソッド呼び出しだけがプロバイダを登録します。それ以降の呼び出しは無視されます。

同じプロバイダーインスタンスを使って複数回登録メソッドを呼び出しても動作に影響ありません。そのため、アプリの起動時に一回呼び出すか、もしくは、エンコーディングが必要な時にその都度呼び出すことが可能です。

以上のことから、この投稿ではじめに記載したサンプルコードを、.NET Framework / .NET Core 3.1まで / .NET Core 5以降で共通に使えるコードに書き換えると以下のようになります。

try
{
#if !NETFRAMEWORK
    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
#endif
    var encoding = Encoding.GetEncoding("Shift_JIS");
    using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
    using (var reader = new StreamReader(stream, encoding , true))
    {
        var line = reader.ReadLine();
        ...

以上、今回の投稿は、.NET Core / .NETで、テキストエンコーディングとして、日本語を含むOSがサポートしているコードページを利用するための方法についてでした。

コメントを残す