Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Introduce hidden backing store for complex properties in C#. #7614

Closed
TrevyBurgess opened this issue Dec 19, 2015 · 10 comments
Closed

Comments

@TrevyBurgess
Copy link

1. Background

In computer programming the state of an object is stored in data fields. This allows callers to view and modify the state of the object at will.

This had two drawbacks:

  1. Since the fields are uncontrolled, we have to validate the data every time we need to use it. (This isn’t essential for private fields, but data corruption is possible.)
  2. We don't know when fields are updated, and we can't perform actions based on that change.

To overcome the above problems, some people use 'get' and 'set' methods to hide the underlying field. This was cumbersome and unnatural. As a result, C# introduced properties.

2. C# Properties

C# properties encapsulate the state of an object, as seen by external callers. The external caller could now reference the state of an object in a more natural way, while still ensuring a consistent state.

public class SomeDataModel
{
    private string _MyProperty;

    public string MyPropery
    {
        get
        {
            return _MyProperty;
        }

        set
        {
            // Add validation code here
            _MyProperty = value;
        }
    }
}

With properties you can:

  • Place error-checking code in one location
  • Raise events when a property changes
  • Create calculated properties
  • Is required for WPF applications when using the MVVM pattern.
  • Used by Visual Studios to do reference counting.

3. Problem

Unfortunately, with standard properties we have four problems:

  1. Broken encapsulation. We can easily bypass validation and break encapsulation by using _MyProperty directly within the class.
  2. Infinite recursion. If you're not careful and mix using _MyProperty and MyProperty, you might end up with one calling the other endlessly (I accidentally assigned a value to MyProperty, instead of _MyProperty in the set).
  3. Refactoring. Since we have two items referring to the same logical state, added complexity is introduced when the time to refactor the code comes.
  4. The maintenance costs increase as the number of properties increase. This can be seen in WPF programs that use the MVVM model. It can be a pain maintaining 30 public properties, referencing 30 private fields that do nothing but store state. (And of course StyleCop will want you to give XML comments to all 30 backing fields.)

Example:

For work I was required to create a WPF application with data binding. As a result, I had to create model classes that had no real functionality other than to store property definitions, and a way to update the UI when a property changed.

public class SomeDataModel
{
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Imagine having 30 of these. Then imaging having to rename them.
    /// Putting the private field under the method offered pseudo encapsulation,
    /// and allowed the property to still have an XML comment.
    /// </summary>
    public string MyPropery
    {
        get
        {
            return _MyProperty;
        }

        set
        {
            // Add validation code here
            _MyProperty = value;
            OnStateChanged();
        }
    }
    private string _MyProperty;

    protected void OnStateChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Therefore, C# properties don't encapsulate state in any object oriented way.
There is an exception to this:

public string MyProperty1 { get; set; }

public string MyReadOnlyProperty1 { get; private set; }

With this construct, the compiler creates a hidden field that holds the state. Unfortunately, this is little more than a field with a few added benefits.

As a work-around, I created a custom base class to manage state: http://www.codeproject.com/Articles/875224/Encapsulating-property-state-in-MVVM-WPF-applicati.

4. C# Properties – With state encapsulation

To enable true OOP properties, we need to be able to encapsulate state. I would like to propose a possible way of encapsulating state.

Dedicated keyword: $state

We can introduce a keyword to represent state. The compiler creates a hidden private class-level field to store the state.

The keyword $state is only valid within the body of a get or set.

Used within the class, it would look like this:

public class MainViewModel
{
    // Constructor
    public MainViewModel(string newProperty)
    {
        this.MyProperty = newProperty;
    }

    public string MyProperty
    {
        get
        {
            return $state;
        }    
        set
        {
            if (!$state.Equals(value))
            {
                $state = value;
                OnStateChanged();
            }
        }
    }

    // -------------
    // Property list
    // -------------

    public string SomeMethod1()
    {
        // Syntax error. $state means nothing within this context.
        return $state;
    }

    public string SomeMethod2()
    {
        return MyProperty;
    }

    protected void OnStateChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Note: The compiler only creates the hidden field when the $state keyword is used.

5. Conclusion

This is not a replacement for private fields.

Instead, this is for times when:

