2

I'm not really sure how to ask this question, but basically I want to know how to specify a class type generically such that I can type and paste code around.

For example I have the following code:

Public Class CustInfo
    Implements INotifyPropertyChanged

    Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

    Protected Sub NotifyPropertyChanged(ByVal Name As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Name))
    End Sub

    Protected Sub NotifyPropertyChanged()
        Dim myStackFrame As New StackFrame(1)
        Dim myChangingPropertyName As String = myStackFrame.GetMethod.Name.Split("_")(1)
        NotifyPropertyChanged(myChangingPropertyName)
    End Sub

    Protected Sub NotifyPropertyChanged(Of TResult)(propertyExpr As Expression(Of Func(Of CustInfo, TResult)))
        NotifyPropertyChanged(Me.GetPropertySymbol(propertyExpr))
    End Sub

The idea is to have the last Sub be generic, that is to say, not specifically have to specify CustInfo in it's definition. This way I can literally copy this code to any other class and it will work unmodified.

I've tried creating a property that returns the type of the class and using that, which predictably failed. I tried using TypeOf, Me, even 'this' on a long shot and nothing worked. It seems there has to be a way for the compiler to know this based on this error message which I get when I change the last sub to:

Protected Sub NotifyPropertyChanged(Of TResult)(propertyExpr As Expression(Of Func(Of TResult)))
    NotifyPropertyChanged(Me.GetPropertySymbol(propertyExpr))
End Sub

Data type(s) of the type parameter(s) in extension method 'Public Function GetPropertySymbol(Of TResult)(expr As System.Linq.Expressions.Expression(Of System.Func(Of CustInfo, TResult))) As String' defined in 'CustClasses.Extensions' cannot be inferred from these arguments. Specifying the data type(s) explicitly might correct this error.

The extension method being referred to is:

<Extension()>
Public Function GetPropertySymbol(Of T, TResult)(obj As T, expr As Expression(Of Func(Of T, TResult))) As String
    Return DirectCast(expr.Body, MemberExpression).Member.Name
End Function

So, as you can see the compiler correctly inferred what is going on, but still couldn't work with it. The only type that seems unknown is TResult, but that type should be easily inferred based on the other information in the extension method that is known.

It may be impossible to generically specify a class type in the procedure definition, in which case I'll modify the code every time I use it, but I'd rather not and learn something new in the process.

cjbarth
  • 4,189
  • 6
  • 43
  • 62
  • Note that as you currently have them declared, `propertyExpr` needs to be `As Expression(Of Func(Of , TResult))` to match `expr`. (I've now reviewed your question enough to know this is what you're trying to solve, in a generic way.) – Mark Hurd Nov 07 '12 at 14:10

2 Answers2

1

EDIT: Does not work as stated. You still need something to specify T As to allow it to call .NotifyPropertyChanged. Can you define an interface for this?


You could "demote" NotifyPropertyChanged(Of TResult) to a Shared method and supply Me, and thus allow its type to be implied, where it is called:

Shared Protected Sub NotifyPropertyChanged(Of T, TResult)(This As T, propertyExpr As Expression(Of Func(Of T, TResult)))
    This.NotifyPropertyChanged(This.GetPropertySymbol(propertyExpr))
End Sub

And now calls are

NotifyPropertyChanged(Me, myChangingPropertyName)

