dotnet / csharplang

The official repo for the design of the C# programming language
11.45k stars 1.02k forks source link

Two-way binding of a property using expression body expression syntax #1327

Closed francois-dorin closed 6 years ago

francois-dorin commented 6 years ago

Expression-bodied members were introduced in C#6 and provide a new syntax to provide a get implementation for read-only properties.

class MyClass
{
   public int MyAttr {get; set;}
}

class MyClassDecorator
{
   private MyClass myClass = new MyClass();

   public int MyAttr => attr; // Provide only a get property
}

But it does not allow to set the attribute

MyClassDecorator instance = new MyClassDecorator();
instance.MyAttr = 5; // does not compile since MyAttr provide only a getter

It would be very usefull in some scenario if this behavior were allowed (for example, when implementing a decorator pattern).

I think it will not be possible to use the exact syntax introduced in C#6 since it will change the behavior of existing code, but we can imagine a new syntax :

class MyClassDecorator
{
   private MyClass myClass = new MyClass();

   public int MyAttr <=> myClass.Attr; // Proposition: extending => notation to a two way binding (<=>)
   public int MyAttr ref myClass.Attr; // Proposition: using ref keyword
   public int MyAttr => ref myClass.Attr; // Proposition: using => notation with ref keyword
   public int MyAttr bindto myClass.Attr; // Proposition: introducing a new keyword (bindto for the example, but can be anything else)
}

It will be more compact than the current way to achieve this:

class MyClassDecorator
{
   private MyClass myClass = new MyClass();

   public int MyAttr
   { 
      get { return myClass.Attr; }
      set { myClass.Attr = value; }
   }
}

I also think that code can be more readable and maintenable by using coding conventions like defining "external" properties (i.e., not defined directly in the class) using expression body members and definined "internal" ones (i.e., defined directly in the class) using the classical approach:

class MyClassDecorator
{
   private MyClass myClass = new MyClass();
   private string myString;

   // External properties. They references a value not directly stored in the object instance
   public int MyAttr <=> myClass.MyAttr;
   public int MySecondAttr <=> myClass.MySecondAttr;
   public int MyAnotherAttr <=> myClass.MyAnotherAttr;
   public int MyLastAttr <=> myClass.MyLastAttr;

   // Internal property. MyString references an object stored directly in the object instance
   public string MyString
   { 
      get { return myString; }
      set { myString = value; }
   }
}
Joe4evr commented 6 years ago

Duplicate of #1278.

Take special note of Gafter's comment in that thread:

If the syntax for a read-write property is shorter than the syntax for a read-only property, then we have failed, and the changes would not encourage programming with immutable data as we intended.

Also, the syntax for a normal auto-property is good enough as it is. And you can always use => on get/set as well since 7.0 if you want some space-saving.

public string MyString { get => myString; set => myString = value; }
francois-dorin commented 6 years ago

Thank you for the link to #1278. I made some search by can't find it.

I don't need a new syntax for saving space (saving space is just a positive side-effect). I propose it since it can provide usefull information in some scenario. And I gave a use case with decorator pattern.

Auto-property cannot be used to achieve what I proposed. For what I called "internal" properties, it is true, but not for "external" ones which require additionnal logic.

One advantage of my proposal is to bind setter and getter on the same expression. When using => as you suggested, we can always bind to an expression for the getter and to another one in the setter.

Furthermore, using a two-way binding to manage such properties add additional meaning to the property, it is not just a syntaxic sugar: the property is delegated to a component of the object instead of being handled by the object itself. And this news semantic can be very usefull for maintanability,

Inspired by auto-property principle, we could name this kind of properties "delegated-property".

DavidArno commented 6 years ago

@francois-dorin,

If I'm trying to create a delegation wrapper over a class with 100 members, being able to write:

public int MySecondAttr <=> myClass.MySecondAttr;

instead of:

public int MySecondAttr
{
    get => myClass.MySecondAttr;
    set => myClass.MySecondAttr = value;
}

100 times would save some typing. But it's still a lot of typing and testing needed. What I'd really want is language support for auto-generating that delegation mapping [see #234].

yaakov-h commented 6 years ago

How about #107?

DavidArno commented 6 years ago

@yaakov-h,

As far as I'm aware (from reading what @CyrusNajmabadi has said on the subject), VS tooling is proving a blocker to the idea of implementing any form of source generator feature and that it's therefore effectively a dead idea.

Very happy to be wrong on this though as that feature is indeed the likely best solution to eg the delegation pattern.

francois-dorin commented 6 years ago

@yaakov-h

I'm not sure it is the right solution. From my point of view, code generation via source generator, even if it can be very useful, must be considered carefully.

Code generation already exists under a certain form. For example, we can generate .cs file from xml schema. But it is always new classes that are generated (and optionaly which can be manualy extended via the use of "partial class").

If my understanding is right, it will be possible to extend and/or replace elements of a class (method, property, ...) with a source generator. That is to say the code written can be different from the code executed. I can imagine the source of headache during a debugging session... (and which code is displayed to the developper, the written one or the executed one?)

@DavidArno

100 times would save some typing. But it's still a lot of typing and testing needed. What I'd really want is language support for auto-generating that delegation mapping [see #234].

I understand your use case. It is sure having a mechanism like delegation will be very useful. There is just one thing I don't like in this approach. If having the following code:

interface IA { /* some definition */ }
class A : IA {/* implementation */}
class B : IA 
{
   private implicit A a ;
}

What append if the interface IA changed ? In a classical scenario (without delegation), there will be a compilation error.

Now, with delegation, what happened? B will be automaticaly updated. There is a strong link between A and B which looks like inheritance.

And what happened if a method void Clear()is added to the interface IA but B already has this method defined? The B's method will be used ? The one "inherited" from A? A compilation error ?

A more simple approach (since it does not require any changes in C#) will be to add this feature to the IDE itself (I'm not sure, but I believe ReSharper can do it.). And in case of updating, the developper just have to update the code.

aluanhaddad commented 6 years ago

I would assume that the method defined in B would be called. This is consistent with the scope rules the language follows. I don't think implementing such a feature purely at the IDE level, whatever that would mean, is good for maintainability. It is still code that must be maintained. I like the idea of delegation.

francois-dorin commented 6 years ago

@aluanhaddad

I don't think implementing such a feature purely at the IDE level, whatever that would mean, is good for maintainability. It is still code that must be maintained. I like the idea of delegation.

I disagree. Maintainability is not limited to the number of lines of code. For example, if an interface is modified, then the contract represented by this interface changed as well. The code need to be updated and I don't believe an automatic delegation is the best solution in this case. The delegation previously configured may be not adapted to the changes anymore.

Only a developper can answer this question by reviewing the code. I confess it will take more time for performing the update than using delegation, but developpers keep the control of the code and I believe it is a safer way for long term maintainability.

quinmars commented 6 years ago

I don't believe an automatic delegation is the best solution in this case. The delegation previously configured may be not adapted to the changes anymore.

That's a valid point. It'd be probably better to override a method of the delegated interface with the keyword override. That would also add some symmetry to normal inheritance.

aluanhaddad commented 6 years ago

This is tricky. I don't think implicit overrides are the way to go. When I said that

I would assume that the method defined in B would be called.

I meant that Bs definition would be independent. If you compiled against a version of B that shadowed a delegated method defined in A, then Bs definition would be called. Consumers compiled against a version of B that did not shadow the method would still call the definition in A regardless of the update to B.

francois-dorin commented 6 years ago

1007 is an equivalent proposal.

I think we can close my issue

ghost commented 5 years ago

2291

public int MyAttr {get; set;} using myClass.Attr;