2018年2月28日水曜日

CheckBox、RadioButtonなど色々なコントロールを動かしてみる

前回のエントリーでは、テキストボックスに入力した文字列をボタンをクリックしたら内部に取り込んでウインドウのラベルに表示する、というプログラムを動かしました。

この中で、テキストボックス、ボタン、ラベルはそれぞれコントロール(Control)と呼ばれるオブジェクトです。Windows Formでは、様々なコントロールを使うことが出来るようです。このページに一覧が出ていますので、参照ください。

CheckBox


今回は、コントロールの中から、チェックボックス(CheckBox)をWindows Formに配置して動かしてみます。CheckBoxは、幾つかあるオプションの中から複数の項目を選択するときに使われるGUIですね。小さな四角で、クリックするとその中にチェックマークが現れる、というデザインのものが多いと思います。

リスト1は、前回と同様に、プログラム本体で定義されているMyFormクラスのインスタンスを生成して、Application.Runメソッドで起動しています。

なお、今後の学習で、このスタイルが何度か続くのが見えていたので、前回からMainメソッドが含まれるクラスを別ファイルにして、コピーして使い回すようにしました。この場合、両方のいずれのファイルも、中でクラスを定義する名前空間を同じにしておきましょう。そうしないと、public定義になっていても、特別な書き方をしないと別のファイルのクラスが見えません。

今回のリストも、特にVisual Studioなんか使わなくても、テキストエディタで打ち込んで、csc.exeでコンパイルすれば動きます。

csc main.cs Program.cs


です。

リスト1 main.cs

using System;
using System.Windows.Forms;

namespace sample15
{
    class Program
    {
        [STAThread]
        private static void Main( string[] args )
        {
            Application.Run( new MyForm() );
        }
    }
}

リスト2が、プログラムの本体になります。MyFormクラスのコンストラクタでMyFormオブジェクトの幅(this.Width)、高さ(this.Height)を設定して、MyForm上に配置されるコントロールの各種設定を行うメソッドを呼び出します。

今回のプログラムでは、LabelオブジェクトとCheckBoxオブジェクトを使用するので、その大きさ、フォント種類、表示するテキスト等の設定を行っています。

さらに、チェックボックスの状態が変化したときに発生するイベントを利用して、Labelオブジェクトの中のテキストを書き換える処理をするイベントハンドラーの登録、実際の処理を行うメソッドの定義をしています。

リスト2 Program.cs

using System;
using System.Drawing;
using System.Windows.Forms;


namespace sample15
{
    public class MyForm : Form  // Windows.Formの派生クラスとしてMyFormを定義
    {
        private Label label;
        CheckBox check;
        public MyForm()
        {
            this.Width = 300;  // MyFormの幅のピクセル値設定
            this.Height = 200;  // MyFormの高さのピクセル値設定
            setupControls();
        }

        public void setupControls()
        {
            label = new Label();  // インスタンス名labelでLabelのインスタンスを生成
            label.Text = "type text:";
            label.Font = new Font("Geneva",12,FontStyle.Regular);
            label.Height = 30;
            label.Width = 300;
            this.Controls.Add(label);  // labelをMyFormに貼り付け
            check = new CheckBox();  // インスタンス名checkでCheckBoxのインスタンスを生成
            check.Text = "check box";
            check.Top = 50;  // <-- CheckBoxの配置座標(左上)
            check.Left = 50;  // <-- CheckBoxの配置座標(左上)
            check.CheckedChanged += check_changed;  // CheckBox状態変化イベントのイベントハンドラー登録
            this.Controls.Add(check);  // CheckBoxをMyFormに貼り付け
        }

        private void check_changed(object sender, System.EventArgs e)
        {
            label.Text = "checked: " + check.CheckState + "(" + check.Checked + ")";
            // labelのテキストをcheckの状態(論理値)に書き換え
        }
    }
 
}

これをコンパイルして動かすと、以下のようになります。

起動直後、チェックボックスがチェックされていない状態で起動します。

チェックボックスにチェックを入れると、下のようになります。

この状態でチェックを外すと、下のようになります。起動直後の状態には戻りませんね。


このsetupControl()メソッドとイベントハンドラーの処理、イベント処理のメソッドを色々と書き換えることで、色々なコントロールを配置して動かしてみることが出来ます。

コントロールそれぞれの使い方は、ドキュメントを見ながら研究するしかありませんが、色々なコントロールを試してみて、Windows FormアプリのGUIの作り方を研究するのも楽しいと思います。

GroupBoxとRadioButton


上記リストのコントロールの配置とイベントハンドラー、イベント処理を書き換えてみます。グループボックス(GroupBox)、ラジオボタン(RadioButton)というコントロールを使って、下記のようなことが出来ます。

        public void setupControls()
        {
            label = new Label();  // Labelクラスのインスタンス生成
            label.Text = "type text:";
            label.Font = new Font("Geneva", 12, FontStyle.Regular);
            label.Height = 30;
            label.Width = 300;
            this.Controls.Add(label);  // labelオブジェクトをMyFormに貼り付け

            GroupBox group = new GroupBox();
            group.Width = 200;
            group.Height = 100;
            group.Top = 50;
            group.Left = 50;
            group.Text = "radio group";
            this.Controls.Add(group);

            radio1 = new RadioButton();
            radio1.Text = "male";
            radio1.Top = 25;
            radio1.Left = 25;
            radio1.Checked = true;
            radio1.CheckedChanged += check_changed;
            group.Controls.Add(radio1);

            radio2 = new RadioButton();
            radio2.Text = "female";
            radio2.Top = 50;
            radio2.Left = 25;
            radio2.CheckedChanged += check_changed;
            group.Controls.Add(radio2);
        }

        public void check_changed(object sender, System.EventArgs e)
        {
            RadioButton btn = (RadioButton)sender;
            label.Text = "selected:  " + btn.Text;
        }

ラジオボタンというコントロールを二つ配置して、押されたら反応する、というプログラムです。radio1オブジェクトを最初から有効な状態にして起動するプログラムになっています。
femaleを有効にすると、その旨のテキストが表示されます。

その状態からmaleを有効にすると、その旨のテキストに書き換わります。


