dotnet / csharplang

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

[Proposal]: Partial properties #6420

Open RikkiGibson opened 1 year ago

RikkiGibson commented 1 year 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

canton7 commented 1 year ago

Apologies if this is considered spam, but I just want to say thank you for working on this, and as an INPC SG author and strong proponent of partial properties, I agree with everything in your proposal πŸ‘

HaloFour commented 1 year ago

I still don't understand the distaste the team has expressed in supporting source generators in AOP scenarios properly. This proposal (and the others) feel a lot less intuitive and are significantly less capable. For example, it remains impossible to have custom logic within the accessor method while applying an aspect, and it remains impossible to apply multiple aspects unless they are all supported by the same source generator.

You either have a big cliff at which point you simply can't use source generators at all, or you need to find the one Swiss Army Source Generator to Rule Them All that lets you do everything you could possibly need to ever do in a property accessor, which likely includes twisting the code in awkward ways that would be less intuitive than AOP would be anyway.

alrz commented 1 year ago

The property declarations and their accessor declarations must have the same modifiers

One thing I like about partial classes is that you can add a part just by a partial class declaration no matter what the base is or what modifiers are applied (but if you do include them, they have to match).

I think the same could be applied to partial members so that generators won't need to bother duplicating every detail.

Sergio0694 commented 1 year ago

This looks awesome! πŸ˜„

"I think the same could be applied to partial members so that generators won't need to bother duplicating every detail."

Had the same thought - it'd be nice if the partial implementation could just use get/set/init without having to repeat the accessibility modifier. Not a big deal though, even if it was required, not an issue for a generator to be more verbose there.

The only remaining concern/scenario that comes to mind to me is: attributes on fields. That is, since releasing the MVVM Toolkit 8.0 we've had tons of requests about being able to customize how attributes can be added to fields and generated properties. It would be nice if people had some ability to annotate a partial property with attributes that are meant for the backing fields too, and the partial implementation would be given field (which would always be present in that case) with those attributes:

[field: SomeFieldAttribute]
public partial string Name { get; set; }

This way the generator could do eg.:

public partial string Name
{
    get => field;
    set => SetProperty(ref field, value);
}

This would be really important as it'd give users more flexibility and avoid "falling off a cliff" where the second you need a field attribute you're forced to give up generated properties entirely, and going back to manually doing everything. This feature has been requested several times and it's one of the main pain points we've seen for users of our INPC source generator.

HaloFour commented 1 year ago

@Sergio0694

This would be really important as it'd give users more flexibility and avoid "falling off a cliff" where the second you need a field attribute you're forced to give up generated properties entirely, and going back to manually doing everything.

I guess the alternative there would be that the source generator would be expected to detect the field-targeted attributes on the partial property and to copy them correctly to the backing field declarations?

Sergio0694 commented 1 year ago

Yeah that'd also be possible, just generators would have to manually copy all attributes (including arguments), because Roslyn would otherwise just ignore them. There's also the issue of the diagnostic being emitted ("attribute target is invalid here and will be ignored"), but I guess Roslyn could automatically suppress it if you're annotating a partial property πŸ€”

333fred commented 1 year ago

but I guess Roslyn could automatically suppress it if you're annotating a partial property πŸ€”

It wouldn't, but you could.

michael-hawker commented 1 year ago

@Sergio0694

This would be really important as it'd give users more flexibility and avoid "falling off a cliff" where the second you need a field attribute you're forced to give up generated properties entirely, and going back to manually doing everything.

I guess the alternative there would be that the source generator would be expected to detect the field-targeted attributes on the partial property and to copy them correctly to the backing field declarations?

I mean some attributes can target both fields and properties, so it'd be hard to separate the intent unless there was a way to specify which target the developer intends for the attribute?

Sergio0694 commented 1 year ago

"it'd be hard to separate the intent unless there was a way to specify which target the developer intends for the attribute?"

That's why I'm saying that developers would use explicit attribute targets in this context πŸ™‚

[Foo] // Goes on the property
[property: Foo] // Redundant, but you can also explicitly target the property if you want
[field: Foo] // Same attribute, but this goes on the field
public partial string Name { get; set; }
HaloFour commented 1 year ago

