2012年12月7日金曜日


Windows の unicode と MAX_PATH

3年前だったか、仕事で付き合いのある人で変わった癖(?)のある人に出会った。
その人、自分でフォルダ名(ファイル名も)を付ける際に異常なほど長い名前を付けていた。
理由は、

「だってほら、一目で何のフォルダ(ファイル)か分かるでしょ?」

一目でっつっても、ファイル名が日本語の summary になっていた。
しかもご丁寧にどのファイルにも作成日が先頭に付いている。
もっと厄介なのは、どのファイル(フォルダも)も、何処に移動しても重複しないように意図されていたこと。
例えば年度フォルダなど、「2012」や「H24」といった簡素なものはまず無く、その親フォルダ名が頭に付いていたりする。
そら長くなるわ。
て言うか、階層の意味ねーじゃん、て言いたくなった。

だが人それぞれ。
毛の抜けたSEもいれば、毛の抜けたアンガス・ヤングもいる。

先日その人から連絡があった。
「毛の抜けたSEさんに影響されて、ちょっと前にプログラミングの勉強を始めた。。。」
ふむふむ。で?、まさか教えろとか言わねーよな。

「MAX_PATH って260バイトですよね?
 おかしいんですよ、400バイト超えてるんですけど平気なんです。」

別に私は貴方のファイルパスが260バイトを超えようが、3バイトに満たなくても平気ですけど。
っつーか、MAX_PATH ってまさか「C」やってるって言わねーよな!

「ご多分に漏れず C# です」

ブライアン・ジョンソンのように高音のしわがれ声で
「#?@! You!!!」
電話を切りたくなったが、ちょっと待てよ。

400バイトってどういう勘定してるんだ?

「だってほら、私が付けるファイル名は殆ど日本語の全角文字だから、
 単純に×2すると400バイトをゆうに超えるんですよ。」

ここにも居たか、shift-jis にしがみつく過去の遺物が。
かくいう私も、しがみつかざるを得ない状況なのだが。。。


今の Windows は内部では UNICODE (UTF-16) を使用している。

これは、全ての文字を2バイトで表現しようとするものだが、
世界中の文字が、たった 65,536 通りで表現できるはずもない。
よって、通常これだけあれば大丈夫だろうと思われる文字群を、
基本多言語面(BMP)という領域に収め、それ以外をサロゲートペアという
2つの2バイト域(計4バイト)で表現する。
つまり、通常2バイトだが、文字によっては4バイト使用する文字もあるということ。

もちろんこの人は、UNICODE の話をしているのではない。
shift-jis の話をしているのだ。だから「全角」という言葉が出てくる。

だが、ちょっと怖くなったので、今回改めてネットで検索してみたら、

やっぱり!

昔の shift-jis の頃に作成されたであろうページがわんさかヒットする。
それらのページの多くには、

「Windows の最大ファイルパス長は 260バイト

だが、新設なページも多数あった。

「Windows の最大ファイルパス長は 260バイト(UNICODE は260文字)」

そう、UNICODE では、206文字なのだ。
だから、

「あいうえお」と「aiueo」は同じ文字数の5文字だ。

shift-jis の世界では「あいうえお」は確かに10バイトだが、
UNICODE でも「あいうえお」はおそらく10バイトだろう。
ただし、「aiueo」は shift-jis では5バイトだが、
UNICODE では10バイトだ。

UNICODE は先にも言ったように、通常2バイトだから、変な漢字(失礼)や特殊な文字を使用していなければ、バイト数計算は楽だ。
だが、最大パス長を示す _MAX_PATH 定数が持つ「260」という数字は、

shift-jis の世界ならバイト数
UNICODE の世界なら文字数

という切り分けをしないといけない。
が、現在ではあまり意識しなくても良い環境になっている。

というのも、Java も C# も VB.NET も、て言うか Visual Studio も、
C++Builder も Delphi もすべて内部では UNICODE になっている。
Windows も UNICODE なんだから何も考えずに「文字数」でいけばいい。

ただし、ファイルを開いたり、保存したりするときは、文字コードに気をつけるのは言わずもがな、である。

あっ!あともう一つ。
USBメモリは良く使うが、NTFS でフォーマットできなかったりする。
確か、Windows日本語版は FAT には shift-jis に変換して送信するはず。
これはファイルの中身の話ではない。ファイル名の話だ。
だから上記の400バイト(実際は200文字以下だろう)を超えるパス長の状態そのままで、USBメモリにコピーするとエラーになる可能性がある。


しかし今や「全角」「半角」という言葉自体が消滅しつつあるのを、先に勉強した方が良い。
それを踏まえて「全角」「半角」という言葉を使って欲しい。

