`WithPropertyEvents` modifier to enable easy INotifyPropertyChanged implementations
- Dominant language
- No language data
- Stars
- 328
- Forks
- 71
- PR merge metrics
- No merged PRs in 30d
Description
# 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:
``` VB.NET
Property Name As String
Get
Return _Name
End Get
Set(value As String)
If value = _Name Then Return
_Name = value
OnPropertyChanged()
End Set
End Property
Protected Sub OnPropertyChanged( Optional propertyName As String = "")
RaiseEvent PropertyChanged(New PropertyChangedEventArgs(propertyName))
End Sub
```
Customers have been begging us for **years** for a solution to this problem but we've been reluctant for several reasons:
1. We're hesitant to bake a specific pattern into the language, as patterns can and do change.
2. We prefer general solutions to specific solutions and have been pursuing a general meta-programming feature that would encompass the `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` or `WithPropertyEvents`. 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 implement `INotifyPropertyChanged` without having to use expanded properties. It'll work something like this:
``` VB.NET
WithEvents Class Person
Property Name As String
End Class
```
Will translate to:
``` VB.NET
WithEvents Class Person
Private _Name As String
Property Name As String
Get
OnPropertyGet(NameOf(Name), _Name)
Return _Name
End Get
Set(value As String)
Dim oldValue = _Name
If Not OnPropertySetting(NameOf(Name), oldValue, value) Then Return
_Name = value
OnPropertySet(NameOf(Name), oldValue, value)
End Set
End Property
End Class
```
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 of `OnPropertySetting` can do several things:
* By returning `False` they could early exit the entire property.
* If the 3rd parameter (`value`) is `ByRef` they can change the value being set, e.g. (trim a string).
* Raise `INotifyPropertyChanging`.
* Log a change before it happens.
* Throw an exception if something is wrong.
In practice code leveraging this feature would look like this.
``` VB.NET
' Write once
Public Class ViewModel
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Overridable Sub OnPropertyChanged( Optional propertyName As String = "")
RaiseEvent PropertyChanged(New PropertyChangedEventArgs(propertyName))
End Sub
Protected Overridable Function OnPropertySetting(Of T)(propertyName As String, oldValue As T, newValue As T) As Boolean
Return Not Object.Equals(oldValue, newValue)
End Function
Protected Overridable Sub OnPropertySet(Of T)(propertyName As String, oldValue As T, newValue As T)
OnPropertyChanged(propertyName)
End Function
End Class
' In this new bright future most VB users would only need to write this class because the feature just looks up the method normally and all the plumbing can be implemented once in a base class.
WithEvents Class Person
Inherits ViewModel
Property FirstName As String
Property MiddleName As String
Property LastName As String
Property DateOfBirth As Date
End Class
```
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
* How do we avoid breaking changes? **The feature requires opt-in through some syntax.**
* Should it be a class modifier? **Yes, specifying the modifier on every property would be annoying.**
* If it's a class modifier, what should the keyword be?
* Does this work for structures? **No reason to disallow.**
* Does this work for `Shared` properties? **No reason to disallow.**
* Does this work for modules? **No reason to disallow.**
* Could we support other property "events" like:
* `OnPropertySetFault`--called when an auto-prop throws an exception. Its existence causes the body of the auto-prop to be wrapped in a `Try`/`Catch`/`Finally` block.
* Could we support other members like auto-implemented events? **No scenario for this.**
* Could we generalize this further to support callbacks in any member body in a class, e.g. `WithMethodEvents` or `OnMethodInvoked` and `OnMethodReturn` to support logging or other instrumentation?
* Should add another layer of precedence to this feature by first looking for a specifically named member to call, e.g. `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 the `Get` or `Set` method for that property.**
* This doesn't solve the repetitiveness of declaring Dependency properties. **I'm OK with that.**
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.