属性与变量为BYREF参数



我创建了一个实现INotifyPropertyChanged接口的基类。此类还包含一个通用函数SetProperty来设置任何属性的值并在必要时提高PropertyChanged事件。

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

然后我有一个课程,应该保存一些数据。为了简单起见,它仅包含一个属性(在此示例中)。

Public Class Item
    Public Property Text As String
End Class

然后,我有一个从基类继承并使用数据保存类的第三类。该第三类应该是WPF窗口的ViewModel。

我不列出RelayCommand类的代码,因为您可能自己都有一个实现。请记住,该类执行命令时执行给定函数。

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

然后我有使用ViewModel类作为DataContext的WPF窗口。

<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>

您可以看到,此窗口仅包含两个文本框和一个按钮。文本框绑定到属性Text1Text2,该按钮应该执行命令TestCommand

执行命令时,属性 Text1Text2均具有值。而且由于这两个属性都引发了PropertyChanged事件,因此应在我的窗口中显示这些值。

,但仅在我的窗口中显示了值" text2"。

属性Text1的值是" text1",但是在该属性获得其值之前,该属性的 PropertyChanged事件似乎是升高的。

是否有任何方法可以在我的基类中更改SetProperty功能以在属性获得其值后提高PropertyChanged

谢谢您的帮助。

实际发生了什么?

这不起作用,因为属性不像字段那样行事。

执行Me.SetProperty(Of String)(_text2, value)时,发生的是传递对字段_text2的引用,而不是其值,因此SetProperty函数可以修改参考中的内容,并修改了字段。

但是,当您执行Me.SetProperty(Of String)(_text1.Text, value)时,编译器会看到属性的Getter,因此它将首先调用 get get _text1 的属性,然后将引用传递到返回值作为参数。因此,当您的函数SetProperty接收ByRef参数时,它是getter的返回值,,而不是实际字段值

根据我在这里了解的内容,如果您说您的属性是Byref,则编译器将在退出函数调用时自动更改字段ref ...因此,这可以解释为什么事件发生后它会更改... p>这个其他博客似乎确认了这种奇怪的行为。

在C#中,等效代码不会编译。.net不愿意通过引用传递属性,因为像埃里克·利珀特(Eric Lippert)这样的人已经进入其他地方(我昏昏欲睡地回忆起埃里克(Eric这将需要一个怪异的解决方法或另一个,所有这些都有C#团队认为是不可接受的缺点)。

vb做到了,但是作为一个相当奇怪的特殊情况:我看到的行为是我期望的,如果它创建了一个临时变量,该变量是通过引用传递的,然后将其值分配给属性之后方法完成。这是一个解决方法(由埃里克·利普特(Eric Lippert)在下面的评论中确认,另请参见@martin verjans的出色答案),副作用对于任何不知道如何在.NET中实现byref/ref的人违反直觉。

当您考虑它时,它们不能使其正常工作,因为VB.NET和C#(以及F#,以及Ironpython等)必须相互兼容,因此VB ByRef参数必须与从C#代码传递的C#ref参数兼容。因此,任何解决方法都必须完全是呼叫者的责任。在理智的范围内,这将其限制为在呼叫开始之前和返回之后可以做的事情。

这是ECMA 335(通用语言基础架构)标准必须说的( ctrl f 搜索" byref"):

  • &sect; i.8.2.1.1&nbsp;托管指针和相关类型

    a 托管指针(§i.12.1.1.2)或 byRef (§i.8.6.1.3,§i.12.4.1.5.2),可以指向数组的复合类型的局部变量,参数,字段。...

换句话说,就编译器而言,ByRef storage As T实际上是存储位置中代码放置值的存储位置的地址。在运行时非常有效,但没有提供句法糖魔法的范围。属性 一对方法,一个getter和setter(当然只有一个或另一个)。

因此,如您所描述的那样,storageSetProperty()内获取新值,并且SetProperty()完成后,_text1.Text具有新值。但是编译器引入了一些神秘的恶作剧,这导致实际事件的实际顺序不是您所期望的。

结果,SetProperty不能以您编写的方式在Text1中使用。我已经测试的最简单修复程序是直接在Text1的设置器中调用OnPropertyChanged()

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

没有办法处理至少一点丑陋的方法。您可以像Text2一样给Text1一个常规的备份字段,但是您需要将其与_text1.Text同步。这比上述IMO丑,因为您必须将两者保持同步,并且在Text1设置器中仍然有额外的代码。

相关内容

  • 没有找到相关文章

最新更新