Property vs. Variable as parameter ByRef

I created a base class that implements the INotifyPropertyChanged interface. This class also contains the general SetProperty function to set the value of any property and raise the PropertyChanged event if necessary.

 Public Class BaseClass Implements INotifyPropertyChanged Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged Protected Function SetProperty(Of T)(ByRef storage As T, value As T, <CallerMemberName> Optional ByVal propertyName As String = Nothing) As Boolean If Object.Equals(storage, value) Then Return False End If storage = value Me.OnPropertyChanged(propertyName) Return True End Function Protected Overridable Sub OnPropertyChanged(<CallerMemberName> Optional ByVal propertyName As String = Nothing) If String.IsNullOrEmpty(propertyName) Then Throw New ArgumentNullException(NameOf(propertyName)) End If RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName)) End Sub End Class 

Then I have a class that should contain some data. For simplicity, it contains only one property (in this example).

 Public Class Item Public Property Text As String End Class 

Then I have a third class that inherits from the base class and uses the data storage class. This third class should be a ViewModel for the WPF window.

I do not list the code for the RelayCommand class, since you probably all have an implementation on your own. Just keep in mind that this class performs this function when the command is executed.

 Public Class ViewModel Inherits BaseClass Private _text1 As Item 'data holding class Private _text2 As String 'simple variable Private _testCommand As ICommand = New RelayCommand(AddressOf Me.Test) Public Sub New() _text1 = New Item End Sub Public Property Text1 As String Get Return _text1.Text End Get Set(ByVal value As String) Me.SetProperty(Of String)(_text1.Text, value) End Set End Property Public Property Text2 As String Get Return _text2 End Get Set(ByVal value As String) Me.SetProperty(Of String)(_text2, value) End Set End Property Public ReadOnly Property TestCommand As ICommand Get Return _testCommand End Get End Property Private Sub Test() Me.Text1 = "Text1" Me.Text2 = "Text2" End Sub End Class 

And then I have my WPF window that uses the ViewModel class as its DataContext .

 <Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfTest" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <local:ViewModel /> </Window.DataContext> <StackPanel Orientation="Horizontal"> <TextBox Text="{Binding Text1}" Height="24" Width="100" /> <TextBox Text="{Binding Text2}" Height="24" Width="100" /> <Button Height="24" Content="Fill" Command="{Binding TestCommand}" /> </StackPanel> </Window> 

As you can see, this window contains only two text blocks and a button. Text fields are bound to the Text1 and Text2 , and the button should execute the TestCommand command.

When the command is executed, the Text1 and Text2 get a value. And since both properties raise the PropertyChanged event, these values โ€‹โ€‹should appear in my window.

But in my window only the value "Text2" is displayed.

The value of the Text1 property is "Text1", but it seems that the PropertyChanged event for this property occurs before the property has received its value.

Is there a way to change the SetProperty function in my base class to raise a PropertyChanged after the property has received its value?

Thank you for your help.

+6
source share
2 answers

What is really going on?

This does not work because properties do not behave like fields.

When you execute Me.SetProperty(Of String)(_text2, value) , what happens is that instead of the field, the value is passed to the _text2 field, so the SetProperty function can change what is inside the link, and the field has been changed.

However, when you execute Me.SetProperty(Of String)(_text1.Text, value) , the compiler sees the getter for the property, so it will first call the Get property of the _text1 property and then pass the return value reference as a parameter. Therefore, when your SetProperty function receives a ByRef parameter, this is the return value from the receiver, not the actual value of the field.

From what I understood here , if you say that your property is ByRef, the compiler will automatically change the ref field when you exit the function call ... So this explains why it changes after your event ...

This other blog seems to confirm this strange behavior.

+4
source

In C #, the equivalent code will not compile .. NET is not comfortable passing properties by reference, for reasons why people like Eric Lippert have gone elsewhere (I vaguely recall that Eric turned to this question regarding C # where something on SO, but cannot find it now - this will require one strange workaround or another, all of which have flaws that the C # team considers unacceptable).

VB does this, but as a rather strange special case: the behavior that I see is what I would expect if it would create a temporary variable that is passed by reference, and then assigns its value to the property after the method is completed. This is a workaround (confirmed by Eric Lippert himself in the comments below, see also the wonderful @Martin Verjans answer) with side effects that contradict each other who doesn't know how byref / ref implemented in .NET.

When you think about it, they cannot make it work properly, because VB.NET and C # (both F #, IronPython, etc.) must be mutually compatible, so the VB byref parameter must be compatible with the argument C # ref passed from C # code. Therefore, any workaround should be fully responsible for the call. Within common sense, this limits him to what he can do before the start of the call, and after his return.

Here the ECMA 335 standard (Common Language Infrastructure) should say ( Ctrl + F search for byref "):

  • & sect; I.8.2.1.1 Managed Pointers and Associated Types

    A managed pointer (ยงI.12.1.1.2) or byref (ยงI.8.6.1.3, ยงI.12.4.1.5.2) can specify a local variable, parameter, field of a composite type, or an array element ....

In other words, for the compiler, ByRef storage As T is actually the address of the storage location in memory where the code places the value. It is very effective at runtime, but does not offer the possibility of syntactic sugar magic with getters and setters. A property is a pair of methods, a getter and a setter (or just one or the other, of course).

So, as you describe, storage gets a new value inside SetProperty() , and after SetProperty() completes, _text1.Text has a new value. But the compiler introduced some occult frauds that lead to the fact that the actual sequence of events will not be what you expect.

As a result, SetProperty cannot be used in Text1 in the way you wrote it. The simplest solution that I checked is to call OnPropertyChanged() directly in the installer for Text1 .

 Public Property Text1 As String Get Return _text1.Text End Get Set(ByVal value As String) _text1.Text = value Me.OnPropertyChanged() End Set End Property 

There is no way to handle this, which is at least a little ugly. You can give Text1 correct support field, for example Text2 , but then you will need to synchronize it with _text1.Text . This is uglier than the previous IMO, because you have to synchronize the two, and you still have additional code in the Text1 installer.

+3
source

Source: https://habr.com/ru/post/1012700/


All Articles