VB6開発者向け:C#で始める.NETプログラミング

第7回オブジェクト指向プログラミング(2)

はじめに

今回は、前回に引き続いて.NETプログラミングの基本とも言えるオブジェクト指向プログラミングについてをご説明していきたいと思います。

イベントを発生させるクラスを作る

以下は、前項のRowクラスから継承されているNotifyPropertyChangedクラスのコードです。

using System.ComponentModel;
namespace WindowsFormsApplication1
{
    //*************************************************************
    /// <summary>値が変わったことを通知するクラスです</summary>
    //*************************************************************
    class NotifyPropertyChanged : INotifyPropertyChanged
    {
        //*************************************************************
        /// <summary>PropertyChanged イベント</summary>
        //*************************************************************
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName) {
            if (this.PropertyChanged == null) return;

            object sender = this;

            PropertyChangedEventArgs e 
                = new PropertyChangedEventArgs(propertyName);

            this.PropertyChanged(sender, e);
        }       
    }
}

インターフェイスの継承

このクラスは、INotifyPropertyChangedインターフェイスを継承しています。 INotifyPropertyChangedは、.NET Frameworkで用意されているインターフェイスでSystem.ComponentModel名前空間に属しています。

インターフェイスとは、現段階では「実装を持たないメンバが定義されているクラス」と理解していただければよいでしょう。 実装を持たないメンバを持つクラスを継承するのですから、当然のことながらインターフェイスを継承したクラスがその実装を持たなければなりません。 このクラスの場合は、以下が実装部分になります。

public event PropertyChangedEventHandler PropertyChanged;

なお、PropertyChangedEventHandlerもSystem.ComponentModel名前空間に属する、あらかじめ用意されているイベントハンドラです。

イベント発生メソッド

以下は、イベントを発生させるためのメソッドです。 最初の引数には自分自身のオブジェクトを、次の引数はPropertyChangedEventArgsオブジェクトを生成して値を渡しています。

protected virtual void OnPropertyChanged(string propertyName) {
    if (this.PropertyChanged == null) return;

    object sender = this;

    PropertyChangedEventArgs e 
        = new PropertyChangedEventArgs(propertyName);

    this.PropertyChanged(sender, e);
}

このメソッドでは、PropertyChangedイベントハンドラがnull(空の状態)でなければ、PropertyChangedイベントを発行しています。

protectedアクセス修飾子は、このクラスを継承したクラスのみに公開されることを示します。 例えば、前項のRowクラスはNotifyPropertyChangedから派生していたためOnPropertyChangedメソッドを呼び出すことができました。 しかしNotifyPropertyChangedクラスを継承していないクラスでは、OnPropertyChangedメソッドを呼び出すことはできません。

virtualキーワードは、このメソッドが継承したクラスによってoverrideすることを許可するという意味です。 overrideによって、元のメソッドを新しいメソッドで上書きできます。

例えば、以下はOnPropertyChangedメソッドをoverrideすることでpropertyNameの値が "" 以外の時だけ継承元クラスのOnPropertyChangedメソッドを実行するためのサンプルです。

class OverrideSample : NotifyPropertyChanged {
    protected override void OnPropertyChanged(string propertyName) {
        if (propertyName == "") return;
        base.OnPropertyChanged(propertyName);
    }
}

これによってOverrideSampleオブジェクトでOnPropertyChangeメソッドを実行すると上記の処理が実行されます。

ロジックのクラスを作る

次に、本ソフトウェアの本体であるLogicという名前のクラスを作ってみましょう。

すでに、データアクセスを受け持つAdapterクラスや、データを格納するRowクラス、プロパティが変わったらイベントを発生させるNotifyPropertyChangedクラスが存在しているため、これらを利用して作っていきます。

using System;
namespace WindowsFormsApplication1
{
    //*************************************************************
    /// <summary>電話帳サンプルコンポーネント</summary>
    //*************************************************************
    class Logic : NotifyPropertyChanged
    {
        private Adapter adapter;

        //*************************************************************
        /// <summary>コンストラクタ</summary>
        //*************************************************************
        public Logic() {
            this.adapter = new Adapter();
        }

        //*************************************************************
        /// <summary>Code プロパティ</summary>
        //*************************************************************
        private int _Code = 0;
        public int Code {
            get { return this._Code; }
            set {
                if (this._Code == value) return;
                this._Code = value;
                base.OnPropertyChanged("Code");
            }
        }

        //*************************************************************
        ///<summary>Row プロパティ</summary>
        //*************************************************************
        private Row _Row = new Row();
        public Row Row {
            get { return this._Row; }
            private set { this._Row = value; }
        }

        //*************************************************************
        /// <summary>追加処理を実行します</summary>
        //*************************************************************
        public void Add() {
            this.InvalidCode();
            if (this.adapter.Read(this.Code, this.Row)) {
                throw new Exception("既に登録されている番号です");
            }
            this.adapter.Add(this.Code);
            this.Read();
        }

        //*************************************************************
        /// <summary>更新処理を実行します</summary>
        //*************************************************************
        public void Update() {
            this.InvalidCode();
            this.adapter.Update(this.Row);
        }

        //*************************************************************
        /// <summary>削除処理を実行します</summary>
        //*************************************************************
        public void Delete() {
            this.InvalidCode();
            this.adapter.Delete(this.Code);

            this.Code = 0;
            this.Row.Clear();
        }

        //*************************************************************
        /// <summary>読込み処理を実行します</summary>
        //*************************************************************
        public void Read() {
            if (!this.adapter.Read(this.Code, this.Row)) {
                this.Row.Clear();
                throw new Exception("番号が見つかりません");
            }
        }

        //*************************************************************
        /// <summary>番号値が有効かどうかを検証した結果を取得します
        /// </summary>
        //*************************************************************
        private void InvalidCode() {
            if (this.Code > 0 || this.Code < 100000000) return;
            throw new Exception("番号が有効な値ではありません");
        }
    }
}

クラスの概要

このクラスの持つインターフェイスは、以下のようになります。

イベント
OnPropertyChanged(継承元クラスで実装)
プロパティ
Code、Row(取得のみで設定は不可)
メソッド
Add、Update、Delete、Read

つまりはCodeにセットされた値に対して、Add、Update、Delete、Readすることができます。

Readによって得られた値はRowプロパティによってRowオブジェクトとして提供されます。 また、起動直後やDeleteメソッドによってRowの値が変更された場合は、Codeプロパティに値をセットします。 Codeプロパティの値が変更されることでOnPropertyChangedイベントが発行されます。

例外エラー

前回移植したC#のコードでは、ユーザーが何らかの違反行為を行った時や、例外エラーが発生した時点でエラーメッセージを表示していました。 しかし、エラーメッセージの表示は、ユーザーインターフェイスを受け持つためのクラスが行うべきことになります。

このクラスでは、エラーメッセージを表示させる代わりに、Exceptionオブジェクトを生成しthrowすることで例外エラーを発生させています。

ユーザーインターフェイスのクラスを作る

Form1クラスは、プロジェクトの作成と共に自動的に作られています。 このクラスでは、ユーザーの操作に関する処理を受け持ちます。

今までに作成したクラスを使って、以下のように書くことができます。

using System;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
    //*************************************************************
    /// <summary>電話帳サンプルソフトフォーム</summary>
    //*************************************************************
    public partial class Form1 : Form 
    {
        private Logic logic;

        //*************************************************************
        ///<summary>コンストラクタ</summary>
        //*************************************************************
        public Form1() {
            this.InitializeComponent();
            this.logic = new Logic();

            this.codeTextBox.DataBindings.Add(
                "Text", this.logic, "Code");
            this.nameTextBox.DataBindings.Add(
                "Text", this.logic.Row, "Name");
            this.telephoneNumberTextBox.DataBindings.Add(
                "Text", this.logic.Row, "TelephoneNumber");
        }

        //*************************************************************
        // イベント処理用メソッド
        //*************************************************************
        private void addButton_Click(object sender, EventArgs e) {
            try {
                this.logic.Add();
            }
            catch(Exception ex) {
                MessageBox.Show(ex.Message);
            }
        }

        private void closeButton_Click(object sender, EventArgs e) {
            this.Close();
        }

        private void readButton_Click(object sender, EventArgs e) {
            try {
                this.logic.Read();
            }
            catch(Exception ex) {
                MessageBox.Show(ex.Message);
            }
        }

        private void updateButton_Click(object sender, EventArgs e) {
            try {
                this.logic.Update();
            }
            catch(Exception ex) {
                MessageBox.Show(ex.Message);
            }
        }

        private void deleteButton_Click(object sender, EventArgs e) {
            try {
                this.logic.Delete();
            }
            catch(Exception ex) {
                MessageBox.Show(ex.Message);
            }
        }
    }
}

上記のコードを眺めることで、このクラスがLogicオブジェクトのフロントエンドな部分を受け持つためのだけに存在していることが分かるかと思います。

データバインディング

前回移植したコードでは、必要に応じて値を取得して、その都度TextBox.Textに値を格納したり、逆にTextBox.Textから値を取得していました。

しかし、今回作成したコードでは、例えばnameTextBox.Textの値は、常にRowオブジェクトのNameプロパティの値と同じです。 codeTextBoxもtelephoneNumberTextBoxも同様に、対応するオブジェクトプロパティが存在します。

つまり、各TextBoxオブジェクトのTextプロパティは、RowオブジェクトやLocigオブジェクトの対応するプロパティ値と常に同じ状態を保つことができればよいのです。

コンストラクタにある以下のコードが、この処理を実現しています。

this.codeTextBox.DataBindings.Add(
    "Text", this.logic, "Code");
this.nameTextBox.DataBindings.Add(
    "Text", this.logic.Row, "Name");
this.telephoneNumberTextBox.DataBindings.Add(
    "Text", this.logic.Row, "TelephoneNumber");

このコードを見て、賢明な方は疑問を持ち、さらに賢明な方は既に前項で説明した内容によって納得していただけるかと思います。

詳細についてcodeTextBoxを例にとって順にご説明します。

【質問】

codeTextBoxにオブジェクトをバインディングしています。 従って、自身のオブジェクトのTextプロパティの値が変わったタイミングで、新しい値をlogicオブジェクトのCodeプロパティに格納することは簡単にできるでしょう。

しかし、logicオブジェクトのCodeプロパティの値が変わってもcodeTextBoxオブジェクトが知ることができなければ、常に同じ値に保つことはできません。どうすればよいのでしょうか?

【回答】

バインディングしたオブジェクトは、すべてINotifyPropertyChangedインターフェイスを継承し、プロパティの値が変わるとPropertyChangedイベントが発生します。

このイベントによってcodeTextBoxは、logicオブジェクトのCodeプロパティの値が変わったことを知り、自身のTextプロパティの値を設定しなおしています。

つまり、このためにINotifyPropertyChangedインターフェイスを継承してPropertyChangedイベントを発生させていたという訳です。

コマンドの実行

以下は、追加ボタンが押された時に実行されるコードです。

try {
    this.logic.Add();
}
catch(Exception ex) {
    MessageBox.Show(ex.Message);
}

これによって、logicオブジェクトのAddメソッドを実行させた時に例外エラーが発生した時は、catchブロックのコードが実行されます。 catchでは、Exceptionオブジェクトを受け取って、Messageプロパティの値をMessageBoxに表示することでエラーメッセージを表示しています。

発生する例外エラーで渡されるオブジェクトはException型だけではありませんが、通常はExceptionクラスから派生したクラスによって生成されているため、Exception型で受け取ることができます。

今回作成したクラスダイアグラム

今回作成したクラスダイアグラムは次のようになっています。

今回作成したクラスダイアグラム
今回作成したクラスダイアグラム

次回の予定

前回から2回にわたって、オブジェクト指向プログラミングを意識してサンプルソフトを作成してみました。

次回はIDEの機能や既存のクラスを利用した省力化の方法をご紹介します。

おすすめ記事

記事・ニュース一覧