@michael-hawker

I mean some attributes can target both fields and properties, so it'd be hard to separate the intent unless there was a way to specify which target the developer intends for the attribute?

Right now the attribute targets the property unless explicitly targeted to the field via [field: FooAttribute].

RikkiGibson commented 1 year ago

The property declarations and their accessor declarations must have the same modifiers

One thing I like about partial classes is that you can add a part just by a partial class declaration no matter what the base is or what modifiers are applied (but if you do include them, they have to match).

I think the same could be applied to partial members so that generators won't need to bother duplicating every detail.

I'm finding out things I never thought to try with partial classes.. it seems like at least in some cases modifiers are essentially concatenated. SharpLab

// produces `public static class C` in metadata
public partial class C {
    public static void M() {
    }
}

static partial class C { }

It does seem reasonable that users shouldn't have to repeat things which are already known about the member. It also seems like source generators in practice just call UserDeclaration.Modifiers.ToString() inside their template and it tends to go pretty smoothly, though.

The relaxation on modifiers I'd like to see considered categorically for both properties and methods, similar to the relaxation on partial modifier ordering.

FaustVX commented 1 year ago

@RikkiGibson but it doesn't works for every modifier, I tried with abstract, but that doesn't work

public partial class C {
    public abstract void M() { // <- error CS0500: 'C.M()' cannot declare a body because it is marked abstract
    }
}

abstract partial class C { }

sharplab.io

mrpmorris commented 1 year ago

Please consider allowing us to not specify public/virtual/override .

public partial virtual string Name { get; protected set; }

Should work with a partial like this

partial string Name;

Or even

partial object Name; // No need to match type

CyrusNajmabadi commented 1 year ago

partial object Name; // No need to match type

Which type should we use in that case?

alrz commented 1 year ago

@RikkiGibson

It also seems like source generators in practice just call UserDeclaration.Modifiers.ToString() inside their template and it tends to go pretty smoothly, though.

For properties it's three places to do this.. and it really doesn't add much besides making the compiler happy.

it seems like at least in some cases modifiers are essentially concatenated

Same for ref/readonly on structs - makes me wonder if private and protected should do that. /s

RikkiGibson commented 1 year ago

@RikkiGibson but it doesn't works for every modifier, I tried with abstract, but that doesn't work

public partial class C {
    public abstract void M() { // <- error CS0500: 'C.M()' cannot declare a body because it is marked abstract
    }
}

abstract partial class C { }

sharplab.io

I think the method here is behaving as expected for an abstract class. If you use a semicolon body, then it compiles. SharpLab.

FaustVX commented 1 year ago

@RikkiGibson haha Ok πŸ˜„

HaloFour commented 1 year ago

IMO the signature of the declaration and the implementation should be required to match exactly. The declaration should be the source of truth, given that it is likely what the developer has written manually and what they expect to be the public surface. I think the onus should be on the source generator to emit the matching signature in order to ensure that the source generator is doing what the developer expects them to be doing. A difference in what the source generator emits should result in an error for the sake of sanity checking. If that poses difficult for the source generator I would suggest that the APIs of source generators be improved to make it easier, rather than changing the syntax of the language to make it easier for a mistake to slip through unexpectedly.

alrz commented 1 year ago

While there's no restriction for types as mentioned (some other part could decide on static, for example), for member this could be simply not the case. Meaning, you either have to repeat all modifiers or none at all. That will make it impossible to have unexpected results by making sure nothing about the member can change out of sight.

Further this can only be allowed on the "partial implementation" rather than both ends (implementation and declaration).

rcbellamy commented 1 year ago

Having written a source generator for my project, in my opinion requiring the source generator to mention access modifiers, etc., is a very big deal. That information is simply never of any relevance to the work that my source generator is created to accomplish (it writes logic, the human-written part defines who can access that logic--simple, right?). It seems like the developers at Microsoft can deal with not requiring the implementation to mention the modifiers once, and save everyone the headache; or they can not bother, and then anyone that ever writes a source generator has to add a significant amount of complexity to their source generator for which many source generator authors will see absolutely no added value. Having Microsoft do it once for everyone seems like a no-brainer.