それと、「260」と言ったが、正確には「259」文字だ。
文字列の最後に null が付くので。

また、パス文字列の前に接頭辞として "\\?\" を付けると、約32,000文字までいけるらしいが、
そもそも最大パス長付近をウロウロしているようでは、まだまだフサフサだな。

2012年12月6日木曜日

C# タイプ別コントロール配列の取得(と、ジェネリックスを使った構造体の比較)

C# タイプ別コントロール配列の取得(と、ジェネリックス)


例えば、ある親コントロール内の子コントロール配列は、以下のようにすれば簡単に取得できる。


Control[] childControl = parentControl.Controls;

通常は以下のような使い方になるか。(フォームのコントロール一覧)

foreach (Control child in this.Controls)
{
  listBox1.Items.Add(child.Name);
}


この Control.Controls は Control.ControlCollection を返す。
普通 VisualStudio などで Windows フォームアプリケーションを開発していると、フォームのデザインはフォームデザイナで行うと思う。
その場合、あまり気づきにくいのだが、Form1.Designer.cs 内に、デザイナで行ったコントロールの追加や、位置およびプロパティの変更値などが書かれている。
例えば、フォームへのコントロールの追加は、この Controls プロパティの Add メソッドを使用している。

this.button1 = new System.Windows.Forms.Button();
this.Controls.Add(this.button1);

また、ある親コントロール内で、指定された名前のコントロールを探すには、上記の Name プロパティで探しても良いが、Controls.Find メソッドを使うと簡単だ。

Control[] controls = parentControl.Find("button1", true);

これは、例えば大量のボタンやラベル、テキストボックスを扱う時に、コントロールの名前を例えば、

button_01, button_02, button_03, ...

などのようにしておけば、プログラム内でシーケンシャルにアクセスできるだろう。

また、当然だが、コントロール名ではなくコントロールそのものが親コントロール内に存在するかどうかの確認も行える。

if (this.Controls.Contains(button1))
{
  MessageBox.Show("いやいや、当然でしょ");
}

しかしこれは、コントロールを動的に作成するような場合でなければ、あまり使うことはないだろう。

そして本題なのだが、コントロール名やコントロールそのもので検索するのではなく、タイプ別にコントロール一覧を取得したい場合がある。(えっ?俺だけ?)

namespace NantokaKantoka
{
  public class Honyalala
  {
    /// <summary>
    /// タイプ別にコントロールの一覧を取得
    /// </summary>
    /// <typeparam name="T">
    /// コントロールのタイプ(型)
    /// </typeparam>
    /// <param name="parent">
    /// 親コントロール
    /// </param>
    /// <param name="searchAllChildren">
    /// parent以下の全てのコントロールを検索するかどうか
    /// </param>
    /// <returns>
    /// 指定された型のコントロール一覧
    /// </returns>
    /// <exception cref="ArgumentNullException">
    /// parentがnullの場合
    /// </exception>
    /// <exception cref="ArgumentException">
    /// parentがControlではない場合
    /// </exception>
    /// <remarks>
    /// あまり使うことはないか...
    /// しかし例えば、大量のチェックボックスがあって、
    /// その全てのCheckedプロパティをfalseにするとか。
    /// いちいちコントロール名を書いていると疲れません?
    /// シーケンシャルな名前にして検索するのも一手だが、
    /// コントロール名には分かりやすい名前を付けたいもの。
    /// </remarks>
    /// <example>
    /// フォーム内の全てのCheckBoxのチェックをオフにする
    /// <code>
    /// foreach (CheckBox cb in
    ///   GetControlsByType&lt;CheckBox&gt;(this, true))
    /// {
    ///   cb.Checked = false;
    /// }
    /// </code>
    /// </example>
    public static T[] GetControlsByType<T>(
      Control parent, bool searchAllChildren)
    {
      if (parent == null)
        throw new ArgumentNullException();
      if (!(parent is Control))
        throw new ArgumentException();
      
      ArrayList list = new ArrayList();
      
      foreach (Control child in parent.Controls)
      {
        if (child is T) list.Add(child);
        if (searchAllChildren)
          list.AddRange(
            GetTypeControls<T>(
              parent, searchAllChildren));
      }
      
      return (list.Count == 0) ? null : (T[])list.ToArray(typeof(T));
    }
  }
}

使い方は example の通り、コントロールの型を指定する。

foreach (CheckBox cb in GetControlsByType<CheckBox>(this, true))
{
  cb.Checked = false;
}

