例えば、ある親コントロール内の子コントロール配列は、以下のようにすれば簡単に取得できる。
{
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<CheckBox>(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 のように「えいっ!」と変わったら?
0 件のコメント:
コメントを投稿