-->

[removed]Of Func(Of T)).Body.Member.Name bizarre “

2019-05-30 10:18发布

问题:

I have observed the following bizarre behavior, and want to see if anyone already came across the same. In fact, I did quite a bit of searching, but have not bumped in anything related as yet.

It has somehow got quite conventional to supply a reference to a Property name from within a Class to a Method by means of a Lambda expression, instead the name String itself. So: RaisePropertyChanged("myProperty") gets RaisePropertyChanged(() => myProperty) in C# or RaisePropertyChanged(Function() myProperty) in VB .Net.

The called Method receives that Lambda expression in a System.Linq.Expressions.Expression<Func<T>> type in C# or Expression(Of Func(Of T)) in VB .Net.

In order the get the Property name in a String representation, the called Method retrieves the Expression(Of Func(Of T)).Body as a MemberExpression. Then accessing memberExprisson.Member.Name will usually get the proper Property name.

However, in VB .Net I have noticed the following bizarre behavior: When calling a method inside of Property Get stub supplying a Property by means such as (Function() myProperty) the memberExprisson.Member.Nameresults to: "$VB$Local_myProperty". So that's $VB$Local_ added in front of the Property name. Calling however from the Property Set stub worked as intended.

Whats more, when the result is OK, the memberExpression.Member'type is a System.Reflection.RuntimePropertyInfo. Whereas when the bizarre "$VB$Local_" is added, the memberExpression.Member results in a System.Reflection.RtFieldInfo type.

When examining the Expression Property of the above mentioned memberExpression thus: memberExpression.Expression, I find that the Type Property thereof will - on good behavior - have the proper Container Class name. On erroneous behavior however, that Type property will have a 'FullName' Property of something like "_Closure$__X" + the Container (Declaring) Class name. Further looking inside this Type property reveals that this FullName consist of a the Name of the Type itself which is "_Closure$__X" combined with the 'ReflectedType' which contains the proper Class name, resulting in this strange FullName. This "_Closure$__X" by the way, The 'X' represents a Number. It will be '1' inside the first Property Get stub and 2 for the second and so forth. So: "_Closure$__1", "_Closure$__2"...

Any comments?

EDIT:

For clarity here is a snapshot of the code:

Public Property RegisteredServer As Result
    Get
        Return GetProperty(Of Result)(Function() RegisteredServer)
    End Get
    Set(value As Result)
        SetProperty(Of Result)(Function() RegisteredServer, value)
    End Set
End Property

Public Property DefaultInstance As Result
    Get
        Return GetProperty(Function() DefaultInstance)
    End Get
    Set(value As Result)
        SetProperty(Function() DefaultInstance, value)
    End Set
End Property

GetPropertyAnd SetProperty are defined in the following code:

Private Function GetPropertyName(Of T)(propertyExpression As Expression(Of Func(Of T)))
    Dim memberExpr As MemberExpression = propertyExpression.Body
    If memberExpr Is Nothing Then
        Throw New ArgumentException("propertyExpression should represent access to a member")
    End If
    Dim memberName As String = memberExpr.Member.Name
    Return memberName
End Function

Shared Function CompareValues(Of T)(storage As T, value As T)
    Return Object.Equals(storage, value)
End Function

Protected Function SetProperty(Of T)(propertyExpression As Expression(Of Func(Of T)), value As T)
    Dim memberName As String = GetPropertyName(propertyExpression)
    Dim currentValue As T = Nothing
    _propertyBag.TryGetValue(memberName, currentValue)
    If CompareValues(currentValue, value) Then
        Return False
    End If
    _propertyBag(memberName) = value
    RaisePropertyChanged(memberName)
    Return True
End Function

Protected Function GetProperty(Of T)(propertyExpression As Expression(Of Func(Of T))) As T
    Dim memberName As String = GetPropertyName(propertyExpression)
    Dim value As T = Nothing
    _propertyBag.TryGetValue(memberName, value)
    Return value
End Function

Hope this helps.

回答1:

If you remember what Expressions are primarily used for, this makes sense: Expressions are generally used to compile into functions. An expression inside a Get Property that references itself would compile into an infinite loop. So instead of that lambda becoming a PropertyExpression, it becomes a FieldExpression on a closure:

GetProperty(Of Result)(Function() RegisteredServer) is the same GetProperty(Of Result)(Function() Me.RegisteredServer), so the compiler encloses the 'this' (Me) reference. A field expression around a closure can result in accessing a compiler-generated class, with weird names.

In your case, you don't really care about the 'this' reference, you just want a way to reference the Property in a strongly-typed way. You can do this by adding an explicit parameter so you're not enclosing anything:

    Public Function GetPropertyName(Of TClass, TProperty)(propertyExpression As Expression(Of Func(Of TClass, TProperty)))
        Dim memberExpr As MemberExpression = propertyExpression.Body
        If memberExpr Is Nothing Then
            Throw New ArgumentException("propertyExpression should represent access to a member")
        End If
        Dim memberName As String = memberExpr.Member.Name
        Return memberName
    End Function
    Protected Function GetProperty(Of TClass, TProperty)(propertyExpression As Expression(Of Func(Of TClass, TProperty))) As TProperty
        Dim memberName As String = GetPropertyName(propertyExpression)
        Dim value As TProperty = Nothing
        _propertyBag.TryGetValue(memberName, value)
        Return value
    End Function

... then GetProperty(Of Result)(Function() RegisteredServer) becomes GetProperty(Of YourClass, Result)(Function(c) c.RegisteredServer).


Edit: Upon further thought, you don't need the TClass type variable:

    Public Function GetPropertyName(Of T)(propertyExpression As Expression(Of Func(Of X, T)))
        Dim memberExpr As MemberExpression = propertyExpression.Body
        If memberExpr Is Nothing Then
            Throw New ArgumentException("propertyExpression should represent access to a member")
        End If
        Dim memberName As String = memberExpr.Member.Name
        Return memberName
    End Function


    Protected Function GetProperty(Of TProperty)(propertyExpression As Expression(Of Func(Of X, TProperty))) As TProperty
        Dim memberName As String = GetPropertyName(propertyExpression)
        Dim value As TProperty = Nothing
        _propertyBag.TryGetValue(memberName, value)
        Return value
    End Function

... where X is the name of your class. This means you can remove the type annotations from your GetProperty calls: So instead of GetProperty(Of Result)(Function() RegisteredServer) or GetProperty(Of YourClass, Result)(Function(c) c.RegisteredServer), you can now do: GetProperty(Function(c) c.RegisteredServer).