はじめに
C# ソフト開発時に、決まり事として実施していた内容を記載します。
参考情報
下記情報を参考にさせて頂きました。
テスト環境
ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。
- Windows Forms - .NET Framework 4.8
- Windows Forms - .NET 8
- WPF - .NET Framework 4.8
- WPF - .NET 8
ファイル一覧
さまざまな局面で、ファイル一覧を生成して、利用することがありました。
ファイル一覧は、用途によって、以下の3パターンのいずれかの実装としていました。
- Windows 日本語カルチャ照合規則でソート
- 自然順(エクスプローラ互換)でソート
- ソート未実施
文字列データに対する、一致判定、および、ソート並び順を既定するルールを照合規則(照合順序)と呼びます。
JIS 規格としては、「JIS X 4061 日本語文字列照合順番」が存在しています。
照合規則についての情報は、本記事、後半に記載します。
まずは、前述3パターンのファイル一覧について、サンプルコードを記載します。
Windows 日本語カルチャ照合規則でソート
Windows 日本語カルチャ(ja-jp)既定の照合規則を利用したソートです。
LINQ で、フルパスではなく、ファイル名のみとしてからソートしています。
// フォルダ下のファイル一覧処理
private int FilesUnderFolder(string target)
{
int fileCount = 0; // フォルダ下のファイル数
var dirInfo = new DirectoryInfo(target);
// サブフォルダ下も処理する場合はコメントを外す
//// フォルダ名称のみを Windows 日本語カルチャ照合規則でソート
// var folders = dirInfo.GetDirectories()
// .Select(fileinfo => fileinfo.Name).OrderBy(x => x);
// if (folders != null)
// {
// foreach (var folder in folders)
// {
// string fullPath = target + '\\' + folder;
// fileCount += FilesUnderFolder(fullPath);
// }
// }
// ファイル名称のみを Windows 日本語カルチャ照合規則でソート
var files = dirInfo.GetFiles()
.Select(fileinfo => fileinfo.Name).OrderBy(x => x);
if (files != null)
{
foreach (var file in files)
{
string fullPath = target + '\\' + file;
// TODO
fileCount++;
}
}
return fileCount;
}
自然順(エクスプローラ互換)でソート
自然順を手軽に実現するのは、WIN32API - StrCmpLogicalW を利用することです。
Array、List、LINQ で利用可能な IComparer<string>
を実装したクラスを作成します。
// 自然順 比較クラス
public class NaturalComparer : IComparer<string>
{
// WIN32API
private static class NativeMethods
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
public static extern int StrCmpLogicalW(string psz1, string psz2);
}
private NaturalComparer()
{
}
public int Compare(string x, string y)
{
if (ReferenceEquals(x, y)) return 0;
if (x is null) return -1;
if (y is null) return 1;
// 文字列比較:自然順 - ここを変更することで任意の照合規則とすることが可能
return NativeMethods.StrCmpLogicalW(x, y);
}
public static NaturalComparer Natural
{
get
{
if (_Natural == null)
_Natural = new NaturalComparer();
return _Natural;
}
}
static NaturalComparer _Natural = null;
}
.NET 8 の場合、Null許容参照型の明示が必要です。
public int Compare(string? x, string? y)
static NaturalComparer? _Natural = null;
LINQ - OrderBy に NaturalComparer を指定すると、「自然順(エクスプローラ互換)でソート」となります。
// ファイル名称のみを自然順(エクスプローラ互換)でソート
var files = dirInfo.GetFiles()
.Select(fileinfo => fileinfo.Name)
.OrderBy(x => x, NaturalComparer.Natural);
if (files != null)
{
foreach (var file in files)
{
string fullPath = target + '\\' + file;
// TODO
fileCount++;
}
}
NaturalComparer クラスは、Array、List でも利用可能です。
var arrayItems = new string[] { "hoge5.txt", "hoge10.txt", "hoge1.txt" };
var lstItems = new List<string>(arrayItems);
// Array
Array.Sort(arrayItems, NaturalComparer.Natural);
// List
lstItems.Sort(NaturalComparer.Natural);
ソート未実施
「ソート未実施」の場合は、LINQ でフルパスを取得します。
// ファイル一覧取得 - フルパス
var files = dirInfo.GetFiles()
.Select(fileinfo => fileinfo.FullName);
if (files != null)
{
foreach (var file in files)
{
// TODO - file はフルパス
fileCount++;
}
}
照合規則
照合規則の構成要素として、以下のようなルールがあげられます。
- 同一視(区別をしない)
- 同一視する文字は、同一視したグループ内でウェイト(優先順位)を付与する
- 同一視状態で完全一致した場合は、後方文字からウェイトに基づいて順序付けする
- 同一視対象としては、下記などがあります
- 英大文字 / 英小文字
- 全角 / 半角
- ひらがな / カタカナ
- 小書き文字 / 清音文字 / 濁点文字 / 半濁音文字
- アラビア数字(算用数字)/ 漢数字 / ローマ数字
- 比較方法
- 文字の数値部分
- 文字として比較 / 数値として比較
- 非数値の文字
- 文字クラスで分類して、分類同士の序列を設定(下記に一例を記載)
- 記号 < アラビア数字 < アルファベット < ひらがな・カタカナ < 漢字
- 分類内の比較
- 文字コード順(Unicode / JIS X 0213 面区点)/ 辞書順(よみがな順)など
- 文字クラスで分類して、分類同士の序列を設定(下記に一例を記載)
- 文字の数値部分
文字コードにおいて、漢字は「部首/画数順」「音読み順」などの順列で配置されているので、文字コード順では、この配置順にソートされます。
照合規則について詳しく知りたい方は、下記情報などを参照してください。
以降では、Windows に標準搭載されている機能をベースに、いくつかのパターンを確認してみます。
Windows 日本語カルチャ照合規則
「JIS X 4061 日本語文字列照合順番」をベースとした実装であると認識していますが、正直、あまり詳しいことまでは理解していません。
そのため、どのような動作となるか、具体的な例を用いて確認することとします。
まずは、同一視対象について確認してみます。
// 英大文字/英小文字 + 全角/半角
var arrayItems1 = new string[] {"a", "b", "A", "B", "A", "B", "a", "b"};
Array.Sort(arrayItems1);
// → "a", "a", "A", "A", "b", "b", "B", "B"
// ひらがな/カタカナ + 全角/半角 + 小書き文字/清音文字/濁点文字/半濁音文字
var arrayItems2 = new string[] { "あ", "い", "ア", "イ", "ア", "イ",
"は", "ぱ", "ば", "つ", "っ", "づ" };
Array.Sort(arrayItems2);
// → "ア", "ア", "あ", "イ", "イ", "い", "っ", "つ", "づ", "は", "ば", "ぱ"
var arrayItems3 = new string[] { "1", "2", "1", "2", "一", "二", "Ⅰ", "Ⅱ" };
Array.Sort(arrayItems3);
// → "1", "1", "2", "2", "Ⅰ", "Ⅱ", "一", "二"
アルファベット、ひらがな/カタカナは、同一視対象とされています。
数値を文字として比較した場合、漢数字とローマ数字は、アラビア数字と同一視対象になりません。
漢字については、母数が少ないですが、下記を用いて確認してみます。
文字 | Unicode | JIS X0213 | 水準 | 画数 | 部首 | 音読み |
---|---|---|---|---|---|---|
一 | U+4E00 | 1-16-76 | 第1水準 | 1 | 一部 | イチ・イツ |
三 | U+4E09 | 1-27-16 | 第1水準 | 3 | 一部 | サン |
二 | U+4E8C | 1-38-83 | 第1水準 | 2 | 二部 | ニ |
亜 | U+4E9C | 1-16-1 | 第1水準 | 7 | 二部 | ア |
亞 | U+4E9E | 1-48-19 | 第2水準 | 8 | 二部 | ア |
唖 | U+5516 | 1-16-2 | 第1水準 | 10 | 口部 | ア・アク |
啞 | U+555E | 1-15-8 | 第3水準 | 11 | 口部 | ア・アク |
啡 | U+5561 | 2-4-8 | 第4水準 | 11 | 口部 | ハイ・ヒ |
排 | U+6392 | 1-39-51 | 第1水準 | 11 | 手部 | ハイ |
柛 | U+67DB | 2-14-50 | 第4水準 | 9 | 木部 | シン |
神 | U+795E | 1-31-32 | 第1水準 | 9 | 示部 | シン・ジン |
神 | U+FA19 | 1-89-28 | 第3水準 | 10 | 示部 | シン・ジン |
var arrayItems4 = new string[] { "一", "三", "二", "亜", "亞", "唖",
"啞", "啡", "排", "柛", "神", "神" };
Array.Sort(arrayItems4);
// → "亜", "唖", "一", "三", "神", "二", "排", "亞", "神", "啞", "啡", "柛"
JIS X 0213 面区点順をベースとしているようですが、「啞」がイレギュラーですね、、、
他にもイレギュラーがありそうなので、別記事で詳細確認することにします。
自然順(エクスプローラ互換)
文字データの数値部分を数値として比較する照合規則です。
ファイル一覧サンプルコードとして掲載した NaturalComparer を用いて確認してみます。
var arrayItems = new string[] {"hoge5.txt", "hoge10.txt", "hoge1.txt"};
// 文字コード順(同一視対象存在)
Array.Sort(arrayItems);
// → "hoge1.txt", "hoge10.txt", "hoge5.txt"
// 自然順(エクスプローラ互換)
Array.Sort(arrayItems, NaturalComparer.Natural);
// → "hoge1.txt", "hoge5.txt", "hoge10.txt"
日本語の語彙として序列を表す「上 / 中 / 下」「前 / 後」、および、漢数字などについても、語彙順にソートされることが「自然」と感じますが、Windows の自然順ではサポート対象外です。
これらの文字を「自然」な並び順とするには、語彙解釈が必要となるためだと思います。
- 序列を表すために利用時(上期、中期、下期)は、序列に従う
- 上記以外は、非数値の文字データに基づく比較(文字コード順、辞書順、特殊)
Windows 日本語カルチャ照合規則との差異について確認してみます。
まずは、同一視対象です。
// 英大文字/英小文字 + 全角/半角
var arrayItems1 = new string[] {"a", "b", "A", "B", "A", "B", "a", "b"};
Array.Sort(arrayItems1, NaturalComparer.Natural);
// → "a", "A", "A", "a", "b", "B", "B", "b"
// ひらがな/カタカナ + 全角/半角 + 小書き文字/清音文字/濁点文字/半濁音文字
var arrayItems2 = new string[] { "あ", "い", "ア", "イ", "ア", "イ",
"は", "ぱ", "ば", "つ", "っ", "づ" };
Array.Sort(arrayItems2, NaturalComparer.Natural);
// → "ア", "ア", "あ", "イ", "イ", "い", "っ", "つ", "づ", "は", "ば", "ぱ"
var arrayItems3 = new string[] { "1", "2", "1", "2", "一", "二", "Ⅰ", "Ⅱ" };
Array.Sort(arrayItem3, NaturalComparer.Natural);
// → "1", "1", "2", "2", "Ⅰ", "Ⅱ", "一", "二"
同一視対象は同等ですが、アルファベットのウェイトに相違がありました。
この差異には気づいていませんでした。注意が必要ですね。
照合規則 | アルファベット ウェイト |
---|---|
Windows 日本語カルチャ照合規則 | 半角小文字 < 全角小文字 < 半角大文字 < 全角大文字 |
自然順 - StrCmpLogicalW | 半角小文字 < 半角大文字 < 全角大文字 < 全角小文字 |
次に、漢字につい確認します。
var arrayItems4 = new string[] { "一", "三", "二", "亜", "亞", "唖",
"啞", "啡", "排", "柛", "神", "神" };
Array.Sort(arrayItems4, NaturalComparer.Natural);
// → "亜", "唖", "一", "三", "神", "二", "排", "亞", "神", "啞", "啡", "柛"
これは同一結果です。
文字コード順
StringComparer で用意されている Ordinal を利用すると、文字コード順(Unicode順)でソートできます。
var arrayItems4 = new string[] { "一", "三", "二", "亜", "亞", "唖",
"啞", "啡", "排", "柛", "神", "神" };
Array.Sort(arrayItems4); // Windows 日本語カルチャ照合規則順にする
var lstItems4 = new List<string>(arrayItems4);
// Array
Array.Sort(arrayItems4, StringComparer.Ordinal);
// → "一", "三", "二", "亜", "亞", "唖", "啞", "啡", "排", "柛", "神", "神",
// List
stItems4.Sort(StringComparer.Ordinal);
// LINQ
var sortd = arrayItems4.OrderBy(x => x, StringComparer.Ordinal);
結果を確認すると文字コード順となりました。