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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
' 参考
' 自分自身のクラス名とメソッド名: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 クラスのコンストラクタの部分が少しトリッキーなことになっています。

1
2
3
4
5
6
7
8
9
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 クラスのようにファクトリーパターンを使います。コンストラクタにクラス名を渡すよりは自然かも。

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

■参考

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

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