As for the notion that the solution is to improve the API, I agree that improving the API's documentation would be a very valuable idea. It wouldn't change the part where this requires a significant amount of complexity added to the source generator which many source generators might not benefit from at all. Plus, let's be realistic. It is far easier for Microsoft's developers to make it so that the source generators are not required to specify the modifiers than it is for them to find people that can adequately improve the documentation.

On a side note, @RikkiGibson , could you please provide a quick documentation link regarding UserDeclaration.Modifiers.ToString()? When I google it, this discussion is the only result.

CyrusNajmabadi commented 1 year ago

and then anyone that ever writes a source generator has to add a significant amount of complexity to their source generator

I don't see there being significant complexity. You can legitimately just reuse the exact same Modifiers property from the original declaration when making your declaration.

CyrusNajmabadi commented 1 year ago

could you please provide a quick documentation link regarding

https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.syntaxnode.tostring?view=roslyn-dotnet-4.3.0

ToString on any node returns the original text (no interpretation or modification) that created it. ToFullString is the same, except with the leading/trailing trivia around that node as well.

Roslyn does not have abstract syntax trees for our syntax model. We have concrete syntax trees. So they contain every last character used to create them in the original text (no more and no less). ToString/ToFullString just give you those characters back.

So, in teh context of this discussion, producing the modifiers is trivial. You just take the modifiers from the thing you have and pass that node along directly to the tree you're creating (one step). Or, if you're producing text, you just ToString the modifiers and append those (also one step). In both cases it's extremely simple on hte generator side.

sab39 commented 1 year ago

One consideration - I don't think source generators are can get this granularity of laziness with the current API, but if the modifiers don't have to be repeated, it might eventually be possible to not rerun the source generator at all if only the modifiers are changed. This might have a significant impact if there's a refactoring operation that changes a lot of modifiers at once.

CyrusNajmabadi commented 1 year ago

This might have a significant impact if there's a refactoring operation that changes a lot of modifiers at once.

The right way to think about incremental-perf is to consider how the generation works on the common editing cases, not hte outliers. In practice, people are editing code bodies most of hte time, and occasionally adding/removing/modifying signatures. The latter is not the common operation, and even if there was "a refactoring operation that changes a lot of modifiers at once", it would be a rare one off that would be greatly subsumed by the normal editing cases which would create your amortized cost.

--

A good analogy here is thinking about how hashtables work. Sure, you might rarely get an O(n) operation when the table needs to resize things. But the vast majority of ops lead to an amortized O(1) cost for the overall structure.

rcbellamy commented 1 year ago

@CyrusNajmabadi , the link that you provide (specifically https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.syntaxnode.tostring?view=roslyn-dotnet-4.3.0) is certainly helpful to have, but does not help clarify if UserDeclaration is a class or a member, so I await the link that I requested from @RikkiGibson.

As for your statement that "producing the modifiers is trivial[,]" you ignore the catch-22. It is not trivial until after you learn how to do it. That means that there is a significant reduction in the learning curve to be accomplished by removing the complexity, which inherently makes the complexity significant. I had no interest in ever writing one source generator, but I needed it in order to correct deficiencies in Microsoft's work in other aspects of .Net. Once you account for this catch-22, you can see that your position is plainly without logic.

Your statements are also rude, arrogant, and offensive. Your attitude is a common attitude among senior technicians in the same kinds of fields that generally seek unionization or tenure --senior technicians that fear competition from new entrants in the labor force. I cannot fathom any manager at Microsoft condoning any employee speaking to any customer that way, and I see your github profile indicates that Microsoft is your organization, so I must demand the name and phone number of the manager responsible for decisions regarding your employment, and I must demand that your post be removed as a violation of the repository's code of conduct's prohibition against "[t]rolling, insulting or derogatory comments, and personal or political attacks[.]"

HaloFour commented 1 year ago

@rcbellamy

It is not trivial until after you learn how to do it.

This applies to literally everything. That's not a reason to affect the language itself. It's infinitely easier to produce documentation with tutorials and helper API. An author of a source generator is not expected to be a beginner with the language or Roslyn framework and IMO it's fully reasonable to put the onus on that developer to emit correct code.

Your statements are also rude, arrogant, and offensive.

You not liking the statement doesn't make it offensive.

