X39.SourceGenerators.Property 1.0.0.9

There is a newer version of this package available.
See the version list below for details.
dotnet add package X39.SourceGenerators.Property --version 1.0.0.9                
NuGet\Install-Package X39.SourceGenerators.Property -Version 1.0.0.9                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="X39.SourceGenerators.Property" Version="1.0.0.9" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add X39.SourceGenerators.Property --version 1.0.0.9                
#r "nuget: X39.SourceGenerators.Property, 1.0.0.9"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install X39.SourceGenerators.Property as a Cake Addin
#addin nuget:?package=X39.SourceGenerators.Property&version=1.0.0.9

// Install X39.SourceGenerators.Property as a Cake Tool
#tool nuget:?package=X39.SourceGenerators.Property&version=1.0.0.9                

X39.SourceGenerators.Property

This library adds a source generator that generates properties for a given class.

Quick Start

Add the nuget package to your project. After that, make the classes you want to generate properties for partial and add corresponding attributes to the class, eg.:

using X39.SourceGenerators.Property;

[NotifyPropertyChanged(true)]
public partial class MyClass
{
    private int _myProperty;
}

This will generate a partial class with the following content:

#nullable enable
public partial class MyClass
    : System.ComponentModel.INotifyPropertyChanged
{
    public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
            this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof(MyProperty)));
        }
    }
}

Common fixes with source generators

The Source-Generator is not working (After installing the package; After Building)

After installing the package, check whether the build output contains a CS9057 warning. If it does, your compiler is not updated enough to support this source generator. Install the latest .NET SDK to fix this.

dotnet build errors that it cannot find the generated output

The dotnet build server may have something cached. Run dotnet build-server shutdown to restart the build server.

Rider does not recognize the source generator

There might be multiple issues here:

  • You may have to restart Rider after installing the package to make the source generator work.
  • Make sure Roslyn Analyzers are enabled in the settings. Search for "Roslyn" in the settings and enable all Roslyn related checkboxes related to building.

When and how are properties generated?

The properties are generated when one of the (Attributes)[#Attributes] is placed on the class or field. The source generator will then generate a partial class with the same name as the original class and add the properties to it.

The property name will be the name of the field with the first letter capitalized and an optional underscore removed. Additionally, if the field is suffixed with Field, the suffix will be removed too. See (PropertyNameAttribute)[#PropertyNameAttribute] to customize the property name.

For a more "example driven" approach to this explanation, see the following table:

Field Name Property Name
_abc Abc
_abcField Abc
abc Abc
__abc _abc
abc Abc_

Attributes

Some attributes are placeable on either the class or field. If the attribute is placed on the class, the attribute will be taken as a default for all fields. This also implies that the field attributes always take precedence over the class attributes.

GeneratePropertiesAttribute

This attribute will make the source generator generate properties if no other attribute is desired.

On the class

// User-Code
[GenerateProperties]
public partial class MyClass
{
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
        }
    }
}

On the field

// User-Code
public partial class MyClass
{
    [GenerateProperties]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
        }
    }
}

NotifyPropertyChangedAttribute

This attribute will make the source generator add a PropertyChanged event call to the setter of the property. If the attribute is placed on the class and the parameter is set to true (default: false), the source generator will implement the INotifyPropertyChanged interface on the class.

On the class with true

// User-Code
[NotifyPropertyChanged(true)]
public partial class MyClass
{
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
    : System.ComponentModel.INotifyPropertyChanged
{
    public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
            this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof(MyProperty)));
        }
    }
}

On the class with false

// User-Code
[NotifyPropertyChanged(false)]
public partial class MyClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
        }
    }
}

On the field with true

// User-Code
public partial class MyClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    [NotifyPropertyChanged(true)]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
            this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof(MyProperty)));
        }
    }
}

On the field with false

// User-Code
public partial class MyClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    [NotifyPropertyChanged(false)]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
        }
    }
}

NotifyPropertyChangingAttribute

This attribute will make the source generator add a PropertyChanging event call to the setter of the property. If the attribute is placed on the class and the parameter is set to true (default: false), the source generator will implement the INotifyPropertyChanging interface on the class.

On the class with true

// User-Code
[NotifyPropertyChanging(true)]
public partial class MyClass
{
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
    : System.ComponentModel.INotifyPropertyChanging
{
    public event System.ComponentModel.PropertyChangingEventHandler? PropertyChanging;
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
            this.PropertyChanging?.Invoke(this, new System.ComponentModel.PropertyChangingEventArgs(nameof(MyProperty)));
        }
    }
}

On the class with false