なお、起動時にmaleが有効な状態で起動しても、プログラム内ではラジオボタンの状態を読んでいないので、表示に変化は起こりません。表示に変化が起こるのは、ラジオボタンの状態が変化してcheck_changedイベントが発生したときだけです。

今回はここまでです。

2018年2月25日日曜日

Windows Formアプリでテキスト入力

前回の初めてのWindows Formでは、ウインドウの中に単純なテキストの出力をしましたが、今回はテキストの入力です。その中で、「イベント」というものを取り扱います。

この段階でも、まだ普通のテキストエディタでソースを入力して、コマンドラインからコンパイル、そして起動する、ってことで対応できます。もちろんVisual StudioなどのIDEでプロジェクトを作ることも可能です。

下に学習に使用したプログラムリストを示します。今回はファイルが二つに分かれています。main.csはプログラム本体のMyFrmクラスのインスタンスを作って呼び出すだけのmainメソッドが入っており、myfrm.csにはプログラム本体Formクラスの派生クラスの定義が入っています。

ファイルが分割されていても、

csc main.cs myfrm.cs


のようにコンパイラに全ファイルを指定すると、ちゃんとmainメソッドから動いてくれます。

ちなみに、Visula Studioをインストールすると、C#のコンパイラであるCSCがインストールされますが、Windows Vista以降のWindowsでは、.NET Frameworkが標準で入っていて、その中にCSCが入っているそうです。こちらに、詳しい説明がありますので、ご興味のある方はお読みください。

さて、下記リストですが、リスト1のmain.csは、mainメソッドの定義だけです。最初にも書いたように、MyFormクラスのインスタンスを生成して呼び出す、という処理をしています。

リスト1 main.cs

using System;
using System.Windows.Forms;

namespace MyFrmApp
{
    class Program
    {

        [STAThread]
        private static void Main( string[] args )
        {
            Application.Run(new MyForm());
        }
    }
}

このmyfrm.csがプログラムの本体になります。

リスト2 myfrm.cs

using System;
using System.Drawing;
using System.Windows.Forms;

namespace MyFrmApp
{
    public class MyForm : Form
    {
        private Label label;
        private TextBox box;
        private Button btn;

        public MyForm()
        {
            this.Width = 300;
            this.Height = 200;
            setupControls();
        }

        public void setupControls()
        {
            label = new Label();
            label.Text = "type text:";
            label.Font = new Font("Geneva", 12, FontStyle.Regular);
            label.Height = 30;
            label.Width = 300;
            this.Controls.Add(label);
            box = new TextBox();
            box.Width = 225;
            box.Top = 50;
            box.Left = 25;
            this.Controls.Add(box);
            btn = new Button();
            btn.Text = "click";
            btn.Height = 30;
            btn.Width = 100;
            btn.Top = 100;
            btn.Left = 100;
            btn.Click += btn_Click;
            this.Controls.Add(btn);
        }

        private void btn_Click(object sender, System.EventArgs e)
        {
            string str = box.Text;
            label.Text = "you write '" + str + "'.";
        }
    }
}

起動すると、まず以下ようなウインドウが出ます。


そして、ウインドウ内のテキストボックスに文字列を書いてclickボタンを押すと、以下のようになります。

type text:がyou write "入力文字列"に書き換わっていますね。

プログラムの内容は以下の通りです。

まず、Formクラスの派生クラスとしてMyFormを定義し、MyFormに貼り付けるLabel、TextBox、Buttonの部品(Control)のための変数を宣言します。これは、MyFormクラスの外からは見えなくて良いので、private宣言をしているんだと思います。

次に出てくるpublic MyFormはMyFormクラスのコンストラクタです。この中で、まず自分自身の幅と高さをthis.Width、this.Heightに数値を設定することで設定してします。

このWidthとHeightは、Formクラスにあるプロパティで、ウインドウの幅と高さを設定する為のものですが、MyFormはFormの派生クラスなので、当然MyFormにも存在するプロパティです。

WidthとHeightを設定した後、setupControls()メソッドを呼び出しています。

setupControls()メソッドの中では、Label、TextBox、Buttonのインスタンスをそれぞれ生成して、冒頭で定義したlabel、box、btn変数にインスタンスを代入しています。

そして、それぞれの幅、高さ、表示されるテキスト、labelに関してはfontの設定も行っています。

さらに、btnで示されるButtonオブジェクトをクリックしたときに発生するイベントを利用して、boxオブジェクトに入力されているテキストをlabelオブジェクトに代入してウインドウに表示させる処理を行うよう、処理の内容をbtn_Click()メソッドに定義し、btnオブジェクトがクリックされたときに発生するイベント(btn.Click)でbtn_Click()メソッドが起動するよう、

btn.Click += btn_Click;

でイベント登録をしています。なお、btn_click()の引数のobject senderとSystem.EventArg eですが、イベントの処理をするメソッドでは必ずこのような書き方をします。今の段階では、イベントでは必ずこのように書く、と覚えておけば良いと思います。

今回の学習で初めて出て来た「イベント」ですが、Windowsではマウスのクリックやキーボードの打鍵などを「イベント」として動作のきっかけにする、イベントドリブンという動作方法で処理が進んでいくものが多く、イベントの取扱方はとても重要です。

今回はこれで終わります。


2018年2月23日金曜日

はじめてのWindows Formアプリケーション

今回から、WindowsのFormアプリの学習に入ります。

 Formアプリケーションは、Windows上でウインドウを持つアプリケーションのことです。

昔から持っているイメージは、プログラミングで使う言語の習得よりも、Windowsで走るプログラムのお作法を勉強する方が大変、MFCクラスライブラリの仕様を勉強しなきゃいけなくて大変、っていうもの。

以前Visual C++を買った時には、その辺の敷居が高すぎて、どうにもこうにも学習する気にならなかったものでした。

Formアプリケーション


下記が学習で打ち込んだリストです。Formアプリケーションというと、Visual Studioでデザイナーを使ってウインドウの設計をして・・・・と、普通は考えると思います。

しかし、ウインドウを表示してその中に文字列を表示させる、という程度の動作であれば、デザイナーを使ってウインドウの設計をする、という作業は不要です。下記リストをテキストエディタで打ち込んで、sample.csというファイル名で保存し、Windowsのコマンドプロンプトから

csc sample.cs

と入力すればコンパイル可能で、そのままコマンドプロンプトからsample.exeと入力すればウインドウが現れます。

using System;
using System.Drawing;
using System.Windows.Forms;

namespace MyFrmApp
{
    public class MyForm : Form
    {

        class Program
        {
            [STAThread]
            private static void Main( string[] args )
            {
                Application.Run(new MyForm());
            }
        }

        public MyForm()
        {
            Label label = new Label();
            label.Text = "Welcome to C#!";
            label.Font = new Font("geneva", 18, FontStyle.Regular);
            label.Height = 30;
            label.Width = 300;
            this.Controls.Add(label);
            this.Width = 300;
            this.Height = 200;

        }
    }
}

起動させると、このようなウインドウが出ると思います。

なお、このソースは、Macではライブラリの違いからコンパイルできません。Windowsの.NET Framework環境固有のライブラリを使用しているからです。

さて、今回のプログラムですが、新しい単語が幾つか出てきています。


これらは、System.DrawingとSystem.Windows.Forms名前空間にあるクラスを参照するために記述しています。それぞれの名前空間にどんなものが定義してあるかは、それぞれに付いているリンク先に記述があります。

今回のプログラムで具体的に何をするために記述しているのかというと、System.DrawingはFontクラスを使うために記述しているようです。また、System.Windows.Formsは、Formクラス、Labelクラスを使用する為ですね。


  • [STAThread]


これは、これが書かれたメソッドがシングルスレッドで動くようにする為の属性指定です。.NET Frameworkで動くウインドウは、原則としてシングルスレッドで動くように設計されているとのこと。

この属性指定を行う事で、ウインドウの中で呼び出される処理は、全てウインドウが動いているスレッドの中で処理される、ということだそうです。

参考文献:初心者のためのC#プログラミング入門

このプログラムの仕掛けは、

  • MyFormクラスをFormクラスの派生クラスとして定義
  • MainメソッドでMyFormクラスのインスタンスを生成して、そのインスタンスを起動
  • MyFormクラスのインスタンスが生成されるときに、コンストラクタとしてpublic MyForm()が起動し、その中で
     Labelクラスのインスタンスを生成(名前:label)
     生成したlabelに
      Text属性
      Font属性(これは、Fontクラスのインスタンスを生成して当てる)
      Height属性
      Width属性
     を設定
  • このlabelオブジェクトを、MyFormのControl.Addメソッドを使ってMyFormの上に置く
  • MyFormのWidth属性、Height属性を設定

ということになります。

というわけで、今回はここまで。

2018年2月21日水曜日

パーシャル宣言の話

今回はPartial宣言の話です。

Partial宣言


クラスの宣言をするにあたって、分割した宣言をしたい場合があります。そのためににPartial宣言というものがあります。そのような宣言をされたクラスを部分クラスと呼びます。

クラスの開発を複数のプログラマが行う場合や、自動生成されたクラス宣言、たとえばVisual Studioが吐き出した宣言に対して、自動生成されたファイルに手を加えることなく機能の追加を行うような場合に有効な方法です。

/*
 * Partial宣言の学習
 * Partial宣言を外すと、クラス宣言の重複でエラーになるよ
 */
using System;

namespace sample11
{
    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person();
            p.name = "Gustav";
            p.age = 56;
            p.print();
            p.printAll();
            Console.ReadKey();
        }
    }

    public partial class Person  <-- Partial宣言
    //public  class Person
    {
        public string name;
        public void print()
        {
            Console.WriteLine("My name is " + name);
        }
    }

    public partial class Person  <-- Partial宣言
    //public  class Person
    {
        public int age;
        public void printAll()
        {
            Console.WriteLine("My name is " + name + ". I'm " + age + " old.");
        }
    }
}

Partial宣言は、分割して宣言するクラス宣言全てに必要です。Partial宣言を忘れると、クラス宣言の重複でエラーになります。

追記
C++では、クラスの分割宣言はできないそうです。ソースファイルを見てクラスの全貌がわからないのは不安、という考え方もあって、なかなか難しい問題ですね。


今回はこれで終わりです。

抽象メソッド、抽象クラス

継承に関係する事柄が続きます。今回は抽象メソッドと、抽象クラスです。

抽象メソッドは、実装を持たないメソッドで、機能の実装は派生クラスで行います。抽象クラスはそれ自体インスタンスを作ることが出来ず、継承を前提として作られたクラスです。

以下に学習の時に使ったリストを示します。

/*
 * 抽象クラスの学習
 * 試しに抽象クラスのoverrideをしないでコンパイルするとエラーになる。
 * 必ずoverride必要
 */
 
using System;

namespace sample10
{
    class Program
    {
        static void Main(string[] args)
        {
            Student p = new Student();
            p.name = "Gustav";
            p.grade = 2;
            p.printData();
            Employee e = new Employee();
            e.name = "MIE";
            e.position = "president";
            e.printData();
            Console.ReadKey();
        }
    }

    abstract class Person
    {
        public abstract void printData();
    }

    class Student : Person
    {
        public String name;
        public int grade;
        public override void printData()
        {
            Console.WriteLine("name: " + name + ", grade: " + grade + ".");
        }
    }

    class Employee : Person
    {
        public String name;
        public String position;
        public override void printData()
        {
            Console.WriteLine("Hi, my name is " + name + ". my position is " + position + ".");
        }
    }
}

abstruct class Personが抽象クラスです。その中のメンバーとして書かれているpublic abustruct void printData()が抽象メソッドになります。ここにはメソッドの実装はなく、抽象クラスPersonのメンバーとして抽象メソッドのprintDataがある、ということしか定義されていません。この内容では、メソッドの実装がないので、当然ながらインスタンスの生成はできません。

その下で、StudentクラスとEmployeeクラスが、Personクラスを基底クラスとした派生クラスとして定義されています。

Personクラスでは抽象メソッドとして書かれていたprintData()メソッドは、これらの派生クラス内で、オーバーライドするメソッドとして定義されています。

これを実行すると、

MacBook-Pro:sample10 gustav$ mcs Program.cs
MacBook-Pro:sample10 gustav$ mono Program.exe
name: Gustav, grade: 2.
Hi, my name is MIE. my position is president.

という結果になります。当然ながらStudentクラスではStudentクラスの、EmployeeクラスではEmployeeクラスのprintDataメソッドがそれぞれオーバーライドされています。