CyrusNajmabadi commented 1 year ago

but does not help clarify if UserDeclaration is a class or a member, so I await the link that I requested from

"UserDeclaration" here refers to the property declaration syntax for the partial property.

In this case, it would be an instance of: https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.csharp.syntax.propertydeclarationsyntax?view=roslyn-dotnet-4.3.0

CyrusNajmabadi commented 1 year ago

As for your statement that "producing the modifiers is trivial[,]" you ignore the catch-22. It is not trivial until after you learn how to do it.

Yes. It will be necessary to learn source generators in order to use then effectively. But in this case, the learning is pretty simple and easy. If the modifiers need to be the same, then generating that is actually really nice with Roslyn due to it concrete syntax tree model. Specifically, because they need to be the same, you can accomplish this just by literally reusing the same syntax pieces from the declaration.

Note that you'd have to do this anyways given things like return type, name and whatnot. So it's just as simple to reuse the modifiers as well :-)

That means that there is a significant reduction in the learning curve to be accomplished by removing the complexity,

I recommend using tools like sharplab or the syntax visualizer. They will greatly reduce the learning curve. "Roslyn quoter" is also very useful.

Finally, if you are still facing difficulties, we have a vibrant and helpful community over at discord.gg/csharp that would be happy to help you. Many if the people here are routinely there, along with hundreds of other passionate developers.

CyrusNajmabadi commented 1 year ago

I had no interest in ever writing one source generator, but I needed it in order to correct deficiencies in Microsoft's work in other aspects of .Net.

Glad to hear that source generators may help you out. Let us know about that experience and how we can make it better. Note that that feedback is best sent to dotnet/Roslyn as that's a compiler/tooling feature. Whereas dotnet/csharplang is specifically for the language design of things. Thanks!

jaredpar commented 1 year ago

@rcbellamy

Personal attacks will not be tolerated in this repository. I've hidden your posts and issued a ~temporary~ permanent ban. Please keep the conversations respectful and on topic.

amitai commented 1 year ago

Thanks Jaredpar

alrz commented 1 year ago

@CyrusNajmabadi

To be honest, I don't really understand your strong objection to this suggestion. This is something that exists in the language today and turns out there are people who have seen value in it, at least that includes Hejlsberg himself. If it was to be allowed only in the partial impl side, would it actively hurt perf, readability, productivity, or simplicity of the feature in any possible way?

CyrusNajmabadi commented 1 year ago

@alrz I genuinely am not sure what you're talking about. What "strong objection" are you referring to?

alrz commented 1 year ago

I think there is only one issue being discussed (about repeating modifiers) that you were disagreeing with, but I think it got lost in the heated back and forth. Sorry I was not entirely clear about that.

CyrusNajmabadi commented 1 year ago

@alrz I was responding to:

and then anyone that ever writes a source generator has to add a significant amount of complexity

And simply stating that I do not believe it's a significant amount of complexity to match modifiers.

richardtallent-erm commented 1 year ago

I just have to say I'm excited about the possibility of this! I have code that tracks "dirty" fields in property setters, and the boilerplate is annoying. Being able to move this to my source generator would be awesome.

Chiming in on the modifiers topic, I do believe the getter/setter access modifiers should be specified in the declaration, and should not be overridable by the source generator (or by a conflicting manually-written partial property in other partial class).

Otherwise, the source generator would have to run before static analysis or compilation could determine if a call to the property getter or setter was accessible.

While I don't have a strong opinion on this, I do believe the implementation should match the access modifiers of the declaration. This is how partial classes work. Keep in mind that the implementation doesn't have to be created by a source generator, it could just be in another manually-coded file, and having two files that essentially disagree about access (since modifiers are not required, the lack of modifiers "means" something).

Youssef1313 commented 1 year ago

We could consider introducing some special way to denote that a partial property implementation is an auto-property, separate from the field keyword.

In case semi-auto properties design has changed to add auto (or similar) modifier on the property, we could do public auto partial string Prop { get; set; }.

listepo commented 1 year ago

Can this be done in C# 11 or C# 11.x?

333fred commented 1 year ago

No.

michael-hawker commented 1 year ago

