Why does WPF support binding to properties of an object, but not fields?

浪尽此生 提交于 2021-01-20 09:54:50

问题


I've got a WCF service that passes around status updates via a struct like so:

[DataContract]
public struct StatusInfo
{
    [DataMember] public int Total;
    [DataMember] public string Authority;
}
...
public StatusInfo GetStatus() { ... }

I expose a property in a ViewModel like this:

public class ServiceViewModel : ViewModel
{
    public StatusInfo CurrentStatus
    {
        get{ return _currentStatus; }
        set
        { 
            _currentStatus = value;
            OnPropertyChanged( () => CurrentStatus );
        }
    }    
}

And XAML like so:

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

When I run the app I see errors in the output window indicating that the Total property cannot be found. I checked and double checked and I typed it correctly. The it occurred to me that the errors specifically indicate that the 'property' cannot be found. So adding a property to the struct made it work just fine. But this seems odd to me that WPF can't handle one-way binding to fields. Syntactically you access them the same in code and it seem silly to have to create a custom view model just for the StatusInfo struct. Have I missed something about WPF binding? Can you bind to a field or is property binding the only way?


回答1:


Binding generally doesn't work to fields. Most binding is based, in part, on the ComponentModel PropertyDescriptor model, which (by default) works on properties. This enables notifications, validation, etc (none of which works with fields).

For more reasons than I can go into, public fields are a bad idea. They should be properties, fact. Likewise, mutable structs are a very bad idea. Not least, it protects against unexpected data loss (commonly associated with mutable structs). This should be a class:

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

It will now behave as you think it should. If you want it to be an immutable struct, that would be OK (but data-binding would be one-way only, of course):

[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;
    }
}

However, I would first question why this is a struct in the first place. It is very rare to write a struct in .NET languages. Keep in mind that the WCF "mex" proxy layer will create it as a class at the consumer anyway (unless you use assembly sharing).


In answer to the "why use structs" reply ("unknown (google)"):

If that is a reply to my question, it is wrong in many ways. First, value types as variables are commonly allocated (first) on the stack. If they are pushed onto the heap (for example in an array/list) there isn't much difference in overhead from a class - a small bit of object header plus a reference. Structs should always be small. Something with multiple fields will be over-sized, and will either murder your stack or just cause slowness due to the blitting. Additionally, structs should be immutable - unlesss you really know what you are doing.

Pretty much anything that represents an object should be immuatable.

If you are hitting a database, the speed of struct vs class is a non-issue compared to going out-of-process and probably over the network. Even if it is a bit slower, that means nothing compared to the point of getting it right - i.e. treating objects as objects.

As some metrics over 1M objects:

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

based on the following (the speed difference is in object allocation, not field vs property). So about 5x slower, but still very, very quick. Since this is not going to be your bottleneck, don't prematurely optimise this!

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();
    }
}



回答2:


I can only guess why they only support properties: perhaps because it seems to be a universal convention in the .NET framework never to expose mutable fields (probably to safeguard binary compatibility), and they somehow expected all programmers to follow the same convention.

Also, although fields and properties are accessed with the same syntax, data binding uses reflection, and (so I've heard) reflection must be used differently to access fields than to access properties.



来源:https://stackoverflow.com/questions/65570025/getting-a-binding-path-exception-inside-of-a-data-template

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!