C#におけるコンストラクタとデストラクタの話

リストを入力して動かしてみる、という学習はまだまだ続きます。

コンストラクタ


今回はコンストラクタの話です。どうでも良い話ですが、最近はコンストラクターと長音(ー)を入れる書き方が普通なんでしょうか。僕は工学者出身なので、後ろの長音は省略する習慣が抜けません。

オブジェクトを生成する、つまりクラスのインスタンスを生成する際には、オブジェクトをキチンと初期化してあげなければいけません。そのために、new演算子でインスタンスを生成するときにだけ走る、特別なメソッドが用意されています。これをコンストラクタといいます。

コンストラクタは、クラスの名前と同じ名前で定義し、戻り値の型は書きません。コンストラクタは戻り値を持つことが許されません。コンストラクタの働きを考えると、戻り値を持つ意味がありませんね。

学習の際に打ち込んだリストは以下です。

/*
 * コンストラクターの学習
 */
using System;

namespace sample9
{
    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person("gustav", 123);
            p.print();
            Console.ReadKey();
        }
    }

    public class Person
    {
        public string name;
        public int age;
        public Person( String name, int age ) ←これがコンストラクタ
        {
            this.name = name;
            this.age = age;
        }

        public void print()
        {
            Console.WriteLine("My name is " + name + ". I'm " + age + " old.");
        }
    }
}

Personクラスの中の、

public Person( String name, int age )

がコンストラクタになります。このプログラムでは引数をクラスのメンバーである変数nameとageに代入しています。これを実行した結果が以下です。1行目でコンパイル、2行目で実行、3行目が出力結果です。

MacBook-Pro:sample9 gustav$ mcs Program.cs
MacBook-Pro:sample9 gustav$ mono Program.exe
My name is gustav. I'm 123 old.

コンストラクタは、オーバーライドすることができます。

/*
 * コンストラクターの学習2
 */
using System;

namespace sample9
{
    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person("gustav", 123);
            Person p2 = new Person();
            
            p.print();
            p2.print();
            Console.ReadKey();
        }
    }

    public class Person
    {
        public string name;
        public int age;
        public Person( String name, int age )
        {
            this.name = name;
            this.age = age;
        }

        public Person()
        {
            this.name = "{NONAME}";
            this.age = 1;
        }

        public void print()
        {
            Console.WriteLine("My name is " + name + ". I'm " + age + " old.");
        }
    }
}

Personクラスの中で、引数が二つある場合と引数がない場合のコンストラクタを定義しています。インスタンス生成で引数を与えたインスタンスと引数を与えなかったインスタンスで、それぞれ異なった初期値がメンバー変数に与えられていることがわかります。

MacBook-Pro:sample9-2 gustav$ mcs Program.cs
MacBook-Pro:sample9-2 gustav$ mono Program.exe
My name is gustav. I'm 123 old.
My name is {NONAME}. I'm 1 old.

デストラクタ


インスタンス生成で生成されたオブジェクトを初期化するためのメソッドがコンストラクタでしたが、オブジェクトが破棄されるときに呼び出されるメソッドというものもあります。それがデストラクタです。

メソッド名を、~クラス名として定義、引数は持てない、ということになっています。上の例に習うと、

~Person()

のような書き方です。

コンストラクタ、デストラクタとも、元々C++にある機能で、コンストラクタはC++と同じ考え方で扱えば良いのですが、C#のデストラクタは、C++のデストラクタとは少々勝手が違います。

C++では、クラスをdeleteした時点、もしくはクラスのスコープを抜けた時点でデストラクタが呼び出されますが、C#ではオブジェクトの寿命をプログラムで制御することが出来ず、ガベージコレクターがオブジェクトを破棄したときに初めてデストラクタが呼び出されます。

ということで、プログラムからの制御が出来ないということは、プログラムを書く側からするととても使いにくい、ということになるので、C#ではデストラクタはあまり使われないようです。

参考文献:++C++ ; // 未確認飛行C コンストラクターとデストラクター

今回はここまでです。


2018年2月20日火曜日

オーバーライド、仮想メソッド

今回は、継承の中で、同じ名前で機能を書き換える、オーバーライドのお話です。

オーバーライド


前回のエントリーで、既存のクラスに機能追加を行って、新たなクラスを作る継承について書きました。しかし、継承にはこれだけではなく、派生クラスに、基底クラスと同じ名前で異なったメソッドを持たせる事が出来る機能があります。これをオーバーライドと言います。基底クラスのメソッドを継承クラスで上書きしてしまうものです。オーバーライドするメソッドは、overrideというキーワードを付けて宣言します。

仮想メソッド


オーバーライドされる側のメソッドを仮想メソッドと呼んでいます。メソッドの定義でvirtualというキーワードを付けて宣言します。

下に、学習したときに入力したプログラムリストを示します。

using System;

namespace sample7
{
    class Program
    {
        static void Main(string[] args)
        {
            EnhancedMyObject obj = new EnhancedMyObject();
            obj.name = "Gustav";
            obj.age = 56;
            obj.mail = "mail@truth-maker.com";
            obj.printData();
            Console.ReadKey();
        }
    }

    class MyObject
    {
        public string name = "(noname)";
        public int age = 0;
        public virtual void printData()
        {
            Console.WriteLine("名前 : {0}, 年齢 : {1}歳",name, age);
        }

    }

    class EnhancedMyObject : MyObject
    {
        public string mail = "(no mail)";

        public override void printData()
        {
            Console.WriteLine("名前 : {0}, 年齢 : {1}歳, メール : {2}.", name, age, mail);
        }
    }
}

MyObjectクラスの中で、キーワードvirtual付きで定義されているメソッドが仮想メソッドです。仮想という名前は付いていますが、オーバーライド対象がなければ、このメソッドが実行されます。

EnhancedMyObjectで定義されている、キーワードoverride付きで定義されているメソッドがオーバーライドメソッドです。EnhancedMyObjectクラスはMyObjectクラスの派生クラスですが、printData()メソッドはEnhancedMyObjectクラスで定義されているprintData()メソッドに上書きされてしまいます。

newによるオーバーライド


継承によるオーバーライド、キーワードoverrideによるオーバーライドの他に、キーワードnewによるオーバーライドというものがあります。

