Open AnthonyDGreen opened 7 years ago
I don't know about others but in the cases where I've had to implement INotifyPropertyChanged, there were just a few properties that it was needed on. For that reason, I'd rather prefer to have the keyword applied to the property not on the class in order to allow a more specific implementation as needed.
For example, I would rather opt in to properties to receive notifications on like this
Public Class Person
Public Property WithEvents Name As String
Public Property Age As Integer
Public Property Gender As Gender
End Class
Instead of having to opt out of properties I don't want to receive notifications on.
That's my two cents on this issue.
I'm more a 'composition above inheritance' guy when possible. Is there a way we can avoid inheritance? I like the way Fody PropertyChanged is working with attributes/interface. Don't know how it's actually implemented though...
Inheritance is just an example. You could implement it on each class individually or use extension methods if you wanted. The feature really is 'compiler will call these methods at these times'. It's not particular about where the methods come from or what they do.
I'm with @franzalex and I would also prefer to have the keyword on property and not on the class.
Although I certainly welcome simplifying INotifyPropertyChanged
properties, this is actually not what bothers me the most. I have custom snippets for these, so I write them quite fast with necessary if-changed-then-raise code in setter. But I use to have always quite a few readonly properties for which I want to raise PropertyChanged
event as well.
One of my typical examples:
Public Class FooGridViewModel
Implements INotifyPropertyChanged
Private m_Records As List(Of Foo) = Nothing
Public Property Records() As List(Of Foo)
Get
Return m_Records
End Get
Set(ByVal value As List(Of Foo))
If Not Object.ReferenceEquals(m_Records, value) Then
m_Records = value
OnPropertyChanged(NameOf(Me.Records))
End If
End Set
End Property
Private m_SelectedRecord As Foo = Nothing
Public Property SelectedRecord() As Foo
Get
Return m_SelectedRecord
End Get
Set(ByVal value As Foo)
If Not Object.ReferenceEquals(m_SelectedRecord, value) Then
m_SelectedRecord = value
OnPropertyChanged(NameOf(Me.SelectedRecord))
OnPropertyChanged(NameOf(Me.IsRecordSelected))
OnPropertyChanged(NameOf(Me.CanEdit))
OnPropertyChanged(NameOf(Me.CanDelete))
End If
End Set
End Property
Public ReadOnly Property IsRecordSelected As Boolean
Get
Return m_SelectedRecord IsNot Nothing
End Get
End Property
Public ReadOnly Property CanEdit As Boolean
Get
Return Me.SelectedRecord IsNot Nothing AndAlso Me.SelectedRecord.Status = Status.Created
End Get
End Property
Public ReadOnly Property CanDelete As Boolean
Get
Return Me.SelectedRecord IsNot Nothing AndAlso Not Me.SelectedRecord.Status = Status.Finished
End Get
End Property
' ...
End Class
So with this proposition, I would still need to write setter for SelectedRecord
. And (worst part) I still need to track in which other property SelectedRecord
is referenced and raise PropertyChanged
event for it. With more complex code one can easily miss something. Using property from base class (inside readonly property) is "pain" as well.
I'm not sure if this is critical for others though. Or if the goal is also to solve such use cases. I also don't know if there is an easy universal solution for it. Fody seems to solve it, but to be honest I didn't try it yet, so I don't know how reliable it is with more complicated getters.
Maybe I was little bit off topic here, but as @AnthonyDGreen mentioned in #198, we have only one shot to make INotifyPropertyChanged
less painful.
I mean, technically you could in your override of OnPropertyChanged Select on propertyName
and raise the appropriate dependent property changed notifications. That seems like the most centralized place to do it. Actually having the language somehow understand dependencies between readonly and read-write properties seems... difficult and messy.
I'd love to see this happen (though looking at #198, maybe that would be a better solution).
I like that specific properties can opt-out of this behaviour, because, when I need to raise PropertyChanged
events, I occasionally have private properties in the same class and wouldn't want to have the event raised for those properties.
I'm also in agreement with @franzalex about having properties opt-in to this behaviour. Perhaps we could allow both styles? You could:
+1 for having the WithEvents
keyword on the property declaration itself and not on the class. If you do it on the class, you introduce the need for NoEvents
, and you create ambiguity for partial classes. (And in any case it is less logical, since WithEvents
has no effect on the class itself, only to turn each property to a WithEvents
property, and that isn't so valuable just to save a single keyword on the property declarations.) However, if it is extremely common that people will create classes with every single property notifying, then WithEvents
on the class does have merit.
I would recommend against re-using the WithEvents
keyword for this (i.e. WithEvents Property someProp As String
). The behaviour isn't the same as WithEvents -- you won't be choosing the property from the drop-down now and writing an event handler that Handles someProp.SomeEvent
.
I think WithPropertyEvents
is better, but creates weird repitition: WithPropertyEvents Property someProp As String
.
I propose as syntax Notifying Property someProp As String
. This accurately describes the fact that the property will be notifying about changes -- implementing INotifyPropertyChanged.
This assumes that this proposal is indeed the best solution for this proposal. However, there are other suggestions, which have to be weighed carefully -- see #219. (The whole idea is basically a single lone sub-scenario of AOP...)
@bandleader Check out #198. It's been rejected, but there's a lot of discussion in there about what keyword to use.
I've revised this idea a bit since the last time we discussed it in the VB LDM. Just haven't gotten the working prototype out there. I'm pretty excited about it.
Hey guys. I'm likely going to withdraw this proposal in favor for what's described by #282. Please check it out.
If possible I wouldn't put the keyword on either the class or the property, instead I would put it the same place that Handles
does, on a method, so the new keyword should be HandlesPropertySetters , with a couple of pseudo events.
Class Person
Property Name As String
Property NickName As String
Protected Sub OnPropertyChanged_BeforeSet(propertyName As String, currentValue as String, ByRef pendingValue as String) HandlesPropertySetters Name.BeforeSet
'whatever you want to do before it is changed
End Sub
Protected Sub OnPropertyChanged_AfterSet(propertyName As String, oldValue as String, ByRef newValue as String) HandlesPropertySetters Name.AfterSet, NickName.AfterSet
'whatever you want to do after it is changed
End Sub
End Class
Which the compiler re-rewrites as :
Class Person
Property Name As String
Get
Return _Name
End Get
Set
OnPropertyChanged_BeforeSet(NameOf(Name), _Name, value)
Dim oldValue = _Name
_Name=value
OnPropertyChanged_AfterSet(NameOf(Name), oldValue, _Name)
End Set
End Property
Property NickName As String
Get
Return _NickName
End Get
Set
Dim oldValue = _Name
_NickName =value
OnPropertyChanged_AfterSet(NameOf(NickName), oldValue, _NickName )
End Set
End Property
Protected Sub OnPropertyChanged_BeforeSet(propertyName As String, currentValue as String, ByRef pendingValue as String)
'whatever you want to do before it is changed
End Sub
Protected Sub OnPropertyChanged_AfterSet(propertyName As String, oldValue as String, ByRef newValue as String)
'whatever you want to do after it is changed
End Sub
End Class
Note that NickName doesn't have a before handler (and there should be a BeforeGet but this is long as it is), and this is also easily extensible if you want several BeforeSet or AfterSet handlers, just add them in lexicographical order (i.e. position in the source tree), and they get added in into the property in the right place.
Scenario
INotifyPropertyChanged
is essential for modern UI patterns like MVVM. Today developers must add code to all of their properties to take advantage of the interface and that is repetitive and erodes the value of automatically implemented properties. Here is a typical pattern:Customers have been begging us for years for a solution to this problem but we've been reluctant for several reasons:
INotifyPropertyChanged
problem.But, in the last VB LDM, VB MVP @KlausLoeffelmann was on campus as our guest and convinced us to get over it, so I'm pulling this proposal out of moth-balls.
Proposal
I proposal a new class modifier, tentatively
WithEvents
orWithPropertyEvents
. When this modifier is present the code generated for auto-implemented properties will invoke a set of pseudo-partial methods. They're not true partial methods for several reasons, but are like partial methods in one particular way--calls to them are only emitted if they have been defined in user code. If user code implements these properties they'll be called. Users can easily leverage this to implementINotifyPropertyChanged
without having to use expanded properties. It'll work something like this:Will translate to:
Essentially auto-props will now have well-known places where users can plug in other things. Normal overload resolution will be performed on the found methods so if the user wants to make things generic or strongly-typed they can.
The idea behind the
OnPropertySetting
call is two fold. It's very common for properties to disregard redundant sets to their current value. The implementer ofOnPropertySetting
can do several things:False
they could early exit the entire property.value
) isByRef
they can change the value being set, e.g. (trim a string).INotifyPropertyChanging
.In practice code leveraging this feature would look like this.
Undoubtedly there will be a case where someone wants to opt-out of this functionality. I propose that another new member modifier,
NoEvents
, can be specified on a property to opt it out of the default behavior.Discussion points
Shared
properties? No reason to disallow.OnPropertySetFault
--called when an auto-prop throws an exception. Its existence causes the body of the auto-prop to be wrapped in aTry
/Catch
/Finally
block.WithMethodEvents
orOnMethodInvoked
andOnMethodReturn
to support logging or other instrumentation? <Let's discuss>OnFirstNamePropertySetting
,OnFirstNamePropertySet
, etc. and only calling the general method if a more specific method isn't implemented? This would allow for some special handling of one-offs, though arguable such functionality would best be written in theGet
orSet
method for that property.