WinFormsで学ぶインスタンス ― Designerが書いたコードを自分で書いてみよう
前回の記事「GitHub Desktopで見てみよう ― WinFormsでボタンを置くと、裏では何が変わる?」では、WinFormsデザイナーでButtonを置くと、Form1.Designer.csに次のようなコードが自動生成されることを、GitHub Desktopの差分から確認しました。
button1 = new Button();
button1.Text = "button1";
Controls.Add(button1);
button1.Click += button1_Click;
これは、デザイナーにしか書けない特別なコードではありません。今回は、同じことを自分の手で書いてみます。
そして最後に自分でクラスを作り、「クラス」と「インスタンス」というオブジェクト指向の基本につなげていきます。
この記事の流れ

- 自分でButtonを作る
- 何度もクリックして、Buttonが増えることを確かめる
- 実行時に作ったButtonにも、Clickイベントを付ける
- GitHub Desktopで、今回はどのファイルが変わったのかを見る
- 自作のStudentクラスでも、同じことをしてみる
準備
前回の記事で作ったプロジェクト(button1とlabel1を置いたForm)を、そのまま使います。
まずは、今の状態をGitHub Desktopでコミットしておきましょう。こうしておくと、このあと自分で書いたコードだけが差分に表示されます。
1.自分でButtonを作ってみる
Form1.csを開き、button1_Clickの中身を次のように書き換えます。メソッドの上にあるcountの行も追加してください。
private int count = 0;
private void button1_Click(object sender, EventArgs e)
{
count++;
Button newButton = new Button();
newButton.Text = $"ボタン{count}";
newButton.Location = new Point(20, count * 55);
newButton.Size = new Size(150, 46);
Controls.Add(newButton);
}
実行して、button1を3回クリックしてみましょう。

クリックするたびに、「ボタン1」「ボタン2」「ボタン3」とButtonが増えていきます。
プログラムを起動したときには存在しなかったButtonが、クリックした瞬間に画面に現れました。
前回のDesigner.csと見比べてみる
今書いたコードを、前回デザイナーが生成したコードと並べてみます。
| デザイナーが書いたコード(Form1.Designer.cs) | 自分で書いたコード(Form1.cs) |
|---|---|
button1 = new Button(); | Button newButton = new Button(); |
button1.Location = new Point(325, 199); | newButton.Location = new Point(20, count * 55); |
button1.Size = new Size(150, 46); | newButton.Size = new Size(150, 46); |
button1.Text = "button1"; | newButton.Text = $"ボタン{count}"; |
Controls.Add(button1); | Controls.Add(newButton); |
やっていることは同じです。
Buttonを生成 → プロパティを設定 → FormのControlsに追加
デザイナーは魔法を使っていたわけではなく、このコードを代わりに書いてくれていただけでした。
なお、Sizeは前回デザイナーが生成した値と同じnew Size(150, 46)にしています。指定しないと、画面の拡大率の設定によっては、デザイナーで置いたボタンより小さく表示されることがあります。
違いは「いつ作られるか」
ひとつだけ大きな違いがあります。
デザイナーで置いたbutton1は、アプリの起動時(InitializeComponent()の中)に作られます。今回のButtonは、button1をクリックしたとき、つまりアプリの実行中に作られます。
そのため、アプリを終了してもう一度起動すると、作ったButtonはなくなっています。Form1.Designer.csには何も書かれていないので、起動した直後にはまだ存在しないのです。
2.なぜButtonが増えるのか
ポイントは次の1行です。
Button newButton = new Button();
new Button()によって、Buttonクラスから新しいオブジェクトが作られます。このように、あるクラスをもとに作られた具体的なオブジェクトを、そのクラスのインスタンスと呼びます。
button1をクリックするたびにbutton1_Clickが実行され、そのたびにnew Button()も実行されます。3回クリックすれば、3つの別々のインスタンスが作られます。
Buttonクラス
│
├── new(1回目)→ インスタンス Text = "ボタン1"
├── new(2回目)→ インスタンス Text = "ボタン2"
└── new(3回目)→ インスタンス Text = "ボタン3"
どれも同じButtonクラスから作られていますが、Textや位置はそれぞれ違います。1つのクラスから、状態の違うインスタンスをいくつでも作れるということです。
何度もクリックし続けると、やがてButtonはフォームの下にはみ出して見えなくなります。見えなくなってもButtonは作られていて、表示できる範囲の外に置かれているだけです。
補足:countをメソッドの外に書いた理由(クリックで開く)
countをbutton1_Clickの中で宣言すると、メソッドが呼ばれるたびに0から作り直されます。そのため、何回押しても「ボタン1」になってしまいます。メソッドの外(クラスのフィールド)に書くと、Formが開いている間は値が保たれます。- 一方、変数
newButtonは、メソッドが終わると使えなくなります。それでもButtonが消えないのは、Controls.Addによって、FormがそのButtonを持ち続けているからです。
3.実行時に作ったButtonにもClickイベントを付ける
作ったButtonは、今のままではクリックしても何も起きません。
前回の記事で、デザイナーが次の1行を書いていたことを思い出してください。
button1.Click += button1_Click;
同じことを、自分で書きます。button1_Clickに1行追加し、NewButton_Clickメソッドを新しく作ります。
private int count = 0;
private void button1_Click(object sender, EventArgs e)
{
count++;
Button newButton = new Button();
newButton.Text = $"ボタン{count}";
newButton.Location = new Point(20, count * 55);
newButton.Size = new Size(150, 46);
newButton.Click += NewButton_Click;
Controls.Add(newButton);
}
private void NewButton_Click(object sender, EventArgs e)
{
Button clicked = (Button)sender;
label1.Text = $"{clicked.Text}が押されました";
}
実行して、button1を3回クリックしてから、「ボタン2」をクリックしてみましょう。