using System;

namespace sample8
{
    class Program
    {
        static void Main(string[] args)
        {
            MyObject obj = new EnhancedMyObject();
            obj.name = "Gustav";
            obj.age = 56;
            obj.printData();

            EnhancedMyObject obj2 = (EnhancedMyObject)obj;
            obj2.mail = "mail@truth-maker.com";
            obj2.printData();
            Console.ReadKey();
        }
    }

    class MyObject
    {
        public string name = "(noname)";
        public int age = 0;
        public virtual void printData()
        {
            Console.WriteLine("名前 : {0}, 年齢 : {1}歳",name, age);
        }
    }

    class EnhancedMyObject : MyObject
    {
        public string mail = "(no mail)";
        public new void printData()
        {
            Console.WriteLine("名前 : {0}\n\t 年齢 : {1}歳\n\t メール : {2}.", name, age, mail);
        }
    }
}

上のリストにおいて、基底クラスのメソッドprintData()を派生クラスのprintData()でオーバーライドするのですが、

public new void printData()

という宣言になっています。これを実行すると

MacBook-Pro:sample8 gustav$ mcs Program.cs
MacBook-Pro:sample8 gustav$ mono Program.exe
名前 : Gustav, 年齢 : 56歳
名前 : Gustav
         年齢 : 56歳
         メール : mail@truth-maker.com


となります。

まず、

            MyObject obj = new EnhancedMyObject();

において、MyObject型の変数objを宣言して、EnhancedMyObject型のオブジェクトのインスタンスを生成、その参照をobjに代入します。そのオブジェクトのprintData()メソッドで出力したのが、最初の出力行です。これは、基底クラスで定義しているメソッドの動作です。

次に、

            EnhancedMyObject obj2 = (EnhancedMyObject)obj;

で、EnhancedMyObject型の変数obj2を宣言し、最初にnewで生成したオブジェクトへの参照を保持している変数objをEnhancedMyObject型にキャストしてobj2に代入しています。これは、型の変換はあるものの、同じオブジェクト(最初に生成したEnhancedMyObject型のオブジェクト)への参照です。

しかし、変数obj2が示すオブジェクトのprintData()メソッドで出力したものは、最初の出力と異なり、派生クラスでnew付きで定義したメソッドの動作です。

同じオブジェクトへの参照ですが、変数の型によって動くメソッドが変わっているのです。もしも派生クラスのメソッドがnewではなくてoverrideで定義されていると、出力結果は両方とも同じ、派生クラスで定義されたメソッドの動作になります。

このように、overrideでは、オーバーライドされるメソッドが完全に書き換えられてしまいますが、newでは基底クラスのメソッドも失われることなく残り、同じインスタンスであっても、参照のしかたで使われるメソッドを使い分けることができます。

今回はここまでです。

クラス、そして継承の話

今回はクラスの話になります。

C#はC++とJavaを源流とする言語なんだそうです。ということは、オブジェクト指向であり、クラスという概念を避けて通れません。特にC#では、前回のエントリーにも書いたように、全ての手続きはクラスに属さなければならない決まりになっているので、プログラムを書く=クラスを定義する、ということになります。マイクロソフトのこのドキュメントの二つ目のパラグラフに書いてありますね。

クラス


次のリストは、私がクラスの定義の学習をしたときに使ったプログラムです。

using System;

namespace sample5
{
    class Program
    {
        static void Main(string[] args)
        {
            MyObject obj = new MyObject();
            obj.name = "Gustav";
            obj.age = 56;
            obj.printData();
            Console.ReadKey();
        }
    }

    class MyObject
    {
        public string name = "(noname)";
        public int age = 0;
        public void printData()
        {
            Console.WriteLine("名前 : {0}, 年齢 : {1}歳",name, age);
        }
    }
}
この中では、sample5という名前空間(クラスを整理する為の棚のようなもの)の中に、ProgramというクラスとMyObjectというクラスが定義されています。ProgramクラスにはMainメソッドがありますね。OS上の各種セットアップ処理が終わった後に、プログラム本体の入口としてここに制御が移ります。

下にあるMyObjectクラスの定義では、メンバーの変数やメソッドの定義がなされます。ここで、各メンバーにはpublic宣言がなされているので、MyObjectクラスの外からこれらメンバーを読んだり書いたり呼び出したりできますが、private宣言というものもあり、private宣言がなされたメンバーは、クラスの外からはアクセスできません。

Mainメソッドの1行目で、

MyObject obj = new MyObject();

という記述がされています。

実は、C#ではstatic宣言がされているクラスを除いて、クラスはnew演算子を使って実態を作る(インスタンスを作る)操作をしないと、使うことが出来ません。クラスの宣言は文字通り宣言だけで、実態を作ることで初めて使うことができる、ということになっています。

C++では、new演算子を使うインスタンス生成と使わないインスタンス生成がありますが、C#では、クラスのインスタンス生成には必ずnew演算子を使う決まりになっています。

なお、C++におけるインスタンス生成で、new 演算子を使った場合はオブジェクトがヒープ領域に、使わない場合はスタック領域に生成され、new演算を使わない場合は、スコープから抜けるとオブジェクトは開放されてしまいます。

クラスの継承


既存のクラスの性質を受け継いで、新たな機能を追加したクラスを作ることを継承と言います。派生と言うこともあるようです。このサイト様もよく参考にさせていただいていますけど、ここに詳しい説明があります。

この概念、個人で小規模なプログラミングをやってる人間にはあまりありがたみが分からないのですが、チームで仕様書ベースで開発するようなケースでは有用なのでしょう。また、あらかじめ用意されている、たとえば.NETの環境で、ライブラリーを継承して新たにクラスを作る、なんてときには嬉しい機能なのだと今回の学習で思いました。

なお、継承元のクラスを基底クラス、又はスーパークラス、継承を受ける側を派生クラス、又はサブクラスと呼びます。

次のリストでは、クラスの継承をしています。MyObjectクラスの定義があり、その次にEnchancedMyObjectクラスの定義がありますが、

class EnhancedMyObject : MyObject

の、": MyObject"の記述が、MyObjectクラスを継承することを示しています。

using System;