// User-Code
[NotifyPropertyChanging(false)]
public partial class MyClass : INotifyPropertyChanging
{
    public event PropertyChangingEventHandler? PropertyChanging;
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
        }
    }
}

On the field with true

// User-Code
public partial class MyClass : INotifyPropertyChanging
{
    public event PropertyChangingEventHandler? PropertyChanging;
    [NotifyPropertyChanging(true)]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
            this.PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(MyProperty)));
        }
    }
}

On the field with false

// User-Code
public partial class MyClass : INotifyPropertyChanging
{
    public event PropertyChangingEventHandler? PropertyChanging;
    [NotifyPropertyChanging(false)]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
        }
    }
}

PropertyNameAttribute

This attribute will make the source generator use the given name as the property name. It cannot be placed on the class.

On the field

// User-Code
public partial class MyClass
{
    [PropertyName("MyProperty")]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
        }
    }
}

ValidationStrategyAttribute

The validation strategy attribute is used to define how additional, validating properties should be handled, when the validation fails.

Supported Validations

  • System.ComponentModel.DataAnnotations.RangeAttribute
  • System.ComponentModel.DataAnnotations.MaxLengthAttribute
  • GuardAttribute

Available Strategies

  • EValidationStrategy.Exception This strategy will throw an ArgumentException when the validation fails. This is the default strategy.
  • EValidationStrategy.Rollback This strategy will behave like the Ignore strategy, unless NotifyPropertyChanged is also present. If the NotifyPropertyChanged attribute is present, the event will be raised with no value change.
  • EValidationStrategy.Ignore This strategy will ignore the validation and exit the setter without changing the value.

On the class (EValidationStrategy.Exception)

// User-Code
[ValidationStrategy(EValidationStrategy.Exception)]
public partial class MyClass
{
    [Range(0, 10)]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            if (value < 0 || value > 10)
                throw new System.ArgumentException("Validation of Field failed: Value must be between 0 and 10", nameof(value));
            _myProperty = value;
        }
    }
}

On the field (EValidationStrategy.Exception)

// User-Code
public partial class MyClass
{
    [ValidationStrategy(EValidationStrategy.Exception)]
    [Range(0, 10)]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            if (value < 0 || value > 10)
                throw new System.ArgumentException("Validation of Field failed: Value must be between 0 and 10", nameof(value));
            _myProperty = value;
        }
    }
}

On the class (EValidationStrategy.Rollback)

// User-Code
[ValidationStrategy(EValidationStrategy.Rollback)]
[NofityPropertyChanged(true)]
public partial class MyClass
{
    [Range(0, 10)]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            if (value < 0 || value > 10)
            {
                this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof(MyProperty)));
                return;
            }
            _myProperty = value;
            this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof(MyProperty)));
        }
    }
}

On the class (EValidationStrategy.Ignore)

// User-Code
[ValidationStrategy(EValidationStrategy.Ignore)]
[NofityPropertyChanged(true)]
public partial class MyClass
{
    [Range(0, 10)]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged;
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            if (value < 0 || value > 10)
                return;
            _myProperty = value;
            this.PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof(MyProperty)));
        }
    }
}

PropertyEncapsulationAttribute

This attribute will make the source generator encapsulate the property with the given access modifier. It cannot be placed on the class.

Possible values are:

  • EPropertyEncapsulation.Public
  • EPropertyEncapsulation.Protected
  • EPropertyEncapsulation.Internal
  • EPropertyEncapsulation.Private
  • EPropertyEncapsulation.ProtectedInternal

On the field

// User-Code
public partial class MyClass
{
    [PropertyEncapsulation(EPropertyEncapsulation.Protected)]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    protected int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
        }
    }
}

VirtualPropertyAttribute

This attribute will make the source generator generate a virtual property. It cannot be placed on the class.

On the field

// User-Code
public partial class MyClass
{
    [VirtualProperty]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public virtual int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
        }
    }
}

EqualityCheckAttribute

This attribute will change how or if the equality check is performed.

Supported Equality Modes

  • EEqualityCheckMode.Default This mode will use the default equality check, using == for primitive types and object.Equals for non-primitive types. This is the default mode.
  • EEqualityCheckMode.Custom This mode will use the given method to check for equality. The method must be a method with the signature bool MethodName(T oldValue, T newValue). The method must be accessible from the generated code. If custom equality check is used, the Custom argument must be set to the name of the method or the generated code will fall back to the default equality check.
  • EEqualityCheckMode.None This mode will not generate an equality check.

On the class (EEqualityCheckMode.Default)

// User-Code
[EqualityCheck(EEqualityCheckMode.Default)]
public partial class MyClass
{
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
        }
    }
}

On the class (EEqualityCheckMode.Custom)

// User-Code
[EqualityCheck(EEqualityCheckMode.Custom, "MyEqualityCheck")]
public partial class MyClass
{
    private static bool MyEqualityCheck(int left, int right) => left == right;
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (MyEqualityCheck(_myProperty, value))
                return;
            _myProperty = value;
        }
    }
}

On the class (EEqualityCheckMode.None)

// User-Code
[EqualityCheck(EEqualityCheckMode.None)]
public partial class MyClass
{
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            _myProperty = value;
        }
    }
}

On the field (EEqualityCheckMode.Default)

// User-Code
public partial class MyClass
{
    [EqualityCheck(EEqualityCheckMode.Default)]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty == value)
                return;
            _myProperty = value;
        }
    }
}

GuardAttribute

The guard attribute allows to make the source generator use custom validation methods to validate the property. See the ValidationStrategyAttribute for more information on how to change validation handling. It cannot be placed on the class.

The guard method must be a method with the signature bool MethodName(T oldValue, T newValue) and has to be accessible from the generated code. Multiple guards may be used with all of them being checked in the order they are placed on the field.

On the field

// User-Code
namespace SomethingFancy
{
    public static GuardMethods
    {
        public static bool MyGuard(int oldValue, int newValue) => newValue > 0;
    }
}
public partial class MyClass
{
    [Guard("MyGuard", className: "SomethingFancy.GuardMethods")]
    private int _myProperty;
}

// Generated-Code
public partial class MyClass
{
    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (!SomethingFancy.GuardMethods.MyGuard(_myProperty, value))
                throw new System.ArgumentException("Validation of Field failed: Guard method SomethingFancy.GuardMethods.MyGuard failed", nameof(value));
            _myProperty = value;
        }
    }
}

Project Notes

This project is part of my personal utility libraries i use in my projects. The following few paragraphs are meant to give you an overview of the project and how you can contribute to it.

Building

This project uses GitHub Actions for continuous integration. The workflow is defined in .github/workflows/main.yml. It includes steps for restoring dependencies, building the project, and publishing a NuGet package.

Test coverage

This project is covered by unit tests for the generator only. This means that the generated code is not yet tested.

Contributing

Contributions are welcome! Please submit a pull request or create a discussion to discuss any changes you wish to make.

Code of Conduct

Be excellent to each other.

Contributors Agreement

First of all, thank you for your interest in contributing to this project! Please add yourself to the list of contributors in the CONTRIBUTORS file when submitting your first pull request. Also, please always add the following to your pull request:

By contributing to this project, you agree to the following terms:
- You grant me and any other person who receives a copy of this project the right to use your contribution under the
  terms of the GNU Lesser General Public License v3.0.
- You grant me and any other person who receives a copy of this project the right to relicense your contribution under
  any other license.
- You grant me and any other person who receives a copy of this project the right to change your contribution.
- You waive your right to your contribution and transfer all rights to me and every user of this project.
- You agree that your contribution is free of any third-party rights.
- You agree that your contribution is given without any compensation.
- You agree that I may remove your contribution at any time for any reason.
- You confirm that you have the right to grant the above rights and that you are not violating any third-party rights
  by granting these rights.
- You confirm that your contribution is not subject to any license agreement or other agreement or obligation, which
  conflicts with the above terms.

This is necessary to ensure that this project can be licensed under the GNU Lesser General Public License v3.0 and that a license change is possible in the future if necessary (e.g., to a more permissive license). It also ensures that I can remove your contribution if necessary (e.g., because it violates third-party rights) and that I can change your contribution if necessary (e.g., to fix a typo, change implementation details, or improve performance). It also shields me and every user of this project from any liability regarding your contribution by deflecting any potential liability caused by your contribution to you (e.g., if your contribution violates the rights of your employer). Feel free to discuss this agreement in the discussions section of this repository, i am open to changes here (as long as they do not open me or any other user of this project to any liability due to a malicious contribution).

License

This project is licensed under the GNU Lesser General Public License v3.0. See the LICENSE file for details.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.3.0.18 196 8/28/2024
2.2.0.17 88 8/2/2024
2.1.0.16 123 7/5/2024
2.1.0.15 94 6/27/2024
2.0.1.14 106 6/24/2024
2.0.0.13 142 4/29/2024
1.0.0.12 128 4/20/2024
1.0.0.11 168 3/22/2024
1.0.0.10 204 3/11/2024
1.0.0.9 196 3/8/2024