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

2

29

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?

Thorite answered 9/5, 2009 at 3:4 Comment(0)
A
35

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();
    }
}
Arum answered 9/5, 2009 at 9:59 Comment(7)
As a C++ programmer I find your comments above very hard to fathom. I also seem to arrive at different conclusions. For example, you say, "So about 5x slower, but still very, very quick." Whereas I see it as, "The use of structs is about 5x faster, but still very, very slow."Drawbridge
@Daniel sigh, here we go again, "C++ is faster than everything, ever, and must be used at all times" (sigh). In a wide variety of applications there is no appreciable difference, other than one being significantly easier to get right.Arum
I never said C++ was faster! Concluding such indicated that you have some serious preconceptions (or maybe misconceptions). "In a wide variety of applications there is no appreciable difference, other than one being significantly easier to get right" - so we agree, although C++ may be faster, this is not important - however, C++ makes it easier to get it right and this is significant. Or maybe I just misinterpreted what you said to support my argument... Sigh indeed.Drawbridge
@Daniel maybe I misunderstood your meaning of "as a c++ programmer ... but still very, very slow"Arum
There was an "I also" in the middle that disconnects the two opinions.Drawbridge
Your performance test is probably too short to properly measure the GC overhead. Try this modification of your tests: pastebin.com/Ajkj0hdm - now classes are 12x slower, because 80% of the time is spent in GC. Now crank the SIZE up to 10M and watch the "private bytes": 200 MB for structs, climbing to 1 GB for classes.Suspicion
And now we can't bind to ValueTuple fields in XAML because mutable, fields on a struct are a very bad idea :|Darendaresay
O
0

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.

Obscenity answered 16/5, 2009 at 15:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.