namespace sample6
{
    class Program
    {
        static void Main(string[] args)
        {
            EnhancedMyObject obj = new EnhancedMyObject();
            obj.name = "Gustav";
            obj.age = 56;
            obj.mail = "mail@truth-maker.com";
            obj.printData();
            obj.printDataEnchanced();
            Console.ReadKey();
        }
    }

    class MyObject
    {
        public string name = "(noname)";
        public int age = 0;
        public void printData()
        {
            Console.WriteLine("名前 : {0}, 年齢 : {1}歳",name, age);
        }
    }

    class EnhancedMyObject : MyObject 
    {
        public string mail = "(no mail)";

        public void printDataEnchanced()
        {
            Console.WriteLine("名前 : {0}\n\t 年齢 : {1}歳\n\t メール : {2}.", name, age, mail );
        }
    }   
}   

Mainメソッドの中で、EnhancedMyObject型のオブジェクトをnew演算子で生成しています。そして、obj.printData()メソッドとobj.printDataEnhanced()メソッドを呼び出しています。

obj.printData()メソッドは、基底クラスに存在しているメソッドなので、派生クラスにも引き継がれ、派生クラスでも使うことが出来ます。プログラムを実行すると、下記のような結果になります。

1行目でソースのコンパイル、2行目で実行させています。

MacBook-Pro:sample6 gustav$ mcs Program.cs
MacBook-Pro:sample6 gustav$ mono Program.exe
名前 : Gustav, 年齢 : 56歳
名前 : Gustav
         年齢 : 56歳
         メール : mail@truth-maker.com.

まず最初に基底クラスから引き継がれたのprintData()メソッドが実行され、次に派生クラスで追加したprintDataEnhanced()メソッドが実行されていますね。

今回はクラスと継承のお話でした。今回はここまで。


2018年2月19日月曜日

mainメソッドの引数 ん?そもそもmainメソッドって何?

今回はmainの引数の話を書きます。

mainの引数


C言語の時代からmainという関数はプログラムの最初に走る関数である、という位置づけの関数でした。その引数は、プログラムを起動する際にコマンドラインから入力されるパラメーターをプログラムに渡すためのものでした。

これはC#でも変わらないのですが、C/C++とC#では書き方が変わっています。

C/C++の場合

int main( argc, argv )
int argc;
char *argv[];
{
・・・
}
もしくは、
int main( int argc, char *argv[] )
{
・・・
}

ここで、argcには引数の数が入り、argv[]は引数が単語に分割されて入っている配列となります。ちなみにargv[]は、正確には文字列へのポインタの配列です。


これに対して、C#では
int main( string[]  args )
{
・・・
}
という書き方になります。argsはstring型の配列です。また、argcはありませんね。C#では、配列には配列要素の数を返すLengthというプロパティがあって、これを参照すると配列に要素がいつく入っているのか簡単に知ることが出来ます。argsの場合だとargs.Lengthです。

したがって、argcがなくても全然困りません。

using System;

namespace argvtest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("args.Length=" + args.Length );
        }
    }
}

このようなテストコードを書いて確かめてみると、args.Lengthで引数の数が取得できているのが分かります。(下記にて、mcsはC#コンパイラーです)

MacBook-Pro:argvtest gustav$ mcs Program.cs 
MacBook-Pro:argvtest gustav$ mono Program.exe
args.Length=0
MacBook-Pro:argvtest gustav$ mono Program.exe aaa
args.Length=1
MacBook-Pro:argvtest gustav$ mono Program.exe aaa bbb
args.Length=2
MacBook-Pro:argvtest gustav$ mono Program.exe aaa bbb ccc
args.Length=3
MacBook-Pro:argvtest gustav$ mono Program.exe aaa bbb ccc ddd
args.Length=4
MacBook-Pro:argvtest gustav$ 

なお、Cで同じような記述をしてみると、以下のようになります。
#include  <stdio.h>

int main( int argc, char *argv[] )
{
    printf( "argc=%d\n", argc );
}

これの実行結果は以下の通りです。

MacBook-Pro:argc-test gustav$ cc -o argc-test argc-test.c
MacBook-Pro:argc-test gustav$ ls
argc-test       argc-test.c
MacBook-Pro:argc-test gustav$ ./argc-test
argc=1
MacBook-Pro:argc-test gustav$ ./argc-test aaa
argc=2
MacBook-Pro:argc-test gustav$ ./argc-test aaa bbb
argc=3
MacBook-Pro:argc-test gustav$ ./argc-test aaa bbb ccc
argc=4
MacBook-Pro:argc-test gustav$ ./argc-test aaa bbb ccc ddd
argc=5

Cでは、自分自身も数に入れて数えるので、C#のプログラムに対して1大きな数になっています。

というわけで、C#ではmainの引数の書き方が変わりました。

mainメソッドって何?


C言語の時代にはクラスというものが言語仕様になかったので、ユーザーが書いたプログラムの中で一番最初に実行される関数がmain関数というのがmainの位置づけでした。mainは関数でした。

C++の時代になっても、mainは相変わらず関数として記述されます。その辺の事情はこの記事に色々と書いてあります。どのクラスにも属さないグローバル関数ということのようです。

C#では、マイクロソフトのドキュメントによれば、一連の手続き(メソッド)は必ずクラスの中に記述されるとのことなので、その考え方に基づくとmainもクラスの中で定義されているメソッドということになります。昔読んだC++の教本にあった表現だと、クラスのメンバー関数、とも言えるのでしょうか。

そういうことで、mainメソッドって何?という問いに対しては、C#ではC/C++のようなグローバル関数を持つことが出来ず、クラスのメソッドとして定義するから、mainメソッドなんだよ、というのが答えです。

今日は、mainの引数のお話と、mainメソッドについてのお話でした。今日はこの辺で。

2018年2月18日日曜日

C#におけるswitch文の話

このブログは、自分が学習したことを忘れにくくするのが目的なので、C#を系統立てて説明したりするつもりはあまりありません。というか、そんな大それた事できません。

そういう事情から、前後のエントリーとの関連性や連続性はほとんどありません。

というわけで、今回のテーマはswitch文です。条件分岐の条件が多岐にわたる場合、if-elseで書くよりもswitchで書いた方がスッキリとした記述になりますね。

switch


まず、下記のリストをご覧下さい。

using System;

namespace sample3
{
    class Program
    {
        static void Main(string[] args)
        {
            string s = "hello";

            switch (s)
            {
                case "ok":
                    Console.WriteLine("OK!");
                    break;
                case "hello":
                    Console.WriteLine("Hi");
                    break;
                case "bye":
                    Console.WriteLine("good bye...");
                    break;
                default:
                    Console.WriteLine("???");
                    break;
            }
        }
    }
}

swich(s)から後ろがswitch文に当たります。このswitch文はCやC++にもあります。しかし、このリストのswitch文はCやC++では間違いです。コンパイルエラーになります。なぜなら、switch(式)で評価される式は、CやC++では整数型変数でなければならないからです。

しかし、C#ではこのswitchの文法が拡張されていて、整数型以外に文字列型を扱えるようになりました。

ですから、C# では正常にコンパイルできますし、書かれている印象どおりの動作をします。

上記リストだと、string型の変数sに"hello"を代入してからswitchで評価しているので、case "hello"のラベルにある処理が実行され、コンソールに"Hi"と表示されます。

ところで、Cでは、case文の後の処理の終わりにbreakを書かないことが許されていて、書かない場合は次のcase文の処理が実行されることになっていました。これをフォールスルーというそうです。C#ではこのフォールスルーが禁止され、breakを書き忘れるとコンパイルエラーになります。

フォールスルーさせたい場合は、goto case〜と、明示的にジャンプさせなければなりません。

フォールスルーは便利ではありますが、バグの原因にもなります。何度もやらかした経験があります。

なお、switch(式)の式が整数型でなければならないC/C++でも、enum(列挙型)を使って、プログラムを読みやすくする、という手法があるそうです。このサイトを読んで知りました。

    typedef enum date  {
        sunday,
        monday,
        tuesday,
        wednesday,
        thursday,
        friday,
        saturday
    } DATE;

    DATE day = monday;

    switch (day)
    {
        case sunday :
        case saturday :
            printf( "holiday\n" );
            break;
        case monday :
            printf( "swimming\n" );
            break;
        case tuesday :
        case thursday :
            printf( "play with friend\n" );
            break;
        case wednesday :
            printf( "piano lesson\n" );
            break;
        case friday :
            printf( "football class\n" );
        default :
            printf( "illegal data\n");
    }
}