C++ にもテンプレートがあって結構重宝していたが、C# のジェネリックスには敵わない。
C++ の場合はやっぱり後付け感満載で、インライン展開されるヘッダ内の巨大なマクロに変身するが、C# の場合まるで手足のように当然感満載なわけだ。
もちろん C# のジェネリックスには、C++ テンプレートに比べ不便なところもあるが、「型推論」を根底の考えに持つジェネリックスでは当然のことと言えよう。
ただ、上記のメソッドは普通のジェネリックメソッドと違い、いわゆる型引数がない(ControlCollection の中から目的の Type に変換可能なものだけを Type[] として返すため、引数で Type を指定するのではない。)
なので、

GetControlsByType<CheckBox>

の <CheckBox> を取ると、型推論できないと叱られる。
しかしこれは明示的に指定するのが正当なやり方のような気がする。
逆に、取り出す先を var にするのはもちろん可能だ。

foreach (var cb in GetControlsByType<CheckBox>(this, true))
{
  cb.Checked = false;
}

これは以前に紹介した var についてでも触れたが、ここでの var は CheckBox という型が決定している。

C# ジェネリックスの良いところは、コンパイル時に型推論が行われ、型を厳密にチェックしてくれる(リフレクションで型の判別ができない Java とは違う)のは言うに及ばず、VisualStudio では、コーディング時に知らせてくれるので非常に助かる。

ついでにジェネリックスを利用したメソッドを2つほど紹介する。
どちらも比較メソッドで、List (ジェネリックリスト)と配列の比較。

using System.Collections;

public static bool ListEquals<T>(List<T> list1, List<T> list2)
{
  // 型は<T>で統一しているので、
  // 型の比較は不要(コンパイルエラーとなる)
  
  // null のチェックとリストのアイテム数のチェック
  // null の引数を以下に通すとエラーになるため
  
  // どちらも null 参照の場合は同じものとする
  if (list1 == null && list2 == null) return true;
  // 片方のみ null 参照は違うものとする
  if (list1 == null || list2 == null) return false;
  // 両方とも空のリストは同じものとする
  if (list1.Count == 0 && list2.Count == 0) return true;
  
  IStructuralEquatable st1 = list1.ToArray();
  IStructuralEquatable st2 = list2.ToArray();
  
  return st1.Equals(st2, StructuralComprisons.StructuralEqualityComparer);
}

public static bool ArrayEquals<T>(T[] array1, T[] array2)
{
  // 型は<T>で統一しているので、
  // 型の比較は不要(コンパイルエラーとなる)
  
  // null のチェックとリストのアイテム数のチェック
  // null の引数を以下に通すとエラーになるため
  
  // どちらも null 参照の場合は同じものとする
  if (array1 == null && array2 == null) return true;
  // 片方のみ null 参照は違うものとする
  if (array1 == null || array2 == null) return false;
  // 両方とも空のリストは同じものとする
  if (array1.Length == 0 && array2.Length == 0) return true;
  
  IStructuralEquatable st1 = array1;
  IStructuralEquatable st2 = array2;
  
  return st1.Equals(st2, StructuralComprisons.StructuralEqualityComparer);
}

使い方はこんな感じ。

List<string> list1 = new List<string>();
List<string> list2 = new List<string>();

bool ret = ListEquals(list1, list2);

string[] str1 = new string[] { "abc", "def", "ghi" };
string[] str2 = new string[] { "abc", "def", "gigii" };

ret = ArrayEquals(str1, str2);

2つともやっていることは殆ど同じで、比較メソッドをサポートする IStructuralEquatable 型へ代入している。
List の方は、ToArray() メソッドを使用して配列に変換している。
「うん?」と思った方も多いだろう。
そう、List の比較でも以下のようにすれば ArrayEquals を使用できる。

bool ret = ArrayEquals(list1.ToArray(), list2.ToArray());

しかし、メソッド呼び出し時点で、list1 が null だったら、この呼び出し時点でエラーとなる。
なぜなら、null 参照の List で ToArray() メソッドは使えないからだ。

そうすると、以下のように事前にチェックが必要となる。

if (list1 != null && list2 != null)
  ret = ArrayEquals(list1.ToArray(), list2.ToArray());

実際のコードではこんなに単純ではないだろう。こういう煩わしいチェックを極力なくすのがオブジェクト指向の一端とも言える。
だからメソッド内でチェックできる環境を作る。
もちろん、これ以前に List 型変数の null 参照がコード内に存在しても良いかどうかは別問題となるが。

また、よく見かけるジェネリックメソッドの典型として、以下のように型特有のメンバを使用するための、型の型決めみたいなもの(条件とも言える)を指定する where なるものがある。

public static bool Comp<T>(T t1, T t2)
  where T : IComparable
{
  return (t1.CompareTo(t2) == 0) ? true : false;
}

