Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Property vs. Variable as ByRef parameter

I created a base class that implements the INotifyPropertyChanged interface. This class also contains a generic function SetProperty 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 is supposed to hold some data. For the sake of simplicity it only contains 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 holding class. This third class is supposed to be a ViewModel for a WPF window.

I don't list the code for the RelayCommand class, since you probably all have an implementation yourself. Just keep in mind, that this class executes the given 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 TextBoxes and a button. The TextBoxes are bound to the properties Text1 and Text2 and the button is supposed to execute the command TestCommand.

When the command is executed both properties Text1 and Text2 is given a value. And since both properties raise the PropertyChanged event, these values should be shown in my window.

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

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

Is there any way to change the SetProperty function in my base class to raise the PropertyChanged after the property got its value?

Thank you for your help.

like image 720
Nostromo Avatar asked Nov 28 '16 14:11

Nostromo


People also ask

What is the meaning of passing a variable by using ByRef method?

Passing ByRef or ByVal indicates whether the actual value of an argument is passed to the CalledProcedure by the CallingProcedure, or whether a reference (called a pointer in some other languages) is passed to to the CalledProcedure.

What is the difference between ByVal and ByRef in VB net?

The advantage of passing an argument ByRef is that the procedure can return a value to the calling code through that argument. The advantage of passing an argument ByVal is that it protects a variable from being changed by the procedure.

What does ByRef mean in pseudocode?

ByRef in means that a reference to the original value will be sent to the function. It's almost like the original value is being directly used within the function. Operations like = will affect the original value and be immediately visible in the calling function .

What is default ByVal or ByRef?

The default is ByVal , but be sure you understand what passing by value and by reference actually means.


2 Answers

What actually happens ?

This doesn't work because the properties don't behave as fields do.

When you do Me.SetProperty(Of String)(_text2, value), what happens is that the reference to the field _text2 is passed instead of its value, so the SetProperty function can modify what's inside the reference, and the field is modified.

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

From what I understood here, if you say that your property is ByRef, the compiler will automatically change the field ref when you exit the function call... So that would explain why it's changing after your event...

This other blog seems to confirm this strange behavior.

like image 122
Martin Verjans Avatar answered Sep 18 '22 04:09

Martin Verjans


In C#, the equivalent code wouldn't compile. .NET isn't comfortable passing properties by reference, for reasons which folks like Eric Lippert have gone into elsewhere (I dimly recall Eric addressing the matter vis a vis C# somewhere on SO, but can't find it now -- loosely speaking, it would require one weird workaround or another, all of which have shortcomings that the C# team regards as unacceptable).

VB does it, but as a rather strange special case: The behavior I'm seeing is what I would expect if it were creating a temporary variable which is passed by reference, and then then assigning its value to the property after the method completes. This is a workaround (confirmed by Eric Lippert himself below in comments, see also @Martin Verjans' excellent answer) with side effects that are counterintuitive for anybody who doesn't know how byref/ref are implemented in .NET.

When you think about it, they can't make it work properly, because VB.NET and C# (and F#, and IronPython, etc. etc.) must be mutually compatible, so a VB ByRef parameter must be compatible with a C# ref argument passed in from C# code. Therefore, any workaround has to be entirely the caller's responsibility. Within the bounds of sanity, that limits it to what it can do before the call begins, and after it returns.

Here's what the ECMA 335 (Common Language Infrastructure) standard has to say (Ctrl+F search for "byref"):

  • §I.8.2.1.1   Managed pointers and related types

    A managed pointer (§I.12.1.1.2), or byref (§I.8.6.1.3, §I.12.4.1.5.2), can point to a local variable, parameter, field of a compound type, or element of an array. ...

In other words, as far as the compiler is concerned, ByRef storage As T is actually the address of a storage location in memory where the code puts a value. It's very efficient at runtime, but offers no scope for 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 the new value inside SetProperty(), and after SetProperty() completes, _text1.Text has the new value. But the compiler has introduced some occult shenanigans which cause the actual sequence of events not to be what you expect.

As a result, SetProperty cannot be used in Text1 the way you wrote it. The simplest fix, which I have tested, is to call OnPropertyChanged() directly in the setter 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's no way to handle this that isn't at least a little bit ugly. You could give Text1 a regular backing field like Text2 has, but then you'd need to keep that in sync with _text1.Text. That's uglier than the above IMO because you have to keep the two in sync, and you still have extra code in the Text1 setter.

like image 39
15ee8f99-57ff-4f92-890c-b56153 Avatar answered Sep 19 '22 04:09

15ee8f99-57ff-4f92-890c-b56153