六日目です。
今日は電卓を改造することにします。
今日は電卓を改造することにします。
六日目に入るにあたり、選択肢は二つありました。
ひとつは、フォームを使ったアプリケーションは一旦横においといて、ほかの題材で勉強する。
もうひとつは、電卓をオブジェクト指向的に改造する。
ひとつは、フォームを使ったアプリケーションは一旦横においといて、ほかの題材で勉強する。
もうひとつは、電卓をオブジェクト指向的に改造する。
ほんとは前者にしようと思っていたのですが、宿題が出てしまったからには仕方がない。電卓をエレガントに改造します。
とはいっても、C#の機能を把握してないので、まどろっこしい手法を使うかもしれません。そこはつっこみを期待するってことで。
とはいっても、C#の機能を把握してないので、まどろっこしい手法を使うかもしれません。そこはつっこみを期待するってことで。
現在の設計の問題点
宿題の問題文の中に思いっきり書いてありますが、現在のソフトウェア構造では、電卓のロジックがすべてForm1::Button_Click()に集中しています。ダサすぎです。ボタンには数値入力の機能を持つものと演算を支持するものの二種類がありますが、これらをButton_Clickのなかで扱っているためにロジックが複雑になっています。ダサすぎです。
STEP1:ボタンのハンドラを分ける
まず、電卓の使い方を分析してみましょう。
- 数値(第一項)を入力する。
- 演算子を入力する。
- 新たな数値(第二項)を入力する。
- 新たな演算子を入力する。
これだけです。二回目の演算子入力によって、第一項と第二項が一回目の演算子に基づいて演算され、その結果が二回目の演算子に係る第一項となります。訳わかりませんね。よーするに数字や演算の種類はどうでも良いってことです。
これに基づき、ボタンのイベントハンドラを二つに分離しましょう。
Button_ClickをNumber_ClickとOperator_Clickに分けました。
Button_ClickをNumber_ClickとOperator_Clickに分けました。
コードはここにアップロードされた6th001.zipを参照のこと。
分岐が減った分わかりやすくなりました。
分岐が減った分わかりやすくなりました。
STEP2:Number_Clickの分岐がダサい
Number_Clickは今こんな感じになってます。
if (sender == Number0) { textBox1.Text += "0"; } else if (sender == Number1) { textBox1.Text += "1"; } else if (sender == Number2) { textBox1.Text += "2"; } ...
しょぼすぎます。直したくてたまりませんでしたが、やり方がわかりませんでした。というか、直せるかどうかもわからなかったのです。そこに謎の宿題文です。「"00"や"000"のボタンを追加しても……」
この問題文から察するに、ボタンにデータを割り当てておいて、ハンドラでそれを取得できそうな気配です。とゆーわけで、明らかに怪しげだったプロパティ、データバインディングを手探りで使ってみましょう……
と思ったけど、考えてみたらこれでも別にいんじゃね?というわけで、上のコードを下のように置き換えてみました。
この問題文から察するに、ボタンにデータを割り当てておいて、ハンドラでそれを取得できそうな気配です。とゆーわけで、明らかに怪しげだったプロパティ、データバインディングを手探りで使ってみましょう……
と思ったけど、考えてみたらこれでも別にいんじゃね?というわけで、上のコードを下のように置き換えてみました。
System.Windows.Forms.Button b = (System.Windows.Forms.Button)sender; if (sender != Dot) { textBox1.Text += b.Text; } else { if (!textBox1.Text.Contains(".")) { textBox1.Text += "."; } //ここで整形されるとドットが消えちゃうので即リターン return; }
とりあえず動きました(6th002.zip)。しょぼい決めうちダウンキャストをやってますが、後で型チェックをいれるということで。型チェックの仕方わかんないし。
as演算子を使うと型チェック+キャストがでけます。Button b = sender as Button; if (b == null){//キャストできないときの処理}キャストできなかったら例外が出ずにキャスト先にnullが入ります。
ちなみに型チェックだけ行いたい場合はis演算子やtypeof演算子を利用する方法があります。if (sender is Button){//ほにゃほにゃ} if (sender.GetType().Equals(typeof(Button))){//ほにゃほにゃ}そして、このように型チェックしたあとぷんが書いているように明示的にキャストすることもできますが、
一般的にas演算子を使う方が処理が早いと言われています。
isやasが中でどんなことしてるのかは知らんです。
あと、例外はコストが高いので使うべきじゃないです。
(NZ-000)
as演算子は超べんりと思うっす。ダウンキャストしたい場合はas、型で分岐したい場合はis、
メタ情報として管理したい場合はtypeofを使うって感じでしょうか。
例外に関しては、その処理コストが問題になる場面ってほとんどないので、積極的に使った方が
良いと僕は考えてます。
つーか例外がないとエラー処理の設計が超めんどい。
(pun)
あぁ、例外を薦めてないのは今回のような明示的なキャストを行う時の話ね。
Buttonしか来るはずないのに違うのもの来たらそりゃバグだし~。
(NZ-000)
追記そのいち
おおーそりゃすげえ。asべんり。というわけで書き直しますた。
おおーそりゃすげえ。asべんり。というわけで書き直しますた。
private void Number_Click(object sender, EventArgs e) { if( resetFlag == true ) { //演算子入力直後はボックスをリセット textBox1.Text = "0"; resetFlag = false; } System.Windows.Forms.Button b = sender as System.Windows.Forms.Button; if (b == null) { return; } if (sender != Dot) { textBox1.Text += b.Text; } else { if (!textBox1.Text.Contains(".")) { textBox1.Text += "."; } //ここで整形されるとドットが消えちゃうので即リターン return; } ... }
senderを先にチェックした方がベター。
(resetFlag == true && !(b is button))な時の関数呼出でおかしくなる(demi)
なんと的確なバグ指摘。
修正しますた。↓
(pun)
何気にちゃんと読んでるdemiワロタ。
(NZ-000)
private void Number_Click(object sender, EventArgs e) { System.Windows.Forms.Button b = sender as System.Windows.Forms.Button; if (b == null) { return; } if( resetFlag == true ) { //演算子入力直後はボックスをリセット textBox1.Text = "0"; resetFlag = false; } if (sender != Dot) { textBox1.Text += b.Text; } else { if (!textBox1.Text.Contains(".")) { textBox1.Text += "."; } //ここで整形されるとドットが消えちゃうので即リターン return; } ... }
まとめ
今日は特に新しい機能も使っておらず、別にまとめるようなこともありませんでした。
と思ったらキャストがとっても勉強になりました。
と思ったらキャストがとっても勉強になりました。
as演算子で安全にダウンキャストできる。
is演算子で型チェックができる。
typeof演算子で型情報をSystem.Type型で取得できる。