为什么WPF支持绑定对象的属性而不是字段?

29

我的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绑定的什么吗?您可以绑定到字段,还是属性绑定是唯一的方法?

2个回答

34

一般来说,绑定不适用于字段。大部分绑定都基于ComponentModel的PropertyDescriptor模型,在默认情况下使用属性。这可以启用通知、验证等功能(其中没有一个功能适用于字段)。

有更多原因我无法解释,公共字段是个坏主意。它们应该是属性。同样,可变结构体是个非常糟糕的想法。至少,这可以防止意外数据丢失(通常与可变结构体相关)。建议将其定义为类:

[DataContract]
public class StatusInfo
{
    [DataMember] public int Total {get;set;}
    [DataMember] public string Authority {get;set;}
}

现在它将按照您认为的方式运作。如果您希望它成为一个不可变结构体,那也可以(但数据绑定只能是单向的):

[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”代理层将在消费者端将其创建为类(除非使用程序集共享)。
回答“为什么使用结构体”的回复(“未知(Google)”):
如果那是对我的问题的回复,则在许多方面都是错误的。 首先,“值类型作为变量”通常分配在堆栈上(首先)。 如果将它们推入堆(例如在数组/列表中),则与类的开销相差不大-一个小的对象标题加上引用。 结构体应始终很小。带有多个字段的结构体将过大,会损坏您的堆栈,或者只是因blitting而导致缓慢。 此外,结构体应该是不可变的-除非您确实知道自己在做什么。
几乎任何表示对象的东西都应该是不可变的。
如果您正在访问数据库,则结构体与类的速度相比,与跨进程甚至可能通过网络的速度相比,都不是问题。即使稍微慢一点,也意味着与正确处理点(即将对象视为对象)相比毫无意义。
对于超过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();
    }
}

5
作为一名C++程序员,我发现你上面的评论非常难以理解。我得出了不同的结论。例如,你说:“因此慢了大约5倍,但仍然非常快。”而我认为,“使用结构体大约快了5倍,但仍然非常慢。” - Daniel Paull
10
@Daniel 叹气,我们又来了,“C++ 比所有其他语言都要快,必须始终使用”(叹气)。在许多应用程序中,除了其中一种显着更易于正确实现外,没有明显差异。 - Marc Gravell
1
我从未说过C++更快!得出这样的结论表明你有一些严重的先入为主(或者可能是误解)。"在各种应用中,除了一个明显更容易正确实现之外,没有什么明显的区别" - 所以我们达成了共识,尽管C++可能更快,但这并不重要 - 然而,C++使得正确实现更容易,这是非常重要的。或者也许我只是曲解了你的话来支持我的观点...唉。 - Daniel Paull
3
@Daniel,也许我误解了你所说的“作为一名C ++程序员......但仍然非常非常慢”的意思。 - Marc Gravell
3
您的性能测试可能太短,无法正确测量垃圾回收(GC)开销。请尝试修改您的测试:http://pastebin.com/Ajkj0hdm - 现在类慢了12倍,因为80%的时间花费在GC上。现在将SIZE增加到10M并观察“私有字节”:结构体为200MB,而类则攀升至1GB。 - Roman Starkov
显示剩余2条评论

0
我只能猜测为什么他们只支持属性:也许是因为在.NET框架中,似乎从来没有暴露可变字段(可能是为了保护二进制兼容性),并且他们希望所有程序员都遵循相同的约定。
此外,尽管字段和属性使用相同的语法进行访问,但数据绑定使用反射,并且(据我所知)必须以不同的方式使用反射来访问字段和访问属性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接