アリスの鞄は作った人を知っている

ちょっとアリスシリーズ風に書き下し。
要は、デバッグ用に呼び出し元のクラス名を取得したいのですが、デバッグ用なので new 時にインスタンスやクラス名を渡したくない、のですね。なので、呼び出されたクラス/メソッドのほうから、こっそりと StackFrame を使って、呼び出し元のクラス名を取得するという技です。

' 参考
' 自分自身のクラス名とメソッド名:Gushwell's C# Dev Notes
' http://gushwell.ldblog.jp/archives/50715142.html

Public Class Form1

	''' <summary>
	''' アリスを作成
	''' </summary>
	''' <param name="sender"></param>
	''' <param name="e"></param>
	''' <remarks></remarks>
	Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

		Dim alice As New Alice
		alice.Check()

	End Sub

	''' <summary>
	''' ロリータを作成
	''' </summary>
	''' <param name="sender"></param>
	''' <param name="e"></param>
	''' <remarks></remarks>
	Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

		Dim lolita As New Lolita
		lolita.Check()

	End Sub
End Class

Public Class Person

	Protected _bag As Bag

	''' <summary>
	''' コンストラクタ
	''' </summary>
	''' <remarks></remarks>
	Public Sub New()
		' バッグを作成(作成者名はバッグの内部で保存される)
		_bag = New Bag
	End Sub
	''' <summary>
	''' 作成者を表示
	''' </summary>
	''' <remarks></remarks>
	Public Sub Check()
		MessageBox.Show("class in " + _bag.GetClassName)
	End Sub

End Class

''' <summary>
''' アリスクラス
''' </summary>
''' <remarks></remarks>
Public Class Alice
	Inherits Person
End Class

''' <summary>
''' ロリータクラス
''' </summary>
''' <remarks></remarks>
Public Class Lolita
	Inherits Person
	' ※本来は、クラス名を渡すべき
	Public Sub New()
		_bag = New Bag("LOLITA")
	End Sub

End Class

Public Class Bag

	Protected _cname As String

	''' <summary>
	''' コンストラクタ
	''' </summary>
	''' <remarks></remarks>
	Public Sub New()

		Dim st As New StackTrace(False)
		' Bag -> Person -> Alice の順で 2 を指定する
		Dim sf As StackFrame = st.GetFrame(2)
		' 呼出元を保存しておく
		_cname = sf.GetMethod.ReflectedType.FullName

	End Sub

	''' <summary>
	''' 本来はクラス名を渡すべき
	''' </summary>
	''' <param name="cname"></param>
	''' <remarks></remarks>
	Public Sub New(ByVal cname As String)
		_cname = cname
	End Sub

	''' <summary>
	''' 設定されているクラス名を取得
	''' </summary>
	''' <returns></returns>
	''' <remarks></remarks>
	Public Function GetClassName() As String
		Return _cname
	End Function
End Class

Alice クラス内で、new Bag をしています。この Bag を生成したのは誰か?というのは、本来ならば Alice のインスタンスか、クラス名を渡す、あるいは、Bag プロパティに設定する、ということをやる必要があるのですが、これをスタックフレームを使って呼び出し元のクラス名を取得するようにします。

Bag クラスのコンストラクタの部分が少しトリッキーなことになっています。

	Public Sub New()

		Dim st As New StackTrace(False)
		' Bag -> Person -> Alice の順で 2 を指定する
		Dim sf As StackFrame = st.GetFrame(2)
		' 呼出元を保存しておく
		_cname = sf.GetMethod.ReflectedType.FullName

	End Sub

StackTrace クラスでスタックトレースを取得した後、GetFrame メソッドで呼び出し元を取得します。このときに、スタックの状態が、Bag -> Person -> Alice になっているので「2」を指定しています。
間に Person クラスが挟まるのは、Alice クラスとの継承関係があるからです。なので、フォームからの呼び出しをチェックする場合も、Form クラスを継承していることを考慮にいれて、GetFrame メソッドに渡す値を調節しないといけません。ややこしいですね…というか、実装依存になるので、ピンポイントでしか使えない技です。なので、素直にクラス名を渡したほうがよさそうです。

こういう場合、XmlDocument クラスのようにファクトリーパターンを使います。コンストラクタにクラス名を渡すよりは自然かも。

Dim bag = Bag.CreateInstanceWithName("Lolita")
'あるいは
Dim bag As New Bag("Lolita")

■参考

自分自身のクラス名とメソッド名:Gushwell’s C# Dev Notes
http://gushwell.ldblog.jp/archives/50715142.html

カテゴリー: 開発, VB パーマリンク