label1に「ボタン2が押されました」と表示されます。「ボタン1」や「ボタン3」を押せば、それぞれの名前が表示されます。
1つのメソッドで、どのボタンか分かる理由
3つのButtonは、すべて同じNewButton_Clickを呼び出します。それでも、どのButtonが押されたのかが分かるのは、引数senderのおかげです。
senderには、イベントを起こしたオブジェクト(今回は、押されたButtonのインスタンス)が入っています。型はobjectなので、(Button)senderでButtonとして扱えるように変換してから、Textを読み取っています。
前回の記事では説明せずに通り過ぎたobject senderは、「誰がこのイベントを起こしたのか」を教えてくれる引数だったのです。
ここでも、3つのButtonが別々のインスタンスであることが分かります。同じクラスから作られていても、それぞれが自分のTextを持っています。
4.GitHub Desktopで差分を見てみる
ここまでの変更を、GitHub Desktopで確認してみましょう。
📷【キャプチャー③:GitHub DesktopのChanges一覧(Form1.csだけが変更されている)】
前回と違って、変更されたファイルはForm1.csだけです。Form1.Designer.csは変更されていません。
📷【キャプチャー④:Form1.csの差分(count、newButton関連、NewButton_Clickの追加)】
| 操作 | 変更されたファイル |
|---|---|
| デザイナーでButtonを置く(前回) | Form1.Designer.cs |
自分でnew Button()を書く(今回) | Form1.csのみ |
デザイナーで置いたコントロールは、起動時に作られるようForm1.Designer.csに書き込まれます。自分でForm1.csに書いたコードは、書いた場所で、書いたタイミングで実行されます。
どちらもnew Button()であることは同じですが、どこに書くかで「いつ作られるか」が変わることが、差分からも読み取れます。
確認できたら、ここでコミットしておきましょう。
5.Buttonは誰が作ったクラス?
ここまで、当たり前のようにnew Button()と書いてきました。では、Buttonクラスは誰が作ったのでしょうか。
Buttonクラスは、Microsoftが作り、.NETの一部として用意してくれているクラスです。私たちはそれを使って、インスタンスを作っていました。
クラスは、自分で作ることもできます。次は、自作のクラスで同じことをしてみましょう。
6.自分でStudentクラスを作ってみる
ソリューションエクスプローラーでプロジェクトを右クリックし、「追加」→「クラス」を選びます。名前をStudent.csにして追加します。
作成されたファイルを、次のように書き換えます。
namespace WinFormsApp1
{
internal class Student
{
public string Name { get; set; } = "";
public string GetGreeting()
{
return $"こんにちは、{Name}です";
}
}
}
ファイルの先頭にusingの行がいくつか生成されていても、そのままで構いません。また、Nameの後ろの= ""は、最初の値を空の文字列にしておくためのものです。これを書かないと、null許容に関する警告が表示されることがあります。
Buttonと比べてみましょう。
Nameはプロパティです。ButtonのTextやLocationと同じように、インスタンスごとの状態を持ちます。GetGreeting()はメソッドです。インスタンスができること(振る舞い)を表します。
Studentのインスタンスを作る
Form1.csのコンストラクター(InitializeComponent();の下)に、次のコードを追加します。
public Form1()
{
InitializeComponent();
Student student1 = new Student();
student1.Name = "田中";
Student student2 = new Student();
student2.Name = "佐藤";
label1.Text = student1.GetGreeting() + "\n" + student2.GetGreeting();
}
実行すると、label1に2人の挨拶文が表示されます。

