1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

はじめに

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

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

1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?