C言語の構造体配列のconst定義した簡易テーブルみたいなアレをC#で扱えるようになれるはず。
やったね!
基本編
まえがき
簡単なデータをプログラム内に実装したいときがあると思います。
- とはいっても、データーベースエンジンを導入するほどのデータを扱いたいわけではないし・・・。
- とはいっても、二次元配列は、同じ型しか使えないし・・・。
そんなときは、構造体を定義して、それを配列にすることで、簡単なテーブルっぽい形のものが作れます。
本ページでは、そんな構造体配列の定義例や初期化例、あとは使用例を紹介していきたいと思います。
構造体配列の定義・初期化例
構造体定義その1
public struct TTT
{
public int iId;
public string sName;
}
初期化その1
private TTT[] stList = {
new TTT { iId = 0, .sName = "A"},
new TTT { iId = 1, .sName = "B"},
new TTT { iId = 2, .sName = "C"}
};
これはこれでいいんだけど、1行データごとにメンバ変数の名前を書かないといけないので、はっきりいって面倒くさい。
また、構造体のメンバ変数は、上記の例では2つだけですが、これが5つ6つと増えると1行データが長くなってしまいます。あとダサい。
C言語みたいな定義がしたい
構造体定義の中に、初期化関数を追加することで、構造体配列変数を定義するときの記載が簡易にできて、まるでC言語の構造体配列の初期化みたいな書き方になります。
構造体定義その2
public struct TTT
{
public int iId;
public string sName;
public TTT(int p1, string p2)
{
this.iId = p1;
this.sName = p2;
}
}
初期化その2
private TTT[] stList = {
new TTT(0, "A"),
new TTT(1, "B"),
new TTT(2, "C"),
new TTT(-1, "") // -1は、終端の意味として使う
};
なんかすっきり。
どう使うのか
定義・初期化ができたら、こんどは構造体配列「stList」をどう使えばよいのか使用例を紹介したいと思います。
構造体配列のインデックス位置取得
たとえば、メンバ変数「iId」の値と一致するインデックス位置(レコード位置)を取得する関数を用意します。
呼び側は、インデックス位置が取得できたら、そのレコードのデータ「sName」を参照することができるようになるといった使い方ができます。
// iIdの値が一致するインデックス位置を取得する関数
int getIdx( int iId )
{
int iIdx;
int iRtn;
iIdx = 0;
iRtn = -1;
// 終端まで、もしくは検出するまで検索
while( stList[iIdx].iId != -1 )
{
// iId一致
if( stList[iIdx].iId == iId )
{
iRtn = iIdx;
// ループを抜ける
break;
}
iIdx++;
}
return( iRtn );
}
呼び出し元
int iIdx;
int iRtn;
// 一覧から検索
iIdx = getIdx( iParam );
if( iIdx != -1 )
{
// 一致した場合
stList[iIdx].sName; // 一致したインデックスのsNameを取得
}
else
{
// 存在しない
}
この例でいうと、変数iParamを引数にgetIdx()関数でインデックス位置を取得してます。
インデックス位置を取得できたら、そのレコードのsNameを取得できます。
応用編
構造体に関数を追加
たとえば、インデックス位置が取得できて、関数を実行させる。といった使い方もできます。
ここまでくると、もうclassにしたら?と言われそうではありますが・・・。
構造体
public delegate bool MyFunction(Byte[] b, string s); // 関数定義
public struct TTT
{
public int iId;
public string sName;
public MyFunction pFunc; // 関数追加
public TTT(int p1, string p2, MyFunction f1)
{
this.iId = p1;
this.sName = p2;
this.pFunc = f1;
}
}
初期化
private TTT[] stList = {
new TTT(0, "A", null),
new TTT(1, "B", checkData), // この行だけ関数定義
new TTT(2, "C", null),
new TTT(-1, "", null) // -1は、終端の意味として使う
};
// 関数定義
public static bool checkData(Byte[] b, string s)
{
bool bRtn;
bRtn = false;
// 値が一致するかチェック
if (s == ByteArrayToHexString(b).Trim())
{
bRtn = true;
}
return bRtn;
}
呼び出し元
// INPUT:int iParam、Byte配列 byData
int iIdx;
bool bRtn;
// 一覧から検索
iIdx = getIdx( iParam );
if( iIdx != -1 )
{
// 関数が定義されていた場合
if( stList[iIdx].pFunc != NULL )
{
// 処理実行
bRtn = stList[iIdx].pFunc( byData, stList[iIdx].sData );
}
}
else
{
// 存在しない
}
この例でいうと、変数iParamを入力値として、getIdx()関数を使用してインデックス位置を取得してます。
次に、メンバ関数pFuncがNULLでない場合、関数を実行するという例になります。(pFunc()の中身は、checkData()関数です)
構造体配列の値でいうと、
この例では、メンバ変数iIdが1の場合だけ、checkData()関数を呼ぶ形になるということになります。
最後に
構造体配列は、地味に使う機会が多かったりします。
内部メモリテーブルとして使うのに便利です。
例えば、初期化処理、メイン処理と、処理ごとに構造体配列化し、1レコードずつ処理を実行させるプログラムなんかが作れたりもします。
メッセージなどのイベント処理を順番に行いたい場合とかに使うのも良いかもしれません。
// 初期化処理リスト
private TTT[] stInit = {
new TTT(0, "A", null),
new TTT(-1, "", null) // -1は、終端の意味
};
// メイン処理リスト
private TTT[] stMain = {
new TTT(1, "B", checkData), // この行だけ関数定義
new TTT(2, "C", null),
new TTT(-1, "", null) // -1は、終端の意味
};
// さらに配列定義する例
private TTT[][] m_stAll = new TTT[][] { stInit, stMain };
呼び側のプログラムをシンプルもしくは汎用的に出来ますし、何か追加や変更があった場合などは、プログラム側の修正はせずとも、テーブル側のみ直せば済む。なんて設計も出来たりします。
新しい処理を増やしたいときなんかは、pFuncメンバ関数に新しい関数を追加するだけで済むような設計にするとか。
応用がいろいろと効くので、好んで使っていたります。
ご参考になればと思います。
最後まで見てくれてありがとう。