NameOf演算子について

タグの編集
投稿者 SSD  (社会人) 投稿日時 2023/1/23 10:22:33
NameOf演算子の引数でtypeOrMemberとなっている部分にはクラスや非共有プロパティーなどを渡すことができます。
この引数部分でクラスを入力し、ドットを入力したところで非共有であってもメンバがインテリセンスによって一覧表示されます。
一般的なメソッドの引数ではこのようにはなりません。

今DisplayName属性値を取得するメソッドを作成しており、以下のようにしました。

    Public Shared Function GetDisplayName(Of T)(propName As StringAs String
        'プロパティー情報取得 
        Dim propInfo = GetType(T).GetProperty(propName)
        '属性取得 
        Dim att = Attribute.GetCustomAttribute(propInfo, GetType(DisplayNameAttribute))
        'キャストして属性値取得 
        Dim name = CType(att, DisplayNameAttribute).DisplayName
        Return name
    End Function


例えばClassAのAlphaプロパティーのDisplayName属性値を取得する場合はこんな感じで呼び出すことになります。

    Dim name = GetDisplayName(Of Class)(NameOf(ClassA.Alpha))


呼び出し時の引数を(Of Class)(NameOf(ClassA.Alpha))ではなく、NameOf演算子のように(ClassA.Alpha)だけにしたいのですが、メソッドの引数をどうすれば受け取れるようにできるのでしょうか?
投稿者 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回で済ます方法は何かないかということだと受け取りました。
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 は、今回考慮しないのですね?

<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 IntegerAs 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 StringAs String
    <DisplayName("Get アイテム[string]")>
    Get
        Return "Items." & s
    End Get
End Property



> メソッドの引数をどうすれば受け取れるようにできるのでしょうか?
呼び方が変わってしまいますが、単行ラムダ式で指定しても構わなければ、
『式木』から辿るというのは如何でしょう。
投稿者 (削除されました)  () 投稿日時 2023/1/24 11:33:10
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2023/1/24 11:45:25
> 『式木』から辿るというのは如何でしょう。

ということで、式木を用いた実装案です。

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など)

お二方とも参考になりました。
ありがとうございました。