student1とstudent2は、同じStudentクラスから作られた別々のインスタンスです。だから、それぞれが違うNameを持ち、GetGreeting()の結果も変わります。
なお、GetGreeting()は文字列を返すだけで、画面への表示はForm側で行っています。Studentは「生徒の情報」を表すクラスなので、画面のことはFormに任せています。このように役割を分けておくと、同じStudentクラスを、別の画面や別のアプリでも使えます。
GitHub Desktopで見ると

今回は、Student.csが新しいファイルとして追加され、中身がすべて緑(追加)で表示されます。Form1.csには、Studentを使うコードの追加分が表示されます。

| やったこと | GitHub Desktopで見えた変化 |
|---|---|
| デザイナーでButtonを置く(前回) | Form1.Designer.csが変わる |
自分でnew Button()を書く(今回の前半) | Form1.csだけが変わる |
| 自分でクラスを作る(今回の後半) | 新しいファイル(Student.cs)が増える |
ButtonとStudentを並べてみる
| Button | Student | |
|---|---|---|
| 誰が作ったクラス? | Microsoft(.NET) | 自分 |
| インスタンスの作り方 | new Button() | new Student() |
| 状態(プロパティ) | Text、Location、Sizeなど | Name |
| 振る舞い | Clickイベント、Focus()などのメソッド | GetGreeting() |
| 複数作ると? | それぞれ違うTextを持つ | それぞれ違うNameを持つ |
Button
↓
.NETが用意したクラス
↓ new Button()
Buttonのインスタンス
↓
Text などの状態を持つ/Click などの振る舞いがある
Student
↓
自分で作ったクラス
↓ new Student()
Studentのインスタンス
↓
Name などの状態を持つ/GetGreeting() などの振る舞いがある
Buttonでやっていたことと、Studentでやったことは同じです。ここまでが、C#でオブジェクト指向を理解するための大切な第一歩です。
- クラス:どんなデータ(状態)と、どんな処理(振る舞い)を持つかを決めたもの。ButtonやStudent。
- インスタンス:クラスから
newで作った実体。newButton、student1、student2。 - プロパティ:インスタンスが持つ状態。
Text、Nameなど。 - メソッド:インスタンスができること。
GetGreeting()など。
「クラスは設計図、インスタンスは実体」という説明をよく見かけます。今回の実験で、それは暗記するための言葉ではなく、ButtonやStudentを使って実際にやっていたことだと分かったはずです。
まとめ
| やったこと | 分かったこと |
|---|---|
自分でnew Button()を書いた | デザイナーが生成していたコードは、自分でも書ける |
| 何度もクリックした | newするたびに、別のインスタンスが作られる |
Click +=で実行時のButtonにイベントを付けた | 1つのメソッドでも、senderでどのインスタンスか分かる |
| GitHub Desktopで差分を見た | 自分で書いたコードはForm1.csにだけ現れる |
| Studentクラスを作った | 自作のクラスでも、Buttonと同じようにインスタンスを作れる |
前回は「デザイナーで操作する → 差分を見る」で、自動生成されたコードの意味を知りました。今回は「同じコードを自分で書く → 実行して確かめる」で、クラスとインスタンスの関係を体験しました。
WinFormsの画面作りと、C#のオブジェクト指向は、別々の学習ではありません。画面に置いていたButtonも、自分で作ったStudentも、同じ仕組みで動いています。
次のステップ:継承へ
今回は、クラスからインスタンスを作るところまでを確かめました。
次は、「なぜButtonにはあんなにたくさんのプロパティがあるのか」という疑問から、クラスどうしの関係である継承へ進みます。
→ WinFormsで学ぶ「継承」入門(シリーズ目次)
デザイナが生成するコードをもっと詳しく知りたい場合は、WinFormsの「ドラッグ&ドロップ」の正体(シリーズ目次)もあわせてどうぞ。









ディスカッション
コメント一覧
まだ、コメントがありません