为啥 WPF 支持绑定到对象的属性,但不支持绑定字段?

Posted

技术标签:

【中文标题】为啥 WPF 支持绑定到对象的属性,但不支持绑定字段?【英文标题】:Why does WPF support binding to properties of an object, but not fields?为什么 WPF 支持绑定到对象的属性,但不支持绑定字段? 【发布时间】:2022-01-11 10:22:58 【问题描述】:

我有一个 WCF 服务,它通过如下结构传递状态更新:

[DataContract]
public struct StatusInfo

    [DataMember] public int Total;
    [DataMember] public string Authority;

...
public StatusInfo GetStatus()  ... 

我像这样在 ViewModel 中公开一个属性:

public class ServiceViewModel : ViewModel

    public StatusInfo CurrentStatus
    
        get return _currentStatus; 
        set
         
            _currentStatus = value;
            OnPropertyChanged( () => CurrentStatus );
        
        

XAML 就像这样:

<TextBox Text="Binding CurrentStatus.Total" />

当我运行应用程序时,我在输出窗口中看到错误,表明找不到 Total 属性。我检查并仔细检查并正确输入。我突然想到这些错误特别表明找不到“属性”。因此,向结构添加属性使其工作正常。但这对我来说似乎很奇怪,WPF 无法处理与字段的单向绑定。从语法上讲,您在代码中以相同的方式访问它们,并且只为 StatusInfo 结构创建自定义视图模型似乎很愚蠢。我是否错过了有关 WPF 绑定的内容?您可以绑定到字段还是属性绑定是唯一的方法?

【问题讨论】:

【参考方案1】:

绑定一般对字段不起作用。大多数绑定部分基于 ComponentModel PropertyDescriptor 模型,该模型(默认情况下)适用于属性。这会启用通知、验证等(这些都不适用于字段)。

出于我无法解释的更多原因,公共领域是一个坏主意。它们应该是属性,事实。同样,可变结构是一个非常的坏主意。尤其重要的是,它可以防止意外的数据丢失(通常与可变结构相关联)。这应该是一个类:

[DataContract]
public class StatusInfo

    [DataMember] public int Total get;set;
    [DataMember] public string Authority get;set;

它现在将按照您认为的方式运行。如果你希望它是一个 immutable 结构,那没问题(当然,数据绑定只能是单向的):

[DataContract]
public struct StatusInfo

    [DataMember] public int Total get;private set;
    [DataMember] public string Authority get;private set;

    public StatusInfo(int total, string authority) : this() 
        Total = total;
        Authority = authority;
    

但是,我首先要质疑为什么这是一个结构。用 .NET 语言编写结构非常少见。请记住,WCF“mex”代理层无论如何都会在消费者处将其创建为一个类(除非您使用程序集共享)。


回答“为什么使用结构”回复(“未知(谷歌)”):

如果这是对我问题的回答,那么它在很多方面都是错误的。首先,值类型作为变量通常(首先)在堆栈上分配。如果它们被推到堆上(例如在数组/列表中),则与类的开销没有太大区别——一小部分对象头加上一个引用。结构应该总是small。具有多个字段的东西会过大,并且会破坏您的堆栈或由于 blitting 而导致缓慢。此外,结构应该是不可变的 - 除非您真的知道自己在做什么。

几乎所有代表对象的东西都应该是不可变的。

如果您正在访问数据库,与进入进程外并且可能通过网络相比,struct vs class 的速度不是问题。即使它有点慢,与正确处理相比毫无意义 - 即将对象视为对象。

作为 1M 个对象的一些指标:

struct/field: 50ms
class/property: 229ms

基于以下(速度差异在于对象分配,而不是字段与属性)。所以大约慢了 5 倍,但仍然非常非常快。由于这不会成为您的瓶颈,因此不要过早地优化它!

using System;
using System.Collections.Generic;
using System.Diagnostics;
struct MyStruct

    public int Id;
    public string Name;
    public DateTime DateOfBirth;
    public string Comment;

class MyClass

    public int Id  get; set; 
    public string Name  get; set; 
    public DateTime DateOfBirth  get; set; 
    public string Comment  get; set; 

static class Program

    static void Main()
    
        DateTime dob = DateTime.Today;
        const int SIZE = 1000000;
        Stopwatch watch = Stopwatch.StartNew();
        List<MyStruct> s = new List<MyStruct>(SIZE);
        for (int i = 0; i < SIZE; i++)
        
            s.Add(new MyStruct  Comment = "abc", DateOfBirth = dob,
                     Id = 123, Name = "def" );
        
        watch.Stop();
        Console.WriteLine("struct/field: "
                  + watch.ElapsedMilliseconds + "ms");

        watch = Stopwatch.StartNew();
        List<MyClass> c = new List<MyClass>(SIZE);
        for (int i = 0; i < SIZE; i++)
        
            c.Add(new MyClass  Comment = "abc", DateOfBirth = dob,
                     Id = 123, Name = "def" );
        
        watch.Stop();
        Console.WriteLine("class/property: "
                   + watch.ElapsedMilliseconds + "ms");
        Console.ReadLine();
    

【讨论】:

作为一名 C++ 程序员,我发现上面的 cmets 很难理解。我似乎也得出了不同的结论。例如,你说,“所以大约慢了 5 倍,但仍然非常非常快。”而我认为,“结构的使用快了大约 5 倍,但仍然非常非常慢。” @Daniel 叹了口气,我们又来了,“C++ 比任何东西都快,而且必须一直使用”(叹气)。在各种各样的应用程序中没有明显的区别,除了一个更容易正确之外。 我从未说过 C++ 更快!得出这样的结论表明你有一些严重的先入之见(或者可能是误解)。 “在各种各样的应用程序中没有明显的区别,除了一个更容易正确处理” - 所以我们同意,虽然 C++ 可能更快,但这并不重要 - 但是,C++ 更容易正确处理和这很重要。或者也许我只是误解了你所说的支持我的论点......确实叹息。 @Daniel 也许我误解了你的意思“作为一个 c++ 程序员......但仍然非常非常慢” 您的性能测试可能太短而无法正确测量 GC 开销。尝试对您的测试进行以下修改:pastebin.com/Ajkj0hdm - 现在类慢了 12 倍,因为 80% 的时间花在 GC 上。现在将 SIZE 提高到 10M 并观察“私有字节”:结构为 200 MB,类则攀升至 1 GB。【参考方案2】:

我只能猜测为什么它们只支持属性:也许是因为它似乎是 .NET 框架中的一个通用约定,从不公开可变字段 (probably to safeguard binary compatibility),并且他们以某种方式期望所有程序员都遵循相同的约定。

此外,尽管使用相同的语法访问字段和属性,但数据绑定使用反射,并且(所以我听说)必须以不同的方式使用反射来访问字段而不是访问属性。

【讨论】:

以上是关于为啥 WPF 支持绑定到对象的属性,但不支持绑定字段?的主要内容,如果未能解决你的问题,请参考以下文章

WPF 让普通 CLR 属性支持 XAML 绑定(非依赖属性),这样 MarkupExtension 中定义的属性也能使用绑定了

WPF PasswordBox不支持绑定解决方法

利刃 MVVMLight 4:绑定和绑定的各种使用场景

利刃 MVVMLight 4:绑定和绑定的各种使用场景

WPF:将列表动态绑定到(某些)对象的属性

wpf 只读属性 绑定