OITA: Oika's Information Technological Activities

@oika 情報技術的活動日誌。

Enumeratorを使って自前オブジェクトのメンバを列挙する

javaでのtoString()って、C#のToString()よりもデバッグ用的な意味合いが
強いのかなという感じがする。

javadocを見ると、以下のように書いてある。

通常、toString メソッドはこのオブジェクトを「テキストで表現する」文字列を返します。この結果は、人間が読める簡潔で有益な情報であるべきです。

そしてjavaではすべてのオブジェクトでtoString()を実装することが
推奨されているらしーんだけども、同じようなことはきっとC#であっても
やっておいたほうがいいんだと思います。

たとえば以下のようなPersonクラスがあったとしたら、

public class Person {
    public int Age { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
}

ToString()は、たとえば以下のような実装をすれば良いでしょう。

public override string ToString() {
    return string.Format("Person[Age={0},Name={1},Address={2}]", 
                         Age, Name, Address);
}

んでですね、このPersonみたいなシンプルなデータクラスの場合、
どうせならEnumeratorでプロパティを列挙するようにしとけば
いろいろ使い勝手が良いんじゃないかっていう話です。

public class Person : IEnumerable<KeyValuePair<string, object>> {
    public int Age { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }

    public IEnumerator<KeyValuePair<string, object>> GetEnumerator() {
        yield return new KeyValuePair<string, object>("Age", Age);
        yield return new KeyValuePair<string, object>("Name", Name);
        yield return new KeyValuePair<string, object>("Address", Address);
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }

    public override string ToString() {
        string vals = string.Join(",", this.Select(p => p.Key + "=" + p.Value));
        return "Person[" + vals + "]";
    }
}

こんなふうにIEnumerableを実装しておくと
オブジェクトの中身をCSVにしたり、流行りのLTSVにしたり、柔軟に使いやすい。

static void Main(string[] args) {

    var person = new Person { Age=20, Name="やまだ", Address="札幌" };

    var csv  = string.Join(",", person.Select(p => "\"" + p.Value + "\""));
    var ltsv = string.Join("\t", person.Select(p => p.Key + ":" + p.Value));
}

ただ、これだと結局プロパティを増やしたり名前を変えたりするたびに
GetEnumerator()の実装もメンテする必要があるのがちょっとめんどくさい。
欲をいえば、メンバ名をベタ書きにしなきゃいけないのをなんとかしたいですね。

てなわけで、そこまでやるならReflectionです。

public IEnumerator<KeyValuePair<string, object>> GetEnumerator() {

    foreach (var pr in this.GetType().GetProperties()) {
        yield return new KeyValuePair<string, object>(pr.Name, pr.GetValue(this));
    }
}

たったこれだけ!
GetEnumerator()を↑に置き換えるだけで、同じようにプロパティ名と値を列挙できちゃうのだ。

ちなみにパラメータなしのType.GetProperties()は、パブリックなプロパティのみを列挙する。
プライベートなプロパティを対象に含めたい場合はBindingFlagsを使ってごにょごにょやりましょう。
プロパティでなくてフィールドを対象にしたい場合はType.GetFields()を使うか、
Type.GetMembers()を使ってフラグでごにょごにょやりましょう。