NameOf演算子について
投稿者 SSD  (社会人)
投稿日時
2023/1/23 10:24:48
訂正
誤)
正)
誤)
Dim name = GetDisplayName(Of Class)(NameOf(ClassA.Alpha))
正)
Dim name = GetDisplayName(Of ClassA)(NameOf(ClassA.Alpha))
投稿者 るきお  (社会人)
投稿日時
2023/1/23 19:52:36
> NameOf演算子のように(ClassA.Alpha)だけにしたいのですが、メソッドの引数をどうすれば受け取れるようにできるのでしょうか?
簡単に言うとできないと思います。
(もし、だれかよいアイディアがあれば教えてください。)
SSD さんのご質問のポイントは、このプログラムでは、2回「ClassA」と記述する必要があるので、スマートではなく、ClassA の記述を1回で済ます方法は何かないかということだと受け取りました。
NameOf演算子は、実行時ではなくコンパイル時に解釈され、指定したものの名前を表す単なる文字列に置き換えられます。その点でNameOfは特殊で、そのオペランド(≒引数)はVBの通常の構文から隔絶された特別扱いをされているのだと思います。上記のプログラムは実行時のVBには次のように見えています。
このプログラムでは「ClassA」は1回しか登場していないのですよね。
とは言え、自分のプログラムで"Alpha"を文字列で書いてしまうと、メンテナンス性が悪くなってしまうので気が引けます。良い解決策はないように思います。
もし、単一のプロパティではなく、複数のプロパティを調べるのであれば、次のように書けるので特に冗長なプログラムにはなりません。
簡単に言うとできないと思います。
(もし、だれかよいアイディアがあれば教えてください。)
SSD さんのご質問のポイントは、このプログラムでは、2回「ClassA」と記述する必要があるので、スマートではなく、ClassA の記述を1回で済ます方法は何かないかということだと受け取りました。
Dim name = GetDisplayName(Of ClassA)(NameOf(ClassA.Alpha))
NameOf演算子は、実行時ではなくコンパイル時に解釈され、指定したものの名前を表す単なる文字列に置き換えられます。その点でNameOfは特殊で、そのオペランド(≒引数)はVBの通常の構文から隔絶された特別扱いをされているのだと思います。上記のプログラムは実行時のVBには次のように見えています。
Dim name = GetDisplayName(Of ClassA)("Alpha")
このプログラムでは「ClassA」は1回しか登場していないのですよね。
とは言え、自分のプログラムで"Alpha"を文字列で書いてしまうと、メンテナンス性が悪くなってしまうので気が引けます。良い解決策はないように思います。
もし、単一のプロパティではなく、複数のプロパティを調べるのであれば、次のように書けるので特に冗長なプログラムにはなりません。
For Each prop In GetType(ClassA).GetProperties()
'属性取得
Dim att = prop.GetCustomAttribute(GetType(DisplayNameAttribute))
If att Is Nothing Then
Continue For
End If
'キャストして属性値取得
Dim name = CType(att, DisplayNameAttribute).DisplayName
Debug.WriteLine($"{prop.Name}: {name}")
Next
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2023/1/24 11:32:13
> 例えばClassAのAlphaプロパティーのDisplayName属性値を取得する場合はこんな感じで呼び出すことになります。
getter / setter 側の DisplayName は、今回考慮しないのですね?
> 'プロパティー情報取得
> Dim propInfo = GetType(T).GetProperty(propName)
> '属性取得
> Dim att = Attribute.GetCustomAttribute(propInfo, GetType(DisplayNameAttribute))
propName が NameOf(プロパティ名) である保証はありませんので、
Nothing 判定を追加した方が良さそうです。
DisplayName はメソッド名やイベント名につけられることもあるからです。
そもそも、NameOf されなければスペルミスされる可能性もあるわけで。
さらに言えば、たとえ T 型の プロパティに限定するという前提であったとしても、
GetProperty ではなく、GetProperties に切り替えた方が良いかもしれません。
そうしないと、オーバーロードを持つプロパティを渡したときにエラーになります。
> メソッドの引数をどうすれば受け取れるようにできるのでしょうか?
呼び方が変わってしまいますが、単行ラムダ式で指定しても構わなければ、
『式木』から辿るというのは如何でしょう。
getter / setter 側の DisplayName は、今回考慮しないのですね?
<DisplayName("クラス")>
Public Class ClassA
<DisplayName("アルファ")>
Public Shared ReadOnly Property Alpha As Byte
Public Shared Property Beta As Byte
<DisplayName("Get Beta")>
Get
Return 0
End Get
<DisplayName("Set Beta")>
Set
_Beta = Value
End Set
End Property
Private Shared _Beta As Byte
End Class
> 'プロパティー情報取得
> Dim propInfo = GetType(T).GetProperty(propName)
> '属性取得
> Dim att = Attribute.GetCustomAttribute(propInfo, GetType(DisplayNameAttribute))
propName が NameOf(プロパティ名) である保証はありませんので、
Nothing 判定を追加した方が良さそうです。
DisplayName はメソッド名やイベント名につけられることもあるからです。
そもそも、NameOf されなければスペルミスされる可能性もあるわけで。
さらに言えば、たとえ T 型の プロパティに限定するという前提であったとしても、
GetProperty ではなく、GetProperties に切り替えた方が良いかもしれません。
そうしないと、オーバーロードを持つプロパティを渡したときにエラーになります。
<DisplayName("アイテム[number]")>
Public Property Items(n As Integer) As String
<DisplayName("Getアイテム[number]")>
Get
Return $"Items[{n}]"
End Get
<DisplayName("Setアイテム[number]")>
Set
End Set
End Property
<DisplayName("アイテム[string]")>
Public ReadOnly Property Items(s As String) As String
<DisplayName("Get アイテム[string]")>
Get
Return "Items." & s
End Get
End Property
> メソッドの引数をどうすれば受け取れるようにできるのでしょうか?
呼び方が変わってしまいますが、単行ラムダ式で指定しても構わなければ、
『式木』から辿るというのは如何でしょう。
投稿者 (削除されました)  ()
投稿日時
2023/1/24 11:33:10
(削除されました)
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2023/1/24 11:45:25
> 『式木』から辿るというのは如何でしょう。
ということで、式木を用いた実装案です。
🔹メリット
• 元の方式だと、GetDisplayName(Of ClassA)(NameOf(ClassB.Prop)) のような指定ミスがあり得るが、ラムダ式を渡せば、そうした心配がない
• オーバーロードを持つメンバーに対しても、どの引数定義を指定しているのかを正しく引き渡せる
🔸デメリット
• イベントの DisplayName を得ることができない
• WriteOnly プロパティを渡すことができない
ということで、式木を用いた実装案です。
Debug.Print(GetDisplayName(Function() ClassA.Alpha)) 'Shared プロパティ
Debug.Print(GetDisplayName(Sub() Me.SubProc())) 'インスタンス Sub メソッド
Debug.Print(GetDisplayName(Function() Me.FuncProc())) 'インスタンス Function メソッド
Dim cOleDb As New System.Data.OleDb.OleDbConnectionStringBuilder()
Debug.Print(GetDisplayName(Function() cOleDb.DataSource)) 'インスタンス プロパティ
Debug.Print(GetDisplayName(Function() cOleDb.FileName))
Debug.Print(GetDisplayName(Function() cOleDb.OleDbServices))
Debug.Print(GetDisplayName(Function() cOleDb.PersistSecurityInfo))
Public Shared Function GetDisplayName(Of T)(proc As Expression(Of T)) As String
If proc Is Nothing OrElse TypeOf proc.Body Is ConstantExpression Then
Return Nothing
End If
Dim m = TryCast(proc.Body, MemberExpression) 'フィールドやプロパティ
Dim c = TryCast(proc.Body, MethodCallExpression) 'メソッド
Dim atr As DisplayNameAttribute = Nothing
If m IsNot Nothing Then
atr = m.Member.GetCustomAttributes(Of DisplayNameAttribute)().FirstOrDefault()
If atr Is Nothing AndAlso m.Member.MemberType = MemberTypes.Property Then
'プロパティには付与されていなかったが、getter や setter には付与されている可能性がある
Dim getter = TryCast(m.Member, PropertyInfo)?.GetGetMethod(False)
'Dim setter = TryCast(m.Member, PropertyInfo)?.GetSetMethod(False)
If getter IsNot Nothing Then
atr = getter.GetCustomAttributes(Of DisplayNameAttribute)().FirstOrDefault()
End If
End If
ElseIf c IsNot Nothing Then
atr = c.Method.GetCustomAttributes(Of DisplayNameAttribute)().FirstOrDefault()
If atr Is Nothing AndAlso c.Method.IsSpecialName AndAlso c.Method.Name.StartsWith("get_") Then
'引数付きプロパティの getter が渡されたら、親プロパティの属性もさぐる
Dim propName = c.Method.Name.Substring(4)
Dim full = BindingFlags.Instance Or BindingFlags.Static Or BindingFlags.Public Or BindingFlags.NonPublic
Dim prop = c.Method.ReflectedType.GetProperties(full) _
.Where(Function(p) p.Name = propName AndAlso p.GetGetMethod() Is c.Method) _
.FirstOrDefault()
atr = prop?.GetCustomAttributes(Of DisplayNameAttribute)()?.FirstOrDefault()
End If
End If
Return atr?.DisplayName
End Function
🔹メリット
• 元の方式だと、GetDisplayName(Of ClassA)(NameOf(ClassB.Prop)) のような指定ミスがあり得るが、ラムダ式を渡せば、そうした心配がない
• オーバーロードを持つメンバーに対しても、どの引数定義を指定しているのかを正しく引き渡せる
🔸デメリット
• イベントの DisplayName を得ることができない
• WriteOnly プロパティを渡すことができない
投稿者 SSD  (社会人)
投稿日時
2023/1/30 16:46:31
NameOfは特殊なのですね。
プロパティー名の指定(引数propName)におけるエラーはあまり考慮しておりませんでした。
(getter,setterなど)
お二方とも参考になりました。
ありがとうございました。
プロパティー名の指定(引数propName)におけるエラーはあまり考慮しておりませんでした。
(getter,setterなど)
お二方とも参考になりました。
ありがとうございました。
この引数部分でクラスを入力し、ドットを入力したところで非共有であってもメンバがインテリセンスによって一覧表示されます。
一般的なメソッドの引数ではこのようにはなりません。
今DisplayName属性値を取得するメソッドを作成しており、以下のようにしました。
例えばClassAのAlphaプロパティーのDisplayName属性値を取得する場合はこんな感じで呼び出すことになります。
呼び出し時の引数を(Of Class)(NameOf(ClassA.Alpha))ではなく、NameOf演算子のように(ClassA.Alpha)だけにしたいのですが、メソッドの引数をどうすれば受け取れるようにできるのでしょうか?