これを制約条件というらしい。
こうしておけば、IComparable を実装している型しか与えることはできない。
逆に言うと where 以下を取り去ると、「CompareTo って何?」と言われる。
またこの制約条件にはインターフェイスなどの他に、「値型ですよ」という struct や、「参照型ですよ」という class などもある。
また、変わったところでは「引数無しのコンストラクタを有する」という new() なんかもあったりする。(複数指定する場合は最後に指定する必要がある)

何故上記の2つを紹介したかというと、元々構造体のメンバの等価比較が簡単にできないかとあれこれ考えていて、ジェネリックスを使うことにしたんだが、例えば以下のようなジェネリックメソッドを考える。

public static bool CompareStruct<T>(T st1, T st2)
  where T : struct
{
  return st1.Equals(st2);
}

上記で説明したように、制約条件には struct を指定する。
現在の C# の構造体は IEquatable を実装しているので、Equals メソッドが使える。
これで構造体の等価比較ができる。

ところが!

構造体のメンバが値型だけなら(String 型も OK)これで良いのだが、配列やクラスなどのメンバは対応できないんだなぁこれが!

論より証拠、以下のような構造体を作る。

struct TypeA
{
  public int a;
  public double b;
  public string c;
  public DateTime d;
}

これを作成したジェネリックメソッドでチェックする

TypeA typeA1 = new TypeA();
TypeA typeA2 = new TypeA();

typeA1.a = 1;
typeA1.b = 1.0;
typeA1.c = "one";
typeA1.d = new DateTime(1968, 10, 16);

typeA2.a = 1;
typeA2.b = 1.0;
typeA2.c = "one";
typeA2.d = new DateTime(1968, 10, 16);

bool ret = CompareStruct(typeA1, typeA2);

みごとに true が返る。
ところが、この TypeA にメンバを追加して以下のようにする。

struct TypeA
{
  public int a;
  public double b;
  public string c;
  public DateTime d;
  public int[] e;
  public double[] f;
  public string[] g;
  public List<string> l;
}

この状態で CompareStruct でチェックするとバッチリ false が返る!
これら値型以外のチェックは独自に行う必要がある。(めんどくさっ)

実はこれ以前に、私は C# で構造体を定義する時は、Equals と GetHashCode メソッドをオーバーライドしていた。
この時に使用していたのが上記2つの等価比較メソッドである。

struct TypeA
{
  public int a;
  public double b;
  public string c;
  public DateTime d;
  public int[] e;
  public double[] f;
  public string[] g;
  public List<string> l;
  
  public bool Equals(TypeA other)
  {
    return (this.a == other.a) &&
      (this.b == other.b) &&
      (this.c == other.c) &&
      (ArrayEquals(this.e, other.e)) &&
      (ArrayEquals(this.f, other.f)) &&
      (ArrayEquals(this.g, other.g)) &&
      (ListEquals(this.l, other.l));
  }
  public override bool Equals(object obj)
  {
    if (obj == null ||
      this.GetType() != obj.GetType())
      return false;
    
    return base.Equals((TypeA)obj);
  }
  public override int GetHashCode()
  {
    int retX = 0;
    
    retX ^= a;
    retX ^= b.GetHashCode();
    retX ^= c.GetHashCode();
    retX ^= d.GetHashCode();

    foreach (int val in this.e)
      retX ^= val;
    foreach (double val in this.f)
      retX ^= val.GetHashCode();
    foreach (string val in this.g)
      retX ^= val.GetHashCode();
    foreach (string val in this.l)
      retX ^= val.GetHashCode();
    
    return retX;
  }
}

実はこれ、IEquatable を実装しているのと同じで、定義する際に、

struct TypeA : IEquatable<TypeA>
{
  ...
}

と IEquatable<TypeA> と明示する。
そして、CompareStruct の制約条件に struct と IEquatable を付ける。

public static bool CompareStruct<T>(T st1, T st2)
  where T : struct, IEquatable<T>
{
  return st1.Equals(st2);
}

この状態で、ジェネリックメソッド CompareStruct でチェックすると、

bool ret = CompareStruct(st1, st2);

見事に true が返る!メデタシ、メデタシだが、元々構造体のみで等価比較ができるように Equals と GetHashCode をオーバーライドしたので、ジェネリックメソッドを使わなくても以下のように簡単に比較できる。

bool ret = typeA1.Equals(typeA2);

こちらも一行で済んでしまうので、今更ジェネリックもなぁ。
しかし、ジェネリックスを勉強してて思ったんだが、ますます持って Java を使うのが怖くなったなぁ。
C# は 2.0 からガラッと変わったが、Java も後方互換性ばかり意識せずに、Python のように「えいっ!」と変わったら?