(Untested - but doesn't work anyway :-( )

Mark Hurd
  • 10,665
  • 10
  • 68
  • 101
  • I'm working at defining an interface for this as I think you might be on to something, but as of yet I've been unsuccessful. – cjbarth Nov 12 '12 at 21:22
1

Based on what @Mark Hurd answered, I've come up with something that makes working with properties much easier. I decided to create a class, decorated with MustInherit that Implements INotifyPropertyChanged and includes all the NotifyPropertyChanged code that is needed all set up with generics for maximum reuse and portability. I would love to hear some feedback on this solution. I don't see any issues with it, but I could easily be missing something.

Public MustInherit Class AutomaticNotfiyPropertyChanged
    Implements INotifyPropertyChanged

    Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

    Protected Sub NotifyPropertyChanged(ByVal Name As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Name))
    End Sub

    Protected Sub NotifyPropertyChanged()
        Dim myStackFrame As New StackFrame(1)
        Dim myChangingPropertyName As String = myStackFrame.GetMethod.Name.Split("_")(1)
        NotifyPropertyChanged(myChangingPropertyName)
    End Sub

    Protected Shared Sub NotifyPropertyChanged(This As AutomaticNotfiyPropertyChanged, Name As String)
        This.NotifyPropertyChanged(Name)
    End Sub

    Protected Overridable Sub NotifyPropertyChanged(Of TResult)(propertyExpr As Expression(Of Func(Of AutomaticNotfiyPropertyChanged, TResult)))
        NotifyPropertyChanged(GetPropertySymbol(propertyExpr))
    End Sub

    Protected Shared Sub NotifyPropertyChanged(Of T, TResult)(This As T, propertyExpr As Expression(Of Func(Of T, TResult)))
        Dim myParent As AutomaticNotfiyPropertyChanged = TryCast(This, AutomaticNotfiyPropertyChanged)

        If myParent IsNot Nothing Then
            AutomaticNotfiyPropertyChanged.NotifyPropertyChanged(DirectCast(myParent, AutomaticNotfiyPropertyChanged), This.GetPropertySymbol(propertyExpr))
        End If
    End Sub

End Class

Then calls to notify of a change in other properties would be NotifyPropertyChanged(Me, Function(x) x.myChangingProperty) or if you want to notify that the current property is changing all that is needed is NotifyPropertyChanged(). Additionally NotifyPropertyChanged(Me, "myChaningProperty") and NotifyPropertyChanged("myChangingProperty") will also work. Finally if NotifyPropertyChanged(Of TResult) is overridden to include the derived class, then a call like NotifyPropertyChanged(Function(x) x.myChaningProperty) will work too.

Also, I've found that NotifyPropertyChanged(Me, Function(x) x.myChaningProperty) will work just fine, and will be checked by the compiler. However, IntelliSense doesn't work for x. I find that strange since the compiler clearly knows what x is because it will even correct capitalization of variables. It isn't a deal-breaker, but overriding NotifyPropertyChanged(Of TResult) to reflect the current class will make IntelliSense work.

cjbarth
  • 4,189
  • 6
  • 43
  • 62
  • Your edit didn't change `myChangingPropertyName` to just `Name` in the `Protected Shared Sub`. – Mark Hurd Nov 12 '12 at 23:45
  • @MarkHurd, thanks for catching this. I've fixed the error and am using this code in my program successfully now. I appreciate your help. – cjbarth Nov 13 '12 at 03:28
  • You'll want to add ` _` to `Protected Sub NotifyPropertyChanged` to help ensure you get the right method. In fact the callers of that routine would need to apply it too to completely ensure this works... – Mark Hurd Nov 13 '12 at 03:46
  • @MarkHurd, I'm not sure why that would be required. Can you explain further? – cjbarth Nov 13 '12 at 04:18
  • When you compile with optimisation and execute without the debugger the JIT may inline methods: [An early Google hit](http://www.hanselman.com/blog/ReleaseISNOTDebug64bitOptimizationsAndCMethodInliningInReleaseBuildCallStacks.aspx); [When it doesn't happen](http://blogs.msdn.com/b/davidnotario/archive/2004/11/01/250398.aspx); [A StackOverflow link](http://stackoverflow.com/q/4660004/256431) – Mark Hurd Nov 13 '12 at 11:56
  • @MarkHurd, After reading that documentation is seems that the only one that I need to be worried about is `Protected Sub NotifyPropertyChanged()` because of the stack call. However, given that it is several lines long and is creating new objects, I don't think it is a candidate for inlining. I've been doing extensive testing in Release mode which would inline code and haven't had any issues. Am I missing something here? Do you know where I can find the inlining rules? What I've read so far supports my analysis. – cjbarth Nov 13 '12 at 15:33