We've had a discussion about generating DependencyProperties with source generators as well with the success of the MVVM Toolkit, latest discussion thread here: https://github.com/CommunityToolkit/WindowsCommunityToolkit/discussions/4620

It'd be really great to just be able to go like:

[DependencyProperty]
public partial string Label { get; set; }

And get all this boilerplate generated for us:

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  nameof(Label),
  typeof(string),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);

public partial string Label
{
    get { return (string)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}

The other thing we've noticed with the MVVM Toolkit having to attribute fields over properties is that if you rename the field and the generator has to make the new Property, other references to the property don't get renamed.

By instead having partial properties and generating off that, it means if you rename the symbol in VS, all the other instances of that usage in places like XAML should now get properly updated and renamed as well from the direct usage of the property (vs. having a reference to the generated property from a field which is getting renamed).

sonnemaf commented 1 year ago

@michael-hawker I would love this! It makes so much sense to use this for Dependency Properties and probably also for Attached Properties.

michael-hawker commented 1 year ago

@sonnemaf I always forget about Attached Properties as they're kind of an odd syntax in comparison. I think they could be done with Source Generators today? As I think they're technically static fields and not properties, right? (@Sergio0694 would probably be able to tell me... πŸ˜‹) I guess the tricky part is how initialization would work?

Like this is the standard Attached Property setup:

public class GameService
{
    public static readonly DependencyProperty IsMovableProperty = 
        DependencyProperty.RegisterAttached(
          "IsMovable",
          typeof(Boolean),
          typeof(GameService),
          new PropertyMetadata(false)
        );

    public static void SetIsMovable(UIElement element, Boolean value)
    {
        element.SetValue(IsMovableProperty, value);
    }

    public static Boolean GetIsMovable(UIElement element)
    {
        return (Boolean)element.GetValue(IsMovableProperty);
    }

Theoretically, it should just look like this:

    // Takes the type of the property as the first parameter, and the target attaching DependencyObject subclass as second (class name type is grabbed by the SG)
    [AttachedProperty(typeof(Boolean), typeof(UIElement))
    public static readonly DependencyProperty IsMoveableProperty;

And then ideally the above code is generated. This makes it really nice to not have a 'magic' string in the Attached Property declaration as well, which would be a huge plus.

Both DependencyProperty and AttachedProperty attributes would need an extra parameter in order to specify a changed callback method to use or something as well.

Sergio0694 commented 1 year ago

That wouldn't work, you cannot generate a field initializer separately πŸ₯² The best you could do would be to have the generator create a static constructor to set that field, but that's not something that I would recommend doing for several reasons (including worse performance, as you lose the beforefieldinit flag).

michael-hawker commented 1 year ago

@Sergio0694 would there be a better way or some other language feature needed to make that sort of Source Generator to work? Like should partial fields be a thing too?

HaloFour commented 1 year ago

@michael-hawker

Maybe write a partial getter method and let the source generator fill in the rest?

[AttachedProperty]
public static partial bool GetIsMovable(UIElement element);
Sergio0694 commented 1 year ago

Using a partial method as trigger makes the most sense, though there are some cases where attached properties don't have any callbacks attached to them. I mean I guess one could use an empty method then, but it might be a bit clunky. Overall I can't really think of an ideal solution for this scenario off the top of my head. For normal dependency properties though, partial properties are definitely the way to go, and also why I've always been recommending folks so far to just hold off on trying to hack alternative solutions by annotation types and whatnot, and just hold their breath until partial properties go online πŸ™‚

datvm commented 1 year ago

Just want to add that I haven't seen MAUI mentioned in this discussion yet and it'd be great!

// MyModel.cs
public partial class MyModel
{
    public partial string Name { partial get; }
}

// MyModel.Android.cs
partial class MyModel
{
    AndroidOnlyClass info;
    // Constructor or static method to assign info

    public partial string Name => info.Name;
}

// Same for iOS etc...
mgravell commented 1 year ago

Another use-case: https://github.com/protobuf-net/protobuf-net/issues/1060 - "discriminated unions" in the case of protobuf; right now, I'd have to use:

[ProtoUnion<int>("Abc", 1, "Bar")]
[ProtoUnion<string>("Abc", 2, "Blap")]
partial class Foo
{
  // ...
}

With partial properties, this could be the much clearer (in that properties are actually properties):

partial class Foo
{
    [ProtoUnion("Abc", 1)]
    public partial int Bar {get;set;}

    [ProtoUnion("Abc", 2)]
    public partial string Blap {get;set;}

    // ...
}
Sergio0694 commented 1 year ago

Just to add one more use case: in ComputeSharp I have a CanvasEffect class that can be used to easily create "packaged" effects. That is, a Win2D-compatible effect that internally can create a custom effect graph containing one or more effects, and expose the whole thing as a nice single effect that developers can consume. For instance, we're using this as the base type for all our custom effects in the Microsoft Store, which then internally can use all sorts of additional effects, custom pixel shaders, and whatnot. An effect has a given graph it can build and update, and properties.

Now, CanvasEffect, much like D2D, has a concept of "dirty" effect graph. That is, if at least one property has been modified since the last time the effect has been drawn, then some logic will be invoked to force a refresh of the state of the graph, to ensure that the underlying D2D objects match the state of the higher level C# wrapper (ie. the CanvasEffect instance).

The way you create an effect is like this:

public sealed class FrostedGlassEffect : CanvasEffect
{
    private IGraphicsEffectSource? _source;

    public IGraphicsEffectSource? Source
    {
        get => _source;
        set => SetAndInvalidateEffectGraph(ref _source, value);
    }

    private float _blurAmount;

    public float BlurAmount
    {
        get => _blurAmount;
        set => SetAndInvalidateEffectGraph(ref _blurAmount, value);
    }

    private Color _tintColor;

    public Color TintColor
    {
        get => _tintColor;
        set => SetAndInvalidateEffectGraph(ref _tintColor, value);
    }

    private float _noiseAmount;

    public float NoiseAmount
    {
        get => _noiseAmount;
        set => SetAndInvalidateEffectGraph(ref _noiseAmount, value);
    }

    // Other properties here...

    protected override void BuildEffectGraph(EffectGraph effectGraph)
    {
        // Build the effect graph, etc.
    }

    protected override void ConfigureEffectGraph(EffectGraph effectGraph)
    {
        // Update the effect graph nodes with the values from the properties
    }
}

You can see that, exactly like with MVVM properties, there's a lot of verbosity. With partial properties, I could instead just add some [EffectProperty] attribute and have a generator create the necessary update code for each property. So you'd get:

public sealed partial class FrostedGlassEffect : CanvasEffect
{
    [EffectProperty]
    public partial IGraphicsEffectSource? Source { get; set; }

    [EffectProperty]
    public partial float BlurAmount { get; set; }

    [EffectProperty]
    public partial Color TintColor { get; set; }

    [EffectProperty]
    public partial float NoiseAmount { get; set; }

    protected override void BuildEffectGraph(EffectGraph effectGraph)
    {
    }

    protected override void ConfigureEffectGraph(EffectGraph effectGraph)
    {
    }
}

This would be way easier to read, write, and maintain πŸ˜„

michael-hawker commented 1 year ago

edit: realized I did bring this up before, but not in the same exact way above

Realized there's another modifier that hasn't been discussed here: static.

Can you create a static partial property?

Scenario for this would be better helping in creating custom attached properties.

While the attached property helper methods could be generated with source generators today, If you wanted to bundle the registration with that to simplify the declaration (which is easy to mess up and keep in sync), then that wouldn't be possible.

For example, going from:

public class GameService : DependencyObject
{
    public static readonly DependencyProperty IsMovableProperty = 
    DependencyProperty.RegisterAttached(
      "IsMovable",
      typeof(Boolean),
      typeof(GameService),
      new PropertyMetadata(false)
    );
    public static void SetIsMovable(UIElement element, Boolean value)
    {
        element.SetValue(IsMovableProperty, value);
    }
    public static Boolean GetIsMovable(UIElement element)
    {
        return (Boolean)element.GetValue(IsMovableProperty);
    }
}

To be generated just as above with this instead (along with #124 syntax for instance too):

public class GameService : DependencyObject
{
    [AttachedProperty<Boolean, UIElement>]
    public static partial readonly DependencyProperty IsMovableProperty { get; init; }
}

Would be amazing!