2012年11月28日水曜日

[C#]式木入門

C#の式木について勉強しながら、勉強したことの理解を書いていきます。

式木ってなに?

式木(Expression tree)とは、式(数式)を木構造で表したものの事です。
以下の様な式を例にします。

int result = 5 + 7 * 3; // result == 26
 

この式をみるとき、加算演算子(+)をAdd関数、乗算演算子(*)をMultiply関数とするとこんな感じですね。

int result = Add(5, Multiply(7, 3));
 

ちょっと改行とかインデントを入れてみます。

int result = 
    Add(
        5, 
        Multiply(
            7, 
            3
            )
        );
 

なんとなく木構造にみえてきませんか?図にしてみます。

はい、どーみても木です。このように式は木で表現することができます。これが式木です。

なににつかうの?

式が木で表せることがわかりました。しかしこれは何の役に立つのでしょうか。

式を組み立てることができる

木構造を作ってあげる事で、好きな式を組み立てることができるようになります。

式を分析することができる

式を木構造にしてやることで、式を分析することができます。

つまり...どういうことだってばよ?

プログラム(式)を作ることができます。またプログラム(式)を分析することができます。

C#での式木

C#(.NET Framework)では式木を扱うための仕組みが用意されています。
System.Linq.Expressions 名前空間に式木を扱うためのオブジェクトが揃っています。

C#の中で式木をオブジェクトとして扱えるということは、C#の中でC#を書くというようなことができるということです。
つまり、プログラムの中でプログラムを作ったり、分析したりできます。いわいるメタプログラミングが可能になります。

さっきの式を組み立ててみる

さっきの簡単な式をC#で組み立てる例を示します。

// 式: 5 + 7 * 3
Expression body = 
    Expression.Add(
        Expression.Constant(5), 
        Expression.Multiply(
            Expression.Constant(7), 
            Expression.Constant(3)
            )
        );
 

このように、式は Expression型 として扱います。Expression型にはいろいろな式を表すための派生型が用意されています。
Expression型にファクトリメソッド(上の例のAddメソッドやConstantメソッドなど)が用意されているので、そこから各派生型のExpressionを作成できます。

加算演算子はAddメソッド、乗算演算子はMultiplyメソッドで作れます。5, 7, 3 などの定数はConstantメソッドで作れます。

このようにして作った式を実行したい場合は、以下のようにします。

Expression<Func<int>> lambda = Expression.Lambda<Func<int>>(body) // () => 5 + 7 * 3;
Func<int> func = lambda.Compile();
int result = func(); // result == 26
 

まず、Expression.Lambdaメソッドを使って、作った式をラムダ式に変換します。この場合は「() => 5 + 7 * 3」というラムダ式ですね。

次に作ったラムダ式を実行可能なデリゲートへコンパイルします。
コンパイルした後は通常のデリゲートと同じなので、普通に実行できます。

まとめ

式木を使うことでC#でメタプログラミングができます。プログラムのなかでプログラムを作るという面白いことが可能です。

こんどは

次は実際に式木がどんな事に役に立つのか、みてみたいと思います。

参考にさせていただきました

2012年11月27日火曜日

[C#]Equalsメソッドの実装について

Equalsメソッドと等値演算子(==)の実装、つまり等価比較について自分の理解が怪しかったので改めてまとめてみました。

きっかけは @xin9le さんのこちらのツイートです。

私は謂わいる ValueObject の実装で、よく値比較のクラスを作るために Equals(とGetHashCode) をもりもり実装していたので、「なんか自分間違ってるのかも!?」と心配になりました。

はじめに

まずはじめにEqualsメソッドや等値演算子(==)の実装について、仕組みや注意点、実装ポリシーをおさらいです。以下の記事が大変参考になります。

Equalsメソッドと等値演算子の実装には、いくつかのケースが考えられるので、それぞれ見ていきたいと思います。

値型の等価比較について

値型のEqualsメソッドをオーバーライドしない場合、既定の実装(ValueTypeの実装)により、定義された各フィールドの等価性を調べるように実装されています。

ただし、この比較はリフレクションを用いて行われるので、パフォーマンスが求められる場合は自分でEqualsメソッドをオーバーライドして実装する必要があります。
(参考:値型については equals と等値演算子をオーバーライドします)

また、値型でEqualsメソッドを実装する場合は、等値演算子を実装する必要があります。この場合、Equalsメソッドと等値演算子は同じ結果を返すようにします。

このように値型の等価比較はあまり複雑なことはありません。通常、値型を作成する場合は、Equalsメソッドと等値演算子を合わせて実装するのが良いようです。

話がややこしくなってくるのは、参照型の場合です。

参照型の等価比較について

参照型のEqualsメソッドは、既定の実装では参照の等価性を評価します。
また、等値演算子も同様に参照の等価性を評価します。(参照型なので当然ですね)

しかし、参照型でも値の比較によって等価性を評価する場合があります。
例えば、文字列型(string)は参照型ですが、Equalsメソッドも等値演算子も値の比較で等価性を評価するように実装されています。

これは参照型であっても、文字列型(string)は値のような特性を持つためにこのようになっています。(たぶん)
文字列型(string)が参照比較だった場合、直感的な挙動でなくなると思うので、なんとなくやりづらそうです。

値型と参照型の選択

「じゃあややこしいから値っぽい型は全部値型でいいじゃん!」と思うわけですが、そういうわけにもいかない事情があります。

値型は代入時にコピーされるので、サイズが大きいと高コストになってしまいます。(逆にサイズが十分小さいなら値型のほうが高速です)
また、ボックス化やボックス化解除が頻繁に発生する場合も高コストになります。

そうなると、値のような特性を持つ型でも、サイズが大きい、ボックス化がよく起きる、というようなケースでは参照型のほうが適切になります。(stringはサイズが大きいですね)

まとめ

というわけで、値のような特性を持つ型については、参照型でもEqualsや等値演算子を実装するケースはあります。
ただ「じゃあそれってどのくらいあるの?」となると、私の想像できる範囲だと最初に挙げた ValueObject くらいしか思いつきません。

なので、そういったオブジェクトを作る必要がなければ、実装することはあまりなさそうです。
あと、Equalsの実装にはGetHashCodeを合わせて実装する必要がありますし、その際にも気をつける必要のある実装ポリシーがいろいろあって、なかなか大変です。

なんとなく自分の理解がそれほど外れた所になさそうな気がして一安心ですが、あまり深く考えずにバシバシ参照型で ValueObject を作っていたので、値型で作ったほうが適切でないか見直しが必要そうです。

あと、Equalsを実装する場合は、タイプセーフに比較できる IEquatable<T> インターフェイスを積極的に使ったほうがよさそうです。

TFT 10.14 Peeba Comp

こちらのガイドの自分用まとめです。 https://www.reddit.com/r/CompetitiveTFT/comments/hraunp/tft_1014_break_the_meta_new_peeba_comp_set_35/ 難しいですが完成すると非常に強く、プレ...