SettableOnceProperty 0.1.5

dotnet add package SettableOnceProperty --version 0.1.5                
NuGet\Install-Package SettableOnceProperty -Version 0.1.5                
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="SettableOnceProperty" Version="0.1.5" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add SettableOnceProperty --version 0.1.5                
#r "nuget: SettableOnceProperty, 0.1.5"                
#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 SettableOnceProperty as a Cake Addin
#addin nuget:?package=SettableOnceProperty&version=0.1.5

// Install SettableOnceProperty as a Cake Tool
#tool nuget:?package=SettableOnceProperty&version=0.1.5                

SettableOnceProperty

Motivation

While playing with .Net built-in Depency Injection (D.I.), I found myself needing to set some properties of some newly created objects by D.I. mechanism.

But some of such properties needed to be set only once through runtime lifetime.

Since I have a lot of those properties, I ended up choosing to use the new .Net Incremental Source Generator tool to do so.

Here is what I came up with.

Description

This package use an Incremental Source Generator underneath to augment classes with settable maximum n times properties.

When appropriately marked, such properties use an hidden SettableNTimesProperty<T> generic class that encapsulate a T property while keeping track of how many times it was set, and nullify any extra set calls beyond maximum limit, limit that you can provide via an Attribute.

Usage

If you want to mark a property being settable max n times, you have to follow thoses rules :

  • Define such properties inside an interface

  • add using SetOnceGenerator; namespace

  • Add above such properties either [SetOnce] attribute or [SetNTimes(n)] attribue

  • On any concrete classes that implement that given interface, make sure to modify it to be partial

  • Optionally, you can add your own logic to handle warnings when trying to get or set the property extending SettableNTimesProperty.GetWarning() and SettableNTimesProperty.SetWarning() method

Example

Lets say you have this DTO class :

internal class DTO
{
    int ID { get; init; }
    string Name { get; init; }

    public DTO(int id, string name = "Default_DTO_Name")
    {
        ID = id;
        Name = name;
    }
}

In order to make its properties settable only once instead of using init, modify your code this way :

internal partial class DTO : IDTO
{
    public DTO(int id, string name = "Default_DTO_Name")
    {
        ((IDTO)this).ID = id;
        ((IDTO)this).Name = name;
    }
}

and add this interface

using SetOnceGenerator;
public interface IDTO
{
    [SetOnce]
    int ID { get; set; }

    [SetOnce]
    string Name { get; set; }
}  

If you want to allow multiple set, up to n times maximum, use [SetNTimes(n)] attribute instead of [SetOnce]

Optional warning handling

By default, nothing warn you when you try to

  • Get a non already setted property

  • Set an already maximum setted property

You can handle this with your own logic by uncommenting and extending the provided partial class SettableNTimesProperty, found in your project directory under the automatically created "Custom_Warning" folder.

You can modify the 2 provided partial methods, GetWarning() and SetWarning() to do so.

SettableNTimesProperty class also expose 2 private fields you can use to customize your warning :

  • `_propertyName`` the name of the property

  • _maximumSettableTimes the maximum settable times this property can be setted

For example :

partial void GetWarning()
{
    Console.WriteLine($"{_propertyName} hasn't been set yet ! (returning default value instead)");    
}

partial void SetWarning()
{
    Console.WriteLine($"{_propertyName} has already reach its maximum ({_maximumSettableTimes}) settable times.");
}

Disable automatic copy of Custom_Warnings folder

If you don't wan't to handle you custom logic of Get and Set warning, and allow to remove the automatically generated Custom_Warnings folder and SettableNTimesProperty class, then you can edit your .csproj file to set RefreshCopy to false :

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <RootNamespace>Testing_SetOncePackage</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
      <RefreshCopy>False</RefreshCopy>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="SettableOnceProperty" Version="0.1.3" />
  </ItemGroup>

</Project>

Embedding attributes

Following Andrew Lock [https://andrewlock.net/creating-a-source-generator-part-8-solving-the-source-generator-marker-attribute-problem-part2/] tutorial,

I ended up using a public attributes DLL to store and share my [SetNTimes(n)] and [SetOnce] attributes,

still alowing to automatically generate and embed those attributes in consuming project assembly by setting

SET_ONCE_GENERATOR_EMBED_ATTRIBUTES MS-Build variable in your .csproj consuming project properties :

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
	<LangVersion>latest</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
	<DefineConstants>SET_ONCE_GENERATOR_EMBED_ATTRIBUTES</DefineConstants>
  </PropertyGroup>
    
</Project>

Excluding generated SettableNTimesProperty<T>

The backbone of those decorated properties to be set up to n times is in the automatically generated and embedded

SettableNTimesProperty<T> partial class. If you prefer to exclude it and furnish your own implementation of this partial class,

you can define SET_ONCE_GENERATOR_EXCLUDE_SETTABLE_N_TIMES_PROPERTY MS-Build variable in your .csproj consuming project properties :

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
	<LangVersion>latest</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
	<DefineConstants>SET_ONCE_GENERATOR_EXCLUDE_SETTABLE_N_TIMES_PROPERTY</DefineConstants>
  </PropertyGroup>
    
</Project>

Note

I first used a bool backend field to manage this but ended up generalising it to be settable n times.

Even though this is now the underneath mechanism , I kept naming it SettableOnceProperty, since I suppose it is the most common scenario, and what people are looking for.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

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
0.1.5 111 7/12/2024

Handle multiple/cross projects types when generating settableNtimes properties and ensure "using SetOnceGenerator" is always added to generated files. Code has been refactored