たしかに直感的に分かるようになりますね。これをmainに組み込んで実行すると、mondayに対応するswimmingが表示されます。

それでは今回はこれで終わりです。

タイトルを若干変えました。switchと言っても、某ゲーム機の話と勘違いされちゃうと申し訳ないので・・・



2018年2月17日土曜日

Hello Worldの後の最初の一歩 1行出力と1行入力

モットーは習うより慣れろ 言い古されたセリフだけど


新しい言語の学習は、とにかく参考書に書いてあるプログラムをコンピューターに入力して、動かしてみることだと僕は思っています。とにかく動かしてみるんです。その言語に触れて、ひたすら打ち込んで動かしてみるんです。

ある程度慣れたら、そこでプログラムリストの意味の理解に努めれば、より理解が深まるのではないかと思います。

最初は全くわからない状態でも、何か高級言語を勉強した経験があれば、しばらくやっていると何となく分かってきます。

というわけで、僕も最初はひたすら教材のリストを入力しては動かす、ということの繰り返しでした。

Hello Worldの後の最初の一歩


最初のエントリーで、Hello Worldと出力するコードをC#で書いたらどうなるかを書きました。これはC#で1行出力をする最も基本的なプログラムです。

次は、1行入力の機能を付け加えてみます。

using System;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("\nWhat is your name? ");
            var name = Console.ReadLine();
            var date = DateTime.Now;
            Console.WriteLine($"\nHello, {name}, on {date:d} at {date:t}!");
            Console.Write("\nPress any key to exit...");
            Console.ReadKey(true);

        }
    }
}
(引用:Visual Studio 2017 での .NET Core を使用した C# Hello World アプリケーションの構築/マイクロソフト)

これを実行すると、

What is your name? 
gustav   <-- キーボードから入力

Hello, gustav, on 2018/02/16 at 20:18!

Press any key to exit...

という動作になります。

この中で出てくるConsole.WriteLine()と、Console.ReadLine()が今回のテーマです。

Console.WriteLine()


標準出力対してテキストを1行出力します。プログラムリストの

Console.WriteLIne( "\nWhat is your name? " );

では、カッコの中にある"\nWhat is your name? "を出力しています。文字列を1行出力した後に、自動的に改行を入れる、という働きもあります。

 Console.WriteLine($"\nHello, {name}, on {date:d} at {date:t}!");

の行については、$(ドル記号)は、書式指定子の一つで、上記の例だと、{name}、{date:d}、{date:t}を該当する変数の中身で置き換えて出力する、というものだそうです。

こちらに詳しい事が書いてありますので、興味があれば参照下さい。また、このメソッドの詳しい説明は、MSDN(Microsoft Developer Network)のこのページにあります。

Console.ReadLine()


標準入力からテキストを1行入力します。戻り値の型はString型です。なお、このメソッドで読み込める最大の文字数は254文字です。それを上回る大きな文字数の行を読み込む必要があるときは、OpenStandardInput(Int32)メソッドで1行バッファの大きさを変更することが出来ます。

このメソッドの詳細な説明はMSDNのこのページにあります。

今回出て来たConsole.WriteLine()とConsole.ReadLine()は、標準入出力を司るConsoleというクラスの中のメソッドとなります。MSDNのこのページにConsoleクラスの詳しい説明があります。

行入力と行出力は入出力の基本ですね。今日はこの辺で。


2018年2月15日木曜日

C#の学習では、環境、教材はこれを使いました

まずは開発環境

何はなくても、まずは学習する言語の処理系、つまりコンパイラもしくはインタープリタを入手しなければなりません。

C#の処理系として一番ポピュラーなのは、マイクロソフトが出しているVisual Studioだと思います。今回は、2種類出ている無償版の中からVisual Studio Communityを入手しました。

実は、Macで動くVisual Studioもあります。もちろんその中でC#も使えます。コンソールベースのアプリケーションを書くのであれば、Windows用とコードの互換性が高いようです。

コンソールベースと言えば、Visual Studio Codeという、Visual Studioよりも軽い開発用のエディタがあり、.NET coreと呼ばれる環境で動くコンソールベースのアプリケーションを書いてビルドすることができるので、ちょっとしたコードテストにはこちらの方が使いやすいです。これはWindows、Mac、Linuxとマルチプラットフォームな環境になっています。

僕の使った教材のご紹介


新しい言語を勉強するとなれば、今はインターネットでかなりの情報を入手できるとは言え、何か1冊入門書を買っておいた方が良いだろうと言うことで、

やさしいC# 第2版 SBクリエイティブ株式会社

という本を入手しました。特に何かこだわりがあったわけではなく、たまたま立ち寄った本屋さんで買ったものです。

また、インターネットで読んだC#の入門書の電子書籍化されたものも入手して参考にしました。

C#ビギナーズガイド: Windowsをプログラミングせよ! PRIMERシリーズ (libroブックス)

電子書籍はKindle版です。

これは、とてもわかりやすい内容になっています。 本は、よほどヒドイ内容でなければなんでも良いと思うんです。最後まで読み切って吸収することが大事だと思います。

本日はここまでです。次回から、言語の学習内容を記載します。

なぜやることになったのか、のご挨拶からの、Hello World

まずはご挨拶

はじまりは・・・


とある半導体メーカーを49歳で退職してから、しばらくの間子育てと家事を引き受けて数年。

ちょっとしたつながりで、とある国立大学の研究プロジェクトの一環としてFORTRANのソースコードの改変が必要ということで、その改変をアルバイトを頼まれて、なんとなく引き受け、NIの開発プラットフォーム、LabVIEWを使った小規模なソフトウエアコーディングを頼まれて引き受け・・・

これらの案件は、持っている知識と経験で乗り越えることができました。

ただし、FORTRANの改変のお仕事に関しては、変更点は3箇所ほど、数値を書き換えるだけでしたが、その改変が他の部分に悪さをして計算結果がおかしくならないかどうかの検証方法がわからず、エンドユーザーさんもわからず、随分辛い思いをしたものでした。

なぜC#なのか


2017年12月、久しぶりに開発の打診を頂いた案件は、一筋縄ではいかなそうな案件でした。内容は、空間で測定した磁界の強さを3次元的にプロットできないか、というもの。

「開発言語はC#で」、とのご指定。

これが、なぜC#か?という問いの答えなのです。しかしC#は触ったことがない、という現実が・・・

1980年代後半、パソコンブームに乗って8ビットパソコンを購入、BASICとMC6809のアセンブラ、そして整数型のCコンパイラでソフトを書くことを趣味としていたので、Cはそれなりに知ってますが、その後awkやperlを仕事を楽にする為に勉強したもののC++には乗り遅れ、それ以来新しい言語には触れていないのです。

しかしながら、C#は使ったことがないからNGです、と即答するのもシャクなので、出来そうかどうか、少し時間を頂いて検討させていただけないでしょうか?と打診を下さった方にお願いし、C#の短期集中学習を開始したのでした。2018年1月の中旬のことでした。

結局僕がマゴマゴと検討に時間を要している間に、打診を下さった方の会社の事業判断として、そのお話はエンドユーザーさんにはお断りを入れて、お仕事はなくなったのですが、久しぶりの新しい言語の学習ですから、何をやったのかはどこかに記録しておきたいと思い、このサイトを始めたというわけです。

あまり役に立たない記事かもしれませんが、ある程度歳を取ってからC#を初めてみようかな、という方がいらっしゃったら、少しは参考になるかもしれません。

さて、Hello World


プログラミング言語の入門書の一番最初に出てくる例題、それは"Hello World"を出力することと、まるで世の中の決まり事のようになっています。

これはカーニハン&リッチーの「プログラミング言語C」からなんでしょうか。若い頃から、もう少しマシなセリフはないのかね、と思いながら色んな本を読んでいたものです。中学の英語の教科書に出てくる"This is a pen."よりはマシかもしれませんけど。

Wikipedaを紐解くと、C言語に関しては、やはり1978年の「プログラミング言語C」からと言われている、と書いてあります。

この本の第1章「やさしい入門」の最初の方に、Cでの書き方が出て来ますね。

main()
{
    printf( "hello, world¥n" );
}

これをC#で書くとどうなるかというのが、やはり入門書の一番最初に出て来ます。マイクロソフトのC#プログラミングガイドに出てくる「最初のプログラム」は、以下のような感じです。

using System;
namespace HelloWorld
{
    class Hello
    {
        static void Main()
        {
            Console.WriteLine("Hello World!");
            // 画面が消えてしまわないように、キーボード入力待ちを入れる
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

Cの記述ですが、この頃のCはまだ高級マクロアセンブラのようなもので、各種整合性のチェックはそれほどうるさくなかったようです。今だと、mainに型を定義してあげないといけないとか、プリプロセッサでstdio.hを読んで型のチェックをさせないと怒られそうですが、当時はこれでもコンパイルは通っていたんだと思います。現代のCコンパイラーにそのまま通すと、僕の環境では

MacBook-Pro:~ gustav$ gcc test.c
test.c:1:1: warning: type specifier missing, defaults to 'int' [-Wimplicit-int]
main()
^
test.c:3:1: warning: implicitly declaring library function 'printf' with type
      'int (const char *, ...)' [-Wimplicit-function-declaration]
printf( "hello, world\n" );
^
test.c:3:1: note: include the header  or explicitly provide a
      declaration for 'printf'
2 warnings generated.

と、騒々しいワーニングメッセージを出してくれますね。あくまでもワーニングなので、オブジェクトは出力してくれてはいます。

C#の記述は、今のところはこんなもんだということで。ただ、Cとの大きな違いは、Cではmainは関数として定義されるのに対して、C#ではHelloクラスのmainメソッドとして定義されます。

クラスというのはCにはない概念で、僕がC++の理解を諦めた大きな要因です。何が嬉しいのか今も全然理解出ていません。

言ってみれば、変数やプログラムを一つにまとめたものをクラスと言うんだそうですが、そのクラスに含まれるプログラム部分のことをメソッドと呼んでいるのです。マイクロソフトのC#プログラミングガイドでの記載はこちら

と言うわけで、第1回目はこんなところでおしまいにします。