dotnet / csharplang

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

[Proposal]: Partial properties #6420

Open RikkiGibson opened 2 years ago

RikkiGibson commented 2 years ago

Partial properties

Summary

Allow the partial modifier on properties to separate declaration and implementation parts, similar to partial methods.

// UserCode.cs
public partial class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChanged]
    public partial string UserName { get; set; }
}

// Generated.cs
public partial class ViewModel
{
    private string __generated_userName;

    public partial string UserName
    {
        get => __generated_userName;
        set
        {
            if (value != __generated_userName)
            {
                __generated_userName = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UserName)));
            }
        }
    }
}

Motivation

When we did extended partial methods, we indicated we would like to consider adding support for other kinds of partial members in the future. The community has shown enthusiasm for partial properties in particular.

.NET has a number of scenarios where a property implementation is some kind of boilerplate. One of the most prominent cases is INotifyPropertyChanged, as seen above. Another is dependency properties. There are currently production source generators designed to handle these scenarios. These currently work by having the user write a field and having the generator add the corresponding property.

// UserCode.cs
public partial class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChanged]
    private string _userName;
}

// Generated.cs
public partial class ViewModel
{
    public string UserName
    {
        get => /* ... */;
        set
        {
            // ...
        }
    }
}

Under this scheme, users have to become familiar with the conventions for how the generator creates properties based on their fields. Additional workarounds are needed for users to be able to change accessibility, virtual-ness, attributes, or other aspects of the generated property. Also, using features like find-all-references requires navigating to the generated property, instead of being able to just look at the declarations in user code. All of these issues are solved fairly naturally by adding partial properties to the language.

Detailed design

Detailed design has been moved to partial-properties.md.

Design meetings

https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-08-31.md#partial-properties https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-11-02.md#partial-properties

RikkiGibson commented 1 year ago

Can you create a static partial property?

Yes, a static property can be partial. I think the only common modifier which will be disallowed for partial properties is abstract.

b-straub commented 1 year ago

I vote for skipping this proposal in favour of allowing Interceptors for getters and setters.

Using interceptors will allow adding one Attribute to a class and intercept all properties without further changes. This is what the SwiftUI people are doing with the new macro based approach.

Sticking with the INPC example this means adding for example [Observable] to a class/struct/record would be enough for having all properties setters fire the relevant event.

burtonrodman commented 1 year ago

@b-straub it's unfortunate that Interceptors is a) still experimental and b) treats property interception as a future TODO:

Class1.cs(33,6): error CS9151: Possible method name 'Property1' cannot be intercepted because it is not being invoked.

I'm also unsure that this would be a viable solution as Interceptors act on the call site (i.e. replaces calls to a method -- not the method itself) -- which would be hairy for things like razor and XAML.

mrpmorris commented 1 year ago

I vote for skipping this proposal in favour of allowing Interceptors for getters and setters.

Using interceptors will allow adding one Attribute to a class and intercept all properties without further changes. This is what the SwiftUI people are doing with the new macro based approach.

Sticking with the INPC example this means adding for example [Observable] to a class/struct/record would be enough for having all properties setters fire the relevant event.

How would that let me add attributes to a property from a code-generator?

b-straub commented 1 year ago

How would that let me add attributes to a property from a code-generator?

What would be the use case for adding attributes to a partial property from a SG? Usually a SG is a consumer for attributes.

HavenDV commented 1 year ago

How would that let me add attributes to a property from a code-generator?

What would be the use case for adding attributes to a partial property from a SG? Usually a SG is a consumer for attributes.

Using the DependencyPropertyGenerator example, there are many attributes that the user can additionally define (for example, System.ComponentModel.Category and System.ComponentModel.Description). Now there are separate properties for this inside the generator attribute

burtonrodman commented 1 year ago

while property interceptors may be a good feature, i agree that they don’t have nearly the flexibility that partial properties would bring. especially given that they act on the call site, i don’t see them covering certain scenarios that may require internal access to the object that a partial property could provide.

b-straub commented 1 year ago

How would that let me add attributes to a property from a code-generator?

What would be the use case for adding attributes to a partial property from a SG? Usually a SG is a consumer for attributes.

Using the DependencyPropertyGenerator example, there are many attributes that the user can additionally define (for example, System.ComponentModel.Category and System.ComponentModel.Description). Now there are separate properties for this inside the generator attribute

I can't see the difference for your example.

code

[global::System.ComponentModel.Category("Category")]
[global::System.ComponentModel.Description("Description")]
public partial bool IsSpinning { get; set; }

SG

public partial bool IsSpinning
{
   get => (bool)GetValue(IsSpinningProperty);
   set => SetValue(IsSpinningProperty, value);
}

code

[global::System.ComponentModel.Category("Category")]
[global::System.ComponentModel.Description("Description")]
public bool IsSpinning { get; set; }

SG

[InterceptsLocation("Program.cs", line: /*L1*/, character: /*C1*/)] // refers to -> public bool IsSpinning { get; set; }
public static bool IsSpinning
{
   get => (bool)GetValue(IsSpinningProperty);
   set => SetValue(IsSpinningProperty, value);
}
KalleOlaviNiemitalo commented 1 year ago

