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

第5回C#のコードに移植する

はじめに

本連載では、実際にVB6で開発したソフトウェアをC#に置き換えながら基本的な技術を学んでいただきます。

前回は、C#を使ってフォームを作成し、イベントに対するメソッドを作成しました。今回は、第2回 VB6で作ったサンプルソフトでご紹介したVB6のコードを、可能な限り忠実にC#のコードに単純に移植したあとに、要点をご説明していきたいと思います。

単純にC#に移植したコード

なるべくVB6のコードに近い形で移植しました。

各メンバ名、引数や戻り値、各メンバの役割りや振る舞いもすべてVB6と同様です。

まずは、以下のC#のコードを第2回 VB6で作ったサンプルソフトでご紹介したVB6のコードと比較しながら確認してみてください。 VB6で書いていた内容をC#で書く場合にはどうすればよいのかが簡単に理解できるはずです。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;    // 追加
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    //*************************************************************
    /// <summary>電話帳サンプルソフトフォーム</summary>
    //*************************************************************
    public partial class Form1 : Form
    {
        private OleDbConnection dbConnection;
        private const string FILENAME = @"c:\Database.mdb";

        //*************************************************************
        ///<summary>コンストラクタ</summary>
        //*************************************************************
        public Form1()
        {
            InitializeComponent();
        }

        //*************************************************************
        // イベント処理用メソッド
        //*************************************************************
        private void addButton_Click(object sender, EventArgs e)
        {
            if (!AddLogic()) return;
            ClearData();
        }

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

        private void Form1_Load(object sender, EventArgs e)
        {
            ConnectDatabase();
            codeTextBox.Text = "";
            ClearData();
        }

        private void Form1_FormClosed(
            object sender, FormClosedEventArgs e)
        {
            CloseDatabase();
        }

        private void readButton_Click(object sender, EventArgs e)
        {
            ReadLogic();
        }

        private void updateButton_Click(object sender, EventArgs e)
        {
            UpdateLogic();
        }

        private void deleteButton_Click(object sender, EventArgs e)
        {
            if (!DeleteLogic()) return;
            ClearData();
        }

        //*************************************************************
        /// <summary>名前と電話番号の表示をクリアします</summary>
        //*************************************************************
        private void ClearData()
        {
            nameTextBox.Text = "";
            telephoneNumberTextBox.Text = "";
        }

        //*************************************************************
        /// <summary>Code プロパティ</summary>
        /// <remarks>CodeTextBox.Text に入力されている値です</remarks>
        //*************************************************************
        private int Code
        {
            get
            {
                int r = 0;
                if (int.TryParse(codeTextBox.Text, out r)) return r;
                return 0;
            }

            set
            {
                codeTextBox.Text = value.ToString();
            }
        }

        //*************************************************************
        /// <summary>追加処理を実行します</summary>
        /// <returns>結果 [True:成功 / False:失敗]</returns>
        //*************************************************************
        private bool AddLogic()
        {
            if (InvalidCode(Code))
            {
                MessageBox.Show("追加できませんでした");
                return false;
            }
            AddRecord(Code);
            return true;
        }

        //*************************************************************
        /// <summary>更新処理を実行します</summary>
        /// <returns>結果 [True:成功 / False:失敗]</returns>
        //*************************************************************
        private bool UpdateLogic()
        {
            if (InvalidCode(Code))
            {
                MessageBox.Show("更新に失敗しました");
                return false;
            }
            
            UpdateRecord(
                Code, nameTextBox.Text, telephoneNumberTextBox.Text);
            
            return true;
        }

        //*************************************************************
        /// <summary>削除処理を実行します</summary>
        /// <returns>結果 [True:成功 / False:失敗]</returns>
        //*************************************************************
        private bool DeleteLogic()
        {
            if (InvalidCode(Code))
            {
                MessageBox.Show("削除に失敗しました");
                return false;
            }
            DeleteRecord(Code);
            return true;
        }

次ページへ続く

前ページより
        //*************************************************************
        /// <summary>読込み処理を実行します</summary>
        /// <returns>結果 [True:成功 / False:失敗]</returns>
        //*************************************************************
        private bool ReadLogic()
        {
            object[] record = ReadRecord(Code);
            if (record == null)
            {
                MessageBox.Show("登録されていませんでした");
                return false;
            }
            else {
                nameTextBox.Text = record[1].ToString();
                telephoneNumberTextBox.Text = record[2].ToString();
                return true;
            }
        }

        //*************************************************************
        /// <summary>番号値が有効かどうかを検証した結果を取得します
        /// </summary>
        /// <param name="Value">番号の値</param>
        /// <returns>結果 [True:無効 / False:有効]</returns>
        //*************************************************************
        private bool InvalidCode(int Value)
        {
            if (Value <= 0 | Value >= 100000000)
            {
                MessageBox.Show("番号の値が不正です");
                return true;
            }
            else
            {
                return false;
            }
        }

        //*************************************************************
        /// <summary>レコードを追加します</summary>
        /// <param name="code">番号</param>
        /// <returns>結果 [True:成功 / False:失敗]</returns>
        //*************************************************************
        private bool AddRecord(int code)
        {
            string sqlText = GetAddSqlText(code);
            return ExecuteQuery(sqlText);
        }

        //*************************************************************
        /// <summary>レコードを更新します</summary>
        /// <param name="code">番号</param>
        /// <param name="nameValue">名前</param>
        /// <param name="telephoneNumber">電話番号</param>
        /// <returns>結果 [True:成功 / False:失敗]</returns>
        //*************************************************************
        private bool UpdateRecord(
            int code, string nameValue, string telephoneNumber)
        {
            string sqlText = GetUpdateSqlText(
                code, nameValue, telephoneNumber);
            return ExecuteQuery(sqlText);
        }

        //*************************************************************
        /// <summary>レコードを削除します</summary>
        /// <param name="code">番号</param>
        /// <returns>結果 [True:成功 / False:失敗]</returns>
        //*************************************************************
        private bool DeleteRecord(int code)
        {
            string sqlText = GetDeleteSqlText(code);
            return ExecuteQuery(sqlText);
        }

        //*************************************************************
        // レコードを読み込みます
        // Code:     番号
        // Returns:  RecordSet オブジェクト
        //*************************************************************
        private object[] ReadRecord(int code)
        {
            OleDbCommand cd = new OleDbCommand();
            OleDbDataReader dr = null;
            try
            {
                cd.Connection = dbConnection;
                cd.CommandType = CommandType.Text;
                cd.CommandText = GetSelectSqlText(code);
                dr = cd.ExecuteReader();
                if (dr.Read()) {
                    object[] r = new object[dr.FieldCount];
                    for(int i = 0; i < r.Length; ++i) 
                    {
                        r[i] = dr.GetValue(i);
                    }
                    return r;
                }
                return null;
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
                return null;
            }
            finally
            {
                if (dr != null) {
                    dr.Close();
                    dr.Dispose();
                }
                cd.Dispose();
            }
        }

        //*************************************************************
        // 各 SQL 文字列を取得します
        // Code:     番号
        // Returns:  SQL 文字列
        //*************************************************************
        private string GetSelectSqlText(int code)
        {
            string s = code.ToString();
            return "SELECT * FROM MyTable WHERE 番号 = " + s;
        }

        private string GetAddSqlText(int code)
        {
            return
                "INSERT INTO MyTable(番号, 名前, 電話番号) VALUES(" +
                code.ToString() + ", '' , '')";
        }

        private string GetUpdateSqlText(
            int code, string nameValue, string telephoneNumber)
        {
            return
                "UPDATE MyTable " +
                "Set " +
                    "名前 = '" + nameValue + "', " +
                    "電話番号 = '" + telephoneNumber + "' " +
                "WHERE 番号 = " + code.ToString();
        }

        private string GetDeleteSqlText(int code)
        {
            string s = code.ToString();
            return "DELETE * FROM MyTable WHERE 番号 = " + s;
        }

        //*************************************************************
        /// <summary>クエリーを実行します</summary>
        /// <param name="sqlText">実行したい SQL 文字列</param>
        /// <returns>結果 [True:成功 / False:失敗]</returns>
        //*************************************************************
        private bool ExecuteQuery(string sqlText)
        {
            OleDbCommand cd = new OleDbCommand();
            try {
                cd.Connection = dbConnection;
                cd.CommandType = CommandType.Text;
                cd.CommandText = sqlText;
                cd.ExecuteNonQuery();
                return true;
            }
            catch(Exception ex) {
                MessageBox.Show(ex.Message);
                return false;
            }
            finally{
                cd.Dispose();
            }
        }

        //*************************************************************
        /// <summary>データベースへの接続を開始します</summary>
        //*************************************************************
        private void ConnectDatabase()
        {
            dbConnection = new OleDbConnection();
            dbConnection.ConnectionString = 
                "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
                FILENAME;
            dbConnection.Open();
        }

        //*************************************************************
        /// <summary>データベースの接続を終了します</summary>
        //*************************************************************
        private void CloseDatabase()
        {
            dbConnection.Close();
            dbConnection.Dispose();
        }
    }
}