  • Strict control of property state is necessary
  • Private fields serve no function and only get in the way of writing succinct code.
@HaloFour
Copy link

TL;DR version:

Don't wanna have to define a backing field for a slightly-more-complicated-than-an-auto-property, so add contextual identifier $state which the compiler uses to automatically create and reference a backing field from within property accessor methods.

In my opinion I don't see a big need for this. Encapsulation is controlled by the type, not the members within the type. The rest of the complaints are completely valid and serve to indicate problems with tooling.

@svick
Copy link
Contributor

svick commented Dec 19, 2015

The syntax $state doesn't feel C#-ish to me. Also, I think it's too implicit: e.g. it's too easy to copy & paste something like OnStateChanged($state); to a property that didn't use $state before, thus introducing a bug.

Because of that, I think #850 is better. As another advantage, it lets you specify the type of the backing field (e.g. to use Lazy<T>).

@bondsbw
Copy link

bondsbw commented Dec 23, 2015

@HaloFour

Encapsulation is controlled by the type, not the members within the type.

This proposal (as well as #850) advocates for an enhanced level of encapsulation, which I feel could be very useful.

Consider how you would communicate to your team of developers that they must use the setter for property SelectedCustomer instead of directly setting the field _selectedCustomer, because the property setter does extra work like change notification or causes a recalculation of some kind. A comment won't work (it might not be seen). Property-scoped fields is a good solution.

Consider why we even have the private modifier, given that we have internal. Developers for a project have full access to its code, so if they want they could change anything that is private to some other access. Then why is it necessary to restrict access further than internal? Because it communicates intended use and makes it harder to screw up.

Same applies here. Prevent mistakes early, instead of finding them once the system is well into production.

@HaloFour
Copy link

Even with that argument I would certainly prefer #850 over this proposal. This proposal would create a form of identifier otherwise completely alien to the C# language. It also seeks to change the charter of auto-implemented properties which were designed specifically to make property-declaration more concise when no additional logic is required in the property accessors.

@bondsbw
Copy link

bondsbw commented Dec 24, 2015

@HaloFour Agreed.

@paulomorgado
Copy link

Yet another way of making backing fields local to properties.

I compile-time-only attributes are introduced, this can be achieved by an analyzer.

[BackingField(nameof(Property), AllowInConstuctor = true)]private readonly int field;

public int Property
{
    get { return this.field; }
    set { this.field = value; }
}

@lachbaer
Copy link
Contributor

Me too, I don't really see a big advantage over #850 here. Only makes things fuzzier an not so clear to read.

@TrevyBurgess
Copy link
Author

Regarding #850.

This person is talking about an anonymous class. That's the logical equivalent of a lambda function. Think of it as a lambda class.

When a basic property is credited, the compiler creates a hidden field to hold the data. I just want that field exposed within the property. It is not an inner class. It is an encapsulated field. This is what properties should be, but without any data leakage.

A property is not syntactic sugar. It is a design a pattern. The guiding principle behind creating a property is to encapsulate an attribute of a class (such as height and weight), to control data access, to ensure the state is always valid, and to notify listeners of updates. Only by exposing the backing store internally can this principle be taken to its logical conclusion.

Finally, the reason I reject the other suggestion is because it violates the S.O.L.I.D. design pattern for properties.

For the record, lambda classes do have value. Normally, an inner class is created, instantiated, and then exposed through a field. A lambda class makes sense here, since only one instance of the class is needed.

However, from a design point of view, properties and lambda classes are separate entities and must not be confused.

Here is my suggestion of how both would look.

// Property
public int MyProperty 
(
    get
    (
      // $state is only valid here. The type is that of value  
      return $state;
    )
    set
    (
      // $state is only valid here. The type is that of value
      $state = value;
    )
)
// Lamda class (Note the lambda assignment operator.)
public class MyLambdaClasd =>
(
);

// Without lambda classes, you would need the below code
namespace Intuit.PCG.ProFile.Test.GuiAutomation.Core.Dialogs
{
    /// <summary>
    /// For:
    /// FileOpenDialog,
    /// CarryForwardFileDialog
    /// </summary>
    public class FileOpenDialog : WindowBase
    {
        public FileOpenDialog()
        {
            Controls = new KnownControls();
        }

        public KnownControls Controls { get; }

        public class KnownControls
        {
            private WindowBase window;
            internal KnownControls(WindowBase window) { this.window = window; }

            public ComboBox FileName
            {
                get
                {
                    var tabControl = window.FindByClass<Tab>("TGPTabControl", false);
                    return tabControl.FindByClass<ComboBox>("TComboBox", false,         System.Windows.Automation.TreeScope.Descendants);
                }
            }
        }
    }
}

With lambda classes, you would have

// Without lambda classes, you would need the below code
namespace Intuit.PCG.ProFile.Test.GuiAutomation.Core.Dialogs
{
    /// <summary>
    /// For:
    /// FileOpenDialog,
    /// CarryForwardFileDialog
    /// </summary>
    public class FileOpenDialog : WindowBase
    {
        public FileOpenDialog()
        {
            // Not needed.
            // SearchArgs = new WindowSearchArgs(SearchMethod.ClassName, "TDlgFileOpen");
        }

        // Lambda class
        // Unfortunately, just like static classes, lambda classes can only have default constructors.
        public class Controls =>
        {
            private WindowBase window;
            internal KnownControls(WindowBase window) { this.window = window; }

            public ComboBox FileName
            {
                get
                {
                    var tabControl = window.FindByClass<Tab>("TGPTabControl", false);
                    return tabControl.FindByClass<ComboBox>("TComboBox", false,         System.Windows.Automation.TreeScope.Descendants);
                }
            }
        };
    }
}

Regarding @svick comment about it being unC#ish, $state isn't much different than the 'this' keyword and the 'value' keyword we already use. Just like 'value', $state is scoped to just the property. As I mentioned before, having an external data store only breaks encapsulation.

@DaniWe
Copy link

DaniWe commented Nov 19, 2018

How about 'self' as backing store. More C-like than $state. We already have an implicit variable here: 'value'.

public MyType MyProperty {
get { return self; }
set { self = value; }
}
Would be the default behaviour.

@jnm2
Copy link
Contributor

jnm2 commented Apr 4, 2020

Using a backing field without naming it was considered in https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-01.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants