🗃️

C#定石 - ファイル一覧 - 照合規則

2025/02/14に公開

はじめに

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 日本語カルチャ照合規則でソート
  • 自然順(エクスプローラ互換)でソート
  • ソート未実施

照合規則についての情報は、本記事、後半に記載します。
まずは、前述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> を実装したクラスを作成します。

https://learn.microsoft.com/ja-jp/windows/win32/api/shlwapi/nf-shlwapi-strcmplogicalw

// 自然順 比較クラス
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;
}

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

ソート未実施

「ソート未実施」の場合は、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 日本語カルチャ照合規則との差異について確認してみます。
まずは、同一視対象です。

// 英大文字/英小文字 + 全角/半角
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順)でソートできます。

https://learn.microsoft.com/ja-jp/dotnet/api/system.stringcomparer

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

結果を確認すると文字コード順となりました。

出典

本記事は、2025/02/14 Qiita 投稿記事の転載です。

C#定石 - ファイル一覧 - 照合規則

Discussion