現時点では、一部わからないコードがあるかもしれません。 これについては、以降の解説で明らかにしていきたいと思います。

なお、上記はVB6のコードをベタに移植したもので、実際にC#で同様のソフトウェアを開発する場合は、もう少し便利な方法があります。 これについては、次回以降ご説明していく予定ですが、その前に、まずは上記のコードを理解することから始めましょう。

オブジェクト指向プログラミング言語

実際にC#のコードを解説する前に、.NETプログラミングとは、オブジェクト指向プログラミングであることをご理解いただく必要があります。

オブジェクト指向プログラミングとは、データと、そのデータを処理する手続きをオブジェクトという単位でまとめて、これらを組み合わせてプログラミングしていくというものです。

オブジェクト指向プログラミングと聞くと新しいもののように感じるかもしれません。 しかし、この考え方による恩恵はVB6の頃からすでに受けていました。

例えば、VB6のサンプルコードではRecordSetオブジェクトを使いました。 また、Form1というオブジェクト上に、ボタンコントロールから生成した「読込み」⁠追加」⁠更新」⁠削除」⁠閉じる」という5つのオブジェクトを配置しました。

これらのオブジェクトを生成するためには、クラスを作成する必要があります。

例えば、今回C#で作成したフォームに配置した「読込み」⁠追加」⁠更新」⁠削除」⁠閉じる」の Buttonオブジェクトを生成するためには、Buttonクラスが必要になります。 しかし、Buttonクラスを自分で書く必要はなく、すでに誰かが作成したものを我々が使っていることになります。

一方で、上記のサンプルコードでは、電話帳フォームのクラスを作成しているに過ぎません。 つまり、極端な言い方をすれば「.NETプログラミングとは、誰かが作ったクラスを使ったり、自らがクラスを作成すること」と言えるかもしれません。

オブジェクト指向プログラミング言語であるかどうかは、とても大きな変更です。 以前、VB6とVB.NETよりも、VB.NETとC#のほうが近しい関係にあるとご説明しました。 この理由も、オブジェクト指向プログラミング言語か否かという違いが大きいと言えます。 また、前項で「このサンプルコードはC#らしさがない」と述べた理由も同様です。

これらについては、次回以降で明らかにして行く予定です。 現時点では「C#という開発言語は、クラスを使ったり作っていくものらしい」という曖昧な理解でよいでしょう。

基本的な構造について

以下のコードは、冒頭のC#サンプルコードの骨組み部分を抜粋したものです。

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1(){}
        private void addButton_Click(object sender, EventArgs e){}
            :
        private bool DeleteRecord(int code){}
    }
}

これを見ると、単純に以下の表記が階層化されて記述されていることに気づきます。

  • 文字列 {}

まずは、上から順に「文字列」の部分をご説明して行きたいと思います。

namespace WindowsFormsApplication1 {}

名前空間が、WindowsFormsApplication1ということになります。

サンプルコードを見ると{}の中に記述されているForm1というクラスがWindowsFormsApplication1に属していることがわかります。 また、ほかの名前空間にもForm1クラスを存在させることもできます。

つまり、名前空間によって、テクノロジーごとにクラスを分類したり、名前の衝突を避けることができます。

public partial class Form1 : Form {}

このソフトウェアのクラスであるForm1を定義しています。 ⁠{}」の中に記述されている変数、メソッド、プロパティ等は、このクラスに属することになります。

また、Formクラスを継承していることを示しています。 クラスの継承については、次回以降にご説明する予定です。

現時点では、Formクラスの機能をForm1が継承しているものだとご理解ください。

public Form1(){}

クラス名と同じ名前を持ち、戻り値の型を表記しないメソッドはコンストラクタです。 コンストラクタとは、オブジェクトを生成するために呼び出される特別なメソッドです。 サンプルコードでは、Form1オブジェクトを生成する際に、InitializeComponent()というメソッドを呼び出しています。

C#では、メソッドを呼び出す場合にはメソッド名の後ろに「()⁠⁠、引数がある場合には「()」の中に引数となる値、もしくは変数を記述します。 また、各命令語の末尾には「;」⁠セミコロン)を記述します。

Method();
Method(1, 2, 3);

private void addButton_Click(object sender, EventArgs e){}

追加ボタンがクリックされた際に呼び出される自動生成されたメソッドです。 VB6では戻り値のないメソッドはSubと記述しますが、C#では戻り値の型にvoidと記述します。

引数にはobject型のsenderとEventArgs型のeがあります。

senderには、このメソッドを呼び出したオブジェクトが格納されます。 変数eには、そのイベントから提供される値がある場合には、それらの値を格納したオブジェクトが格納されます。

VB6では、イベントから提供される値が複数ある場合は、その数だけ引数が存在しましたが、C#ではeというオブジェクトにすべて格納されることになります。

private bool DeleteRecord(int code){}

int型の引数を与えることでbool型の値を返すメソッドです。 VBでは戻り値のあるメソッドの場合はFunctionキーワードを記述しなければなりませんがC#では戻り値の型を記述します。 また、戻り値を持たない場合は、voidを記述します。

なお、VB6のLong型がintになっている理由ですが、.NETではInteger型で格納できる範囲が32ビットに変更になったためです。

各部詳細について

ここではC#特有の記述に関してをご説明します。 VB6のコードと見比べた時、単純にはご理解いただけないであろう部分のみご説明して行きたいと思います。

using

先頭のusing句から始まる記述ですが、このほとんどは最初から記述されており、今回移植するにあたって追記したのは「// 追加」とコメントした行のみです。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;    // 追加
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

usingの後に記述されているSystem.~ですが、これらは名前空間です。 このように記述することで、これらの名前空間に属する型名は、名前空間を省略して記述することができます。

例えば、サンプルコードで記述しているクラスはFormというクラスを継承しています。

public partial class Form1 : Form

このクラスは、System.Windows.Forms.Formのように名前空間を含めて記述するのが正式な表記ですが、単純にFormとだけ記述できるのは、⁠using System.Windows.Forms;」と記述されているためです。

///<summary></summary>

「//」のようにスラッシュを2つ重ねると、これを記述した後は行末まで単なるコメントとして扱われます。

「///」のようにスラッシュを3つ重ねるとドキュメンテーションコメントになります。

例えば、⁠private bool AddRecord(int code)」のように記述されているメソッド名の上の行に///と入力すると、自動的に以下の行が追加されます。

/// <summary>
/// 
/// </summary>
/// <param name="code"></param>
/// <returns></returns>

ここに対して、以下のように各タグに対する情報を入力します。

/// <summary>レコードを追加します</summary>
/// <param name="code">番号</param>
/// <returns>結果 [True:成功 / False:失敗]</returns>

こうすることで、ここに記述された内容をXMLファイルに書き出すことができるようになります。 また、C#のコードエディタ上でAddRecordメソッドを呼び出している部分にマウスカーソルを当てると、ここに記述された文字がヒントとして表示されます。

private const string FILENAME = @"c:\Database.mdb";

C#では、エスケープシーケンスを文字列定数に含めることができます。 例えば、"ABC\tDEF" と記述するとABCとDEFの間にタブが含まれることになります。

サンプルコードでは、上記のように「@」が先頭付加されています。 このように書くことで、エスケープシーケンスの使用を避けることができます。

if (!AddLogic()) return;

if文は、以下のように「{}」を用いて記述しますが、1命令で済んでしまう場合は、上記のように条件式の後ろに続けて記述することもできます。 また「!」は、VB6のNotと同じ意味になります。
if ()
{
    // ここに真の場合に実行される命令を記述する
}
else
{
    // ここに偽の場合に実行される命令を記述する
}

メソッドから抜けたい場合はreturnを記述します。 上記は、戻り値がない(void)メソッドの場合です。

戻り値がある場合は、戻り値の型に準じて、例えばint型なら「return 0」のようにreturnの後に戻り値を指定します。

以上によって、上記は「AddLogic() メソッドの戻り値が False ならメソッドから抜けなさい」という意味になります。

private int Code { get {} set {} }

プロパティを記述するには、メソッド名の後ろに引数を記述するための「()」は付けません。 そして、必要に応じてGetとSetを上記のように記述します。 なお、VB6のLet キーワードがSetと同じ意味になっています。

また、プロパティに対して渡された値は、常にvalue変数にプロパティの型と同じ型として格納されます。

if (int.TryParse(codeTextBox.Text, out r)) return r;

この1行に、複数の疑問が生じることと思います。

まずはintという型にメソッドが付加されている点についてです。 乱暴な答え方をしてしまうと、C#がオブジェクト指向プログラミング言語だから、となります。

オブジェクト指向では、すべてがオブジェクトという考え方です。 intという型であっても例外ではなく、VB6のRecordSet型やCommandButtonコントロールの型と同様に、メソッドやプロパティを持つオブジェクトなのです。

int r = 0;
if (int.TryParse(codeTextBox.Text, out r)) return r;
return 0;

TryParse()メソッドは、codeTextBox.Textに格納されている文字列をint型に変換します。 この時、int.TryParseメソッドは、その結果をbool型(VB6ではBoolean型)で返します。 そして、outキーワードの後に記述されている変数に、その結果を格納しています。

このようにC#で引数に記述した変数に値を出力する場合はoutキーワードを記述します。 これによって、値を受け取る引数を明確に分けることができています。

codeTextBox.Text = value.ToString();

プロパティの値がvalue変数に格納されることはすでに説明しました。 しかし、上記ではint型のvalueという変数の後ろにToString()というメソッドが記述されています。

これも、直前の項で説明したint型にメソッドやプロパティを持つことと同じ理由です。 ToString()メソッドは、valueに格納されているint型の数値を文字列として返すためのメソッドです。

object[] record = ReadRecord(Code);

型名の後ろに[]を記述することで、その型の配列であることを表します。 上記は record変数にReadRecordメソッドから返されたobject型の配列を格納しています。

if (record == null)

recordがnullなら、という条件式です。 VB6では、代入演算子も比較演算子も「=」と記述しますが、C#では「=」が代入演算子、⁠==」が比較演算子になります。

nullは、オブジェクトがない、空の状態という意味になります。

if (Value <= 0 | Value >= 100000000)

「|」はVB6のOrと同じ意味です。

なお、⁠||」とすると、ショートサーキットによる評価になります。今回の場合は、本来はショートサーキットによる評価でよいでしょう。

try {} catch {} finally {}

try {}に記述されたコードで例外エラーが発生した場合は、catch {}内のコードが実行されます。 また、このブロックを抜ける直前にfinally {}内のコードが実行されます。

dbConnection.Dispose();

Dispose()メソッドを持つオブジェクトは、使用したリソースを解放するためにDispose()メソッドを実行する必要があります。

次回の予定

今回は、第2回 VB6で作ったサンプルソフトでご紹介した VB6 のコードを、可能な限り忠実にC#のコードに単純に移植した後に要点をご説明しました。

次回は、オブジェクト指向プログラミングについてご説明して行きたいと思います。

おすすめ記事

記事・ニュース一覧