Attributes from partial properties would still be useful for code generators that are not Roslyn source generators and perhaps do not read C# source files at all, thus won't know the file names and line numbers to intercept.

b-straub commented 1 year ago

Attributes from partial properties would still be useful for code generators that are not Roslyn source generators and perhaps do not read C# source files at all, thus won't know the file names and line numbers to intercept.

Not my point. I'm not opposing against partial in general but against the tailoring to SGs needs. SGs have several weaknesses, especially performance and cascading. Extending to more types doesn't make the concept any better.

Rekkonnect commented 1 year ago

That one I really don't understand. SGs already have performance issues, this proposal won't make them slower or anything. And SGs will become faster in the future if they put the effort into enhancing the design.

b-straub commented 1 year ago

That one I really don't understand. SGs already have performance issues, this proposal won't make them slower or anything. And SGs will become faster in the future if they put the effort into enhancing the design.

I see I didn't explain my concerns very well. My concern is that with each mainly SG iteration of the language, such as the one proposed, the number of SGs will increase. The problem with this increase is the fact that SGs cannot be combined these days, see dotnet/roslyn#57239. When runtime SGs come into play (RegEx, JSON), the problem becomes immediately obvious. Finding a solution to combine SGs is obviously quite difficult, given the input from dotnet/roslyn#57239.

Rekkonnect commented 1 year ago

I see where you come from, even though I have only noticed a spike in popularity from each new SG introduced into the framework, not with each feature that makes SGs friendlier. Making partial properties won't make SGs explode, it'll only help existing SGs that use backing fields to generate the properties, or slapping attributes on classes.

That being said, it's not just SGs that auto-generate code. You can always make your own tool that generates some specific code upon request, and not on every compilation. That one would create specific files that would make use of features that SGs also benefit off of. The main problem we're all facing with SGs is that they run very often, and your PC only lacks some wings before it can fly to the skies.

KUNGERMOoN commented 11 months ago

Hi, is there any progress on this? And if not, is there a way I could contribute to help to bring this idea to life?

jaredpar commented 11 months ago

Hi, is there any progress on this?

Not a lot. This is the point in the .NET ship cycle where the team is very heads down on finishing the current version of the language: C# 12 in this case. This feature didn't make that release and hence there isn't going to be much activity on it. This is in strong consideration for C# 13 so once we move back to new feature work it's likely to see some activity. We are at best several weeks out from that right now.

heartacker commented 11 months ago

want this TO be in NET 8

ghord commented 9 months ago

Please consider supporting indexers. Currently they are still heavily used in C#, for example when using Range parameters in custom types.

I would like to be able to auto generate indexers from SG based on attributes.

neuecc commented 8 months ago

+1 for C# 13.

rofenix2 commented 7 months ago

Probably the most interesting and more useful feature that could be added in a long time to C#. This could bring DDD with source generators to another level and define very complex models in a elegant way without too much typing, making at least the domain model development way faster. +100 to this, i wish this was the top priority right now.

RoyAwesome commented 7 months ago

I am currently writing a networking library for a video game, and something like this would be very useful. I currently have some code that requires generated OnChanged_PROPERTYNAME calls to be placed in properties and wherever fields are modified to indicate to my system to catch that field on the next network tick, package it into a state update packet, and send it off. Having the ability to auto generate this OnChanged_ function call for a property's setter would be immensely useful.

robloo commented 5 months ago

Haven't seen any update from Microsoft on this in a while. Everyone doing application development has a use case for this feature -- especially with code generation.

The value add to the C# language for developers is quite high for this. Time saved would be significant.

Do we know the current status? Can this be discussed again in the language meetings?

heartacker commented 5 months ago

want this TO be in NET 8

hope this is earn in NET9

thomaslevesque commented 4 months ago

When implementing this feature, please consider also allowing partial events.

SystematicChaos012 commented 4 months ago

Probably the most interesting and more useful feature that could be added in a long time to C#. This could bring DDD with source generators to another level and define very complex models in a elegant way without too much typing, making at least the domain model development way faster. +100 to this, i wish this was the top priority right now.

Yes,If we have this, Event Sourcing between aggregate root and entities will be simple, code also becomes more concise

Uzbekbekbek commented 2 months ago

One more use-case is docummenting. I want to have all public API for the class in the file separated from the implementation itself. And I want to have good comments with code snippets, etc. something like:

MyClass.cs

public partial class MyClass {
  public int DoSomethingWithNumber(int x) { 
     //do a big job with input to produce output, a lot of lines of code could be there which shouldn't be noised by comments
    return x*Multiplier; 
  }
  public int Multiplier {get;set;}
}

MyClass.API.cs

public partial class MyClass {

  ///<summary>Description</summary>
  ///<example><code>code sample here</code></example>
  public partial int DoSomethingWithNumber(int x);

  ///<summary>Description</summary>
  public partial int Multiplier {get;set;}
}
DJGosnell commented 2 months ago

One more use-case is docummenting. I want to have all public API for the class in the file separated from the implementation itself. And I want to have good comments with code snippets, etc. something like:...

I think for something like this, Interfaces is a much better system to handle documentation.

wanggangzero commented 1 month ago

I want it too.

robloo commented 1 month ago

It's in preview 6 as of a few days ago