【勉強メモ】ITエンジニアになる!チャレンジC#プログラミング
C#によるアプリ作成(パスワードジェネレータ)を通じて、C#による処理(ロジック)作成、GUI(フォームとWPF)作成、デザインパターンについて学ぶことができる。
よくあるC#入門書とは異なり、C#の文法やフォームアプリの作り方を体系的・網羅的に教えるのでなく、パスワードジェネレータの作成過程を通じて、ロジック・クラス・GUIの作り方を教えるだけでなく、「GUIアプリ作りの考え方」そのものを体験させてくれる。
素人・趣味プログラマからすると、「プロは、どのようにプログラミングしているだろう?」と気になることは多々ある。自分のプログラミングが一人よがりなんじゃないかと気になる。その点、本書はOJTを受けながらC#を学んでいるような気分になれて良い(プログラミングのOJTなんて受けたことないけど)。
また、本書内にもあるように「デザインパターンの入門書」としても読めるので、他の本よりもオブジェクト指向プログラミングの意義を感じながら学んでいける。
C#の基本文法についてもざっくりとしか説明していないので、これを「はじめの1冊」にするのは厳しいかもしれないが(僕にとってはまともに読んだ初のC#本だけど、C言語は習得済だったので。C系統の言語経験があればイケると思う)、2~3冊目に読むと面白い本だと思う。
Chapter 0 プログラマーになるということ
- プログラマも、プログラムを書くだけでなく、開発工程を意識することが必要
- 開発工程には、要件定義・システム設計・プログラミング・テスト・運用保守の工程がある
- 最も重要なのは要件定義
- オブジェクト指向プログラミングは「考え方」であり、機能実装に対するアプローチ方法
- 文法や経験だけではオブジェクト指向は身につかない。デザインパターンの学習が必要
Chapter 1 C#の基本
数字、大小英字、記号をランダムに混ぜた文字列(パスワード)を生成するロジックを作成していく
- C#の基礎
- Visual Studioの使い方(各機能について随時説明あり)
Chapter 2 オブジェクト指向プログラミング
クラスの作成
Chapter1で作成したプログラムは1ファイル内に記述したものだったが、それを機能ごとにクラスに分ける(そしてクラスごとにファイルも分ける)。
オブジェクト指向プログラミングとは、機能の変更や拡張に対して、最小のコストで対応できるプログラム設計のこと(らしい)。従来のプログラミングは、どのようなメソッドを作成するかがポイントだったが、オブジェクト指向プログラミングでは、どんなクラスを作成するのか、どのクラスをグループ化するのかがポイントになる。
継承
- 親クラス(基底クラス)、子クラス(派生クラス)
- 親クラス型の変数(インスタンスではない)は、自身を継承する子クラスのインスタンスを格納できる
- 継承とは、クラスのグループ化。共通要素を親クラスにまとめる。
- メソッドのオーバーライド
- 親クラスのメソッドと同じ名前のメソッドを子クラスで作成する(上書きする)
- オーバライドされる親クラスのメソッドには、virtualキーワードを付ける
- オーバーライドする子クラスのメソッドには、overrideキーワードを付ける
ポリモーフィズム
- 同名メソッドの処理が、実際に格納されるインスタンスによって内容が変わること
- 親クラスのvirtualメソッドを継承した子クラスは、オーバーライドすることで異なる処理内容を実装可能
- どの子クラスのインスタンスを使うかによって、オーバライドメソッドの内容が異なる。
抽象クラス
- 抽象クラス、抽象メソッドには abstractキーワードを付ける
- 抽象クラスには、処理内容を記述してはいけない
- 子クラスは必ず抽象メソッドの処理を実装しなければならない
- 抽象クラスは1つ以上の抽象メソッドを持つ
インターフェイス
- インターフェイスはメソッドの定義を記述したカタログのようなもの
- 抽象クラスと似ているが、インターフェイスにはメソッド定義しかコードできない
- クラスがインターフェイスを実装(記述は継承と同じだが、「実装」という)すると、クラスはインターフェイスのメソッドを必ず実装しなければならない。
- インターフェイスにメソッドを強制されることで、インターフェイスを使用するのに必要な要件が分かる。
- インターフェイス型の変数も、親クラスと同様に自身を実装したクラスのインスタンスを格納できる
- C#ではクラスの多重継承は禁じられているが(親クラスは1つ)、インタフェースは多重継承(多重実装?)できる。
静的メソッド
- インスタンスを生成しなくとも呼び出せるメソッド
- クラス内でのメソッド定義で、staticキーワードを付ける
- インスタンスを生成できず、静的なメソッド・変数・プロパティ・イベントしか持たない静的クラスというものもある
Chapter 3 Windowsフォームアプリケーションの開発
MVCでの開発
- MVCは、UIを持つアプリの代表的なデザインパターン。
- プログラムを「Model(ロジック) / View(UI) / Controller(ユーザ入力の処理)」の役割に分類
- ViewはControllerクラスにユーザからの情報を渡す
- Controllerクラスは、Viewから受け取ったユーザ入力を処理してModelに渡し、処理を委ねる
- Modelの処理結果をControllerが受け取り、Viewに渡す。
- Viewが直接Modelを呼び出すことは禁止。Controllerが仲介役。
- MVCは、UIクラス(フォーム)にできるだけロジックコードを書かず、UIとロジックを分離するデザイン
- Controllerクラスは、Mainメソッドの作業を担当。コンソールアプリを作成するということは、ロジッククラスを構築することだけでなく、Controllerクラスの雛型を用意していることになる。
Chapter 4 WPFアプリケーションの開発
MVVMでの開発
- MVVMは、実質的にはMVCと同じ。「 Model / View/ ViewModel(=Controller)」
- ユーザの入力情報と格納データを、データバインディングにて自動的に処理していくのがMVVMの設計思想
- データバインディングはMVVMモデル特有の仕組み。MVCとの違いでもある。
- ViewはViewModelに依存し、ViewModelはModelに依存する。逆方向の依存は無い。
- MVVMはもともとWPFで生まれた考え方。現在はAndroidやWebのJavaScriptでも採用されている
データバインディング
- ViewとView Modelを結びつける仕組み。
- ViewとView Modelのどちらかで値が書き換われば、値が変化するたびにViewとView Model両方が変更される。
デリゲート
- デリゲート(delegate)は、メソッド型というデータ型の一種。メソッドを参照するための型。
- メソッドをプロパティのように使うことができる機能
- C/C++の関数ポインタや関数オブジェクトをオブジェクト指向に適すように拡張したもの。
- delegate型の変数には、メソッドを代入できる。Cの関数ポインタと用途はほぼ一緒
using System; delegate void ShowMessage(); // デリゲートの型定義 class Person{ string naem; public Person(string name){ this.name = name;} public void ShowName(){ Console.Write("名前: {0}\n", this.name); } } class Delegate Test { static void Main() { Person p = new Person("山田太郎"); // デリゲートにメソッド参照を格納。インスタンスメソッドもクラスメソッドも格納可能 ShowMessage show = new ShowMessage(p.ShowName); // ShowMessage show = p.ShowName; // C#2.0以降はこの書き方も可 show(); // デリゲート変数を使ってメソッドを実行 }
using System; delegate void ShowMessage(); // デリゲートの型定義 class DelegateTest { static void Main() { ShowMessage a = new ShowMessage(A); //デリゲートにメソッドを代入 a += new ShowMessage(B); a += new ShowMessage(C); // C#2.0以降は以下のように簡略化して書ける // a = A; // a += B; // a += C; a(); // デリゲートでメソッドを実行。登録順にA,B,Cが実行される。 } static void A(){ Console.Write("Aが呼ばれました\n");} static void B(){ Console.Write("Bが呼ばれました\n");} static void C(){ Console.Write("Cが呼ばれました\n");} }
Func デリゲート
Func<T> == ”delegate T functionName()”
メソッドのシグニチャ(引数、戻り値の構造)が異なると新たにデリゲートの定義が必要だが、C#では戻り値のあるデリゲートに対して、Func
Func<T> //引数を取らず、戻り値がTとなるデリゲート| Func<T1, T2> //引数にT1型を取り、T2型の戻り値を持つデリゲート| Func<T1,T2,T3> //引数にT1、T2型を取り、T3型の戻り値を持つデリゲート|
Action デリゲート
Action<T> == "void function( T arg)"
戻り値を持たない(void型)デリゲートは、Action
Action<T> //引数にT型を受け取って処理を行うデリゲート Action<T1,T2> //引数にT1、T2型を受け取って処理を行うデリゲート //Actionデリゲートは引数を16個まで受け取れる。
ラムダ式
ラムダ式はメソッド(処理)そのもの。その場で使い捨ての関数を定義できる便利機能。例では1行のみだが、{ }で囲えば複数行処理も可能。C++の「名無しの関数オブジェクト」と考え方は似ている気がする(そういえばC++にもラムダ式が導入された)。 "=>"(アロー演算子)の左辺が引数であり、右辺が処理・戻り値となる。
Func<int,int> double = x => x * 2; // デリゲート変数doubleにラムダ式の参照を格納 Func<int,int,int> plus = (a,b) => a + b; // デリゲート変数plusにラムダ式参照を格納 Func<string> hello = () => Console.WriteLine("Hello"); // 引数無い場合は()を書く
デリゲートコマンド
Commandプロパティ用のヘルパークラスを作成する。 * WPFでは、Buttonクリック時に処理を行わせるのにCommandプロパティを使う。 * Commandプロパティに設定する値は、ICommandインタフェースを実装したクラス。 * ICommandインタフェースは、System.Windows.Input名前空間で定義されている。 * VisualStudioのライトバブル機能を使うと便利。 * データバインディングされるView-Modelクラスのプロパティも、ICommandインタフェース実装が必要。ただし、このクラスには実際の処理は記述しない。 * 引数としてデリゲートを受け取る。処理はそのデリゲートが引き受ける。
イベント
C#においてイベント駆動型のプログラム作成を容易にするため、イベント処理用のevent構文が用意された。 C#のイベント機能は、あるクラスで発生した出来事を、あらかじめ登録された一群のメソッドに対して、1回の呼び出しによって全て伝える機能と言える。 * イベント:デリゲート専用のプロパティの一種。イベント駆動処理に使われる。 * delegate型をクラス外に公開する場合、プロパティ構文とevent構文が選べる。 * 普通のプロパティで公開した場合、デリゲートに対してすべての操作ができるが、 * event構文で公開した場合、クラス外からはメソッドの登録と登録解除しかできなくなる(自クラス内では普通のデリゲートのように使用できる) * event構文を用いた場合、設定元からはメソッド呼出できなくなる。呼び出せるのはクラス内からのみ。 * イベントには発生側と受取側があって、発生側に受取側を登録する口が必要 * C#のevent構文は、このイベント登録口を作るための構文。ただ、使いにくいらしい。
using System; using System.Threading.Tasks; // イベントを発生させるクラス class EventSource { public event EventHandler<int> Progress; // 登録口(イベントハンドラ) private async Task RunAsync(){ for (int i = 0; i < 100; i++){ await Task.Delay(100); // クラス内からは普通のデリゲートのように使って呼び出せる。 // EventHandler(object? sender, EventArgs e)デリゲートは、 // イベントデータを含まないイベントを処理するメソッドを表すデリゲート Progress(this, i); // イベントを起こす } } } class EventSubscriber : IDisposable { EventSource _source; public EventSubscriber(EventSource source){ // コンストラクタ _source = source; _source.Progress += OnProgress; // 購読開始。イベントの受取側としてOnProgressを追加 } // イベントが通知されると呼び出される処理 // EventSourceクラス側でProgressを呼ぶたび、EventSubscribeクラス側でOnProgressが実行される。 private void OnProgress(object sender, int i){ Console.WriteLine("進捗 " + i + "%"); } public void Dispose(){ // デストラクタ(イベントからの登録解除は必須) _source.Progress -= OnProgress; // 購読解除。OnProgressをイベント受取から外す } }
属性
- 属性(attribute)は、クラスやメンバーに追加情報を与える。
- 属性は角括弧[ ]でくくり、クラスやメンバーの前に付けて使う。
- 属性名は語尾に「Attribute」を付けることになっているが、C#から利用する場合は省略可
- WPFはデータドリブンプログラミング
Chapter 5 パスワード生成ルールの追加
パスワード生成アプリに新しいロジックを追加する。 ここまでで作成したパスワード生成ルールを担当するクラスは全て、自作の「ILetterFactory」インターフェイスを実装しており、「Create」メソッドを持つ。
つまり、「ILetterFactory」型の変数は、「ILetterFactory」インターフェイスを実装するクラスのインスタンスを格納できるので、追加したいパスワード生成ルールを担当するクラスを「ILetterFactory」インターフェイスを実装して作れば、パスワードを受け取る部分は何の変更もする必要はない。
パスワード生成ルールは、変更や追加の可能性があることは想像がつくので、パスワード生成ルールを担当するクラスを作り、ルールの数だけクラスを生成するという設計にすると、変更や追加の際にメリットがある。
Chapter 6 ロジックの改良
LINQで、紛らわしい文字を排除するロジックを追加する
- C#3.0以降では、LINQ(Language-Integrated Query、統合言語クエリ)という機能を使用可能。
- LINQは、リストのような複数要素(コレクション)に対し、簡易な構文で何らかの処理をする機能
- 「LINQはforeachのパワーアップ版」と言う人も(要素1個1個に対して処理をするから?)。
- クエリ構文とメソッド構文がある。'.'でつなぐ下記のようなのはメソッド構文。
- ある人曰く「とりあえずSelectとWhereだけ覚えればなんとかなる」
- Selectは、コレクションの中身を全て別のものに変換して次に渡す。最も使用頻度高い。2つの引数を受け取る関数を渡すことができ、第1引数には要素、第2引数にはインデックスが渡される。
- Whereは、コレクションの値を検証して、条件に合うものだけを通す。
- ラムダ式で作った関数は必ずデリゲートに登録される。LINQの引数も実はデリゲート。Select()の引数は、実は Func<T, TResult>
// LINQを使った書き方。メソッドチェーン。 string[] lowers = Enumerable.Range(97, 26) // Range(始値, 幅)で連続値(97~122)のコレクションを生成 // ↑ 戻り値はIEnumerable<Int32>型 .Where(x => x != 108) // Where(ラムダ式)で条件に合うもの(108以外)を抽出 // ↑ 戻り値はIEnumerable型 .Select(x => ((char)x).ToString()) // 抽出した数値を文字列に変換して新しい // コレクションを生成。戻りはIEnumerable<T(今回はstring)>型 .ToArray(); // IEnumerable<string>型をstring型の配列に変換 int n = random.Next(lowers.Length); return lowers[n];de
- EnumerableクラスのRangeメソッドを使って、指定した範囲内の整数のシーケンス(連続値)を生成。
戻り値は IEnumerable - 生成した連続値に対し、Whereメソッドでデータ抽出。引数にはラムダ式で抽出条件を指定。
戻り値は IEnumerable型 - 抽出されたコレクションに対して、Selectメソッドで変換処理を施した新コレクションを生成(射影)。
引数には処理内容を記述したラムダ式を受け取れる。
要素をchar型にキャストし、さらに文字列に変換(ToStringメソッド) - ToArrayメソッドで、IEnumerable
型のコレクションを、string型の配列に変換。
上記のように複数の処理を繋げて記述することをメソッドチェーンといい、LINQの特徴。
StringBuilderクラス
- StringBuilderクラスは、文字列の制御・変更を行うクラス
- C#で文字列を変更する場合、新しくメモリを確保する必要がある。
文字列を何度も変更する場合にStringBuilderクラスを使えば、繰り返しメモリを確保せずに済む。 - StringBuilderインスタンスの生成時に、制御対象の文字列を引数に渡す。
class Program{ static void Main(string[] args){ string s = "12@34#56"; var builder = new StringBuilder(s); // 文字列sのStringBuilderオブジェクト生成 builder.Remove(2, 1); // s[2]から1文字を除去 builder.Append("@"); // 最後に"@"を追加 Console.WriteLine(buidler.ToString()); } }
※ ちなみに、StringBuilderクラスを使わない場合
class Program{ static void Main(string[] args){ string s = "12@34#56"; s.Remove(2, 1); // s[2]から1文字を除去 s. +="@"; // 最後に"@"を追加 Console.WriteLine(buidler.ToString()); } }
……StringBuilderクラス使わない方が短く書ける。この程度のコードじゃ、ありがたみが無いんだろう。
パスワードを生成したら、自動的にクリップボードにコピーさせる
Windowsフォームアプリの場合
.NET Frameworkには「System.Windows.Forms.Clipboardクラス」が用意されており、クリップボードのデータを取得設定することができる。
クリップボードにテキストデータを転送するには SetTextメソッド、クリップボードからテキストデータを取得するときは GetTextメソッドを使う。
MainForm.cs の パスワード生成ボタンの Excecute_Click イベントハンドラーにコードを追加する。
using System.WIndows.Forms; private void Execute_Click(object sender, EventArgs e){ string pw = controller.MakePassword(); Password.Text = pw; Clipboard.SetText(pw); // クリップボードにコピー }
これで、パスワード生成ボタンをクリックすると、クリップボードに生成した文字列がコピーされる。
WPFアプリの場合
「プロジェクト」→「Add Reference(参照の追加)」→「Assemblies(アセンブリ)」 →「System.Windows.Forms」にチェックをいれて「OK」
プロジェクトは「Clipboardクラス」にアクセスすることができるようになっている。
MainWindowVMクラスのコードに、usingディレクティブを追加する。
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using PasswordCreator; using System.Windows.Forms; // 追記
パスワードはCreatedPasswordプロパティに設定される。よって、setアクセサー内にクリップボードにテキストデータを転送するコードを追加する。
private string createdPassword; public string CreatedPassword{ get { retrun createdPassword; } set { createdPassword = value; Clipboard.SetText(createdPassowrd); // 追記 OnPropetyChanged(); } }
WPFでもフォームアプリのクラスを使うのね。
Chapter 7
フォルダリング
オブジェクト指向プログラムミングでは必然的にクラスファイルが増加する。クラス数増加はソフトウェア設計において悪いことではないが、ソリューションエクスプローラに多くのクラスが表示されていると、クラス同士の関連性を直感的に理解しにくくなる。そこで、フォルダを作成し、関連したクラス群をフォルダーに格納することで、ソフト構造を理解しやすくする。
コンソールアプリをフォルダリングする
UIプロジェクトはその性質上、クラスは多くない。コンソールアプリは継承やインタフェースを使用しているので、クラスが多く作成される。これらを、クラスの関連性に着目してグループ分けする。
ソリューションエクスプローラで「PasswordCreator」を選択した状態で、 「プロジェクト」→「New Folder(新しいフォルダ)」を選択すると…
ソリューションエクスプローラに「NewForlder1」が作られるので、「Letter」と名前変更し、Letter関連クラス(Letter.cs, LowerLetter.cs, MarkLetter.cs, NumLetter.cs, UpdderLetter.cs)をドラッグ&ドロップ
同様にして「Factory」フォルダ、「Utility」フォルダを作成し、関連クラスをグループ分けする。 * Factoryフォルダ: *Factoryクラス * Utirlyフォルダ: Shuffleクラス、ArrangeEndクラス
フォルダ作成後、ソリューションエクスプローラでフォルダを選択した状態でクラスを追加すると、そのフォルダ―内にクラスが追加される。さらに、そのクラスの名前空間には、プロジェクト名にフォルダ名が追加される(「PasswordCreator.Factory」のように)。このクラスを使う際には、usingディレクティブでフォルダ名を含めた形で追加しておくと、コーディングが楽になる。ディレクティブを追加しない場合、クラス名の前にフォルダ名をコードすることを強制される。
ただ、既に作成済みのクラスをフォルダに移動しても、名前空間には変更はない。
MVVMのプロジェクトテンプレート
WPFアプリをMVVMで開発するときは、ViewModelクラスとDelegateCommandクラスが必要になる。必須のクラスなので、毎回作成する手間を省きたい。プロジェクトテンプレートを作成することで、その手間が省ける。