データコンバートに explicit を使う

C#のあまり知られていない機能シリーズってことで、explicit を使ってデータコンバートしましょうってのを試してみます。と言いますか、linq to xml いらないよもどきを作っている最中に、そういえばキャストもオーバーライドができたよなぁ、ってところで気づきました。

一般的な型のコンバートは↓のようにするのが普通なのですが…

型なしDataTableから型付きDataTableにコピーする方法 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2047

いちいち、何とか.CONV 関数を作るのがうざったいですよね。と言うか、使うときに Conv しているのが、ちょっとってな感じなのです。変換しているのが分かり易いといえば分かり易いのですが、C++ 風に言えば、型から型へキャストっていうのも明示的な変換な訳でして、それを利用します。

まずは、使い方から

private void button3_Click(object sender, EventArgs e)
{
	// データベース接続
	SqlConnection cn = new SqlConnection(@"Data Source=.\sqlexpress;Initial Catalog=mvcdb;Integrated Security=True;MultipleActiveResultSets=True");
	cn.Open();
	DataTable dt = new DataTable();
	SqlDataAdapter da = new SqlDataAdapter(
		"SELECT * FROM TProduct", cn);
	da.Fill(dt);
	// キャストする
	DataTableProduct product = (DataTableProduct)dt; // ★
	dataGridView1.DataSource = product;
}

何をやっているのか、いまいち不明ですがw、★のところで、DataTable から 型付の DataTableProduct に変換しています。ここのところ、普通は converet 関数を作るところなのですが、こんな風に明示的なキャスト(explicit)を使うことができます。

# ちなみに暗黙のキャスト(implicit)を使うこともできるのですが、この場合は convert するのが明確になったほうがいいかなと。

そして、これを実現しているのが↓の仕組みです。

public class DataRowProduct
{
	// プロパティ
	public string id { get; set; }
	public string name { get; set; }
	public int price { get; set; }

	// キャストで変換
	public static explicit operator DataRowProduct(DataRow row)
	{
		DataRowProduct dest = new DataRowProduct();
		foreach (PropertyInfo pi in dest.GetType().GetProperties())
		{
			pi.SetValue( dest, Convert.ChangeType(row[pi.Name], pi.PropertyType),null);
		}
		return dest;
	}
}
public class DataTableProduct : IListSource 
{
	protected List<DataRowProduct> _rows = new List<DataRowProduct>();
	public List<DataRowProduct> Rows
	{
		get { return _rows; }
	}

	public bool ContainsListCollection
	{
		get { return true; }
	}

	public System.Collections.IList GetList()
	{
		return this.Rows;
	}

	// キャストで変換
	public static implicit operator DataTableProduct(DataTable dt)
	{
		DataTableProduct dest = new DataTableProduct();
		foreach (DataRow row in dt.Rows)
		{
			dest.Rows.Add((DataRowProduct)row);
		}
		return dest;
	}
}

DataRowProduct クラスのほうでは、リフレクションを使ってデータの詰め込み、そして、DataTableProduct のほうは、キャストを使ってデータ Rows コレクションにため込んでいます。実は、この convert の時にコピーが発生しているので、あんまりうまくないんですよね。どちらかといえば、Rows コレクションのアクセスのたびに DataTable.Rows から引っ張ってくるっていう技のほうが良いかなぁとも思います。

まぁ、それは兎も角として、キャストもうまく使うと変換の意味が表せるから便利ってな話です。
キャストと convert 関数の違いは、

  • キャストのほうは、変換先が注目される。
  • convert 関数のほうは、変換元にメソッドを追加する。
  • あるいは、convert 関数は、変換先に from メソッドを追加する。

という違いがあります。具体的にコードで示してみると、


DataTable dt ;
// キャストの場合は、左向きが明確になる。
// 変換元は表面上は出てこない
DataTableProduct table = (DataTableProduct)dt;

// 変換元で追加するコンバート関数
// ToInteger なんてのがそう。今回の場合は、DataTable を触れないので無理。
// 実は、C# の拡張メソッドを利用すると実現できたり。
DataTableProduct table = dt.ToDataTableProduct();

// 変換先で追加するコンバート関数
// ParseInt なんてのがそう。
// From メソッドか、Conv メソッドか命名に迷うところ
DataTableProduct table = DataTableProduct.From(dt);

こんな感じにすると、キャストの意味が分かっていれば、キャストのほうが自然かな、と思ったりします。DataTable も DataTableProduct も似た感じのクラスな訳ですから、型から型への変換、というほうが自然に見えます。まぁ、シンタックスシュガーっぽいところはありますが。

カテゴリー: C# パーマリンク