SettableOnceProperty 0.2.0
dotnet add package SettableOnceProperty --version 0.2.0
NuGet\Install-Package SettableOnceProperty -Version 0.2.0
<PackageReference Include="SettableOnceProperty" Version="0.2.0" />
<PackageVersion Include="SettableOnceProperty" Version="0.2.0" />
<PackageReference Include="SettableOnceProperty" />
paket add SettableOnceProperty --version 0.2.0
#r "nuget: SettableOnceProperty, 0.2.0"
#:package SettableOnceProperty@0.2.0
#addin nuget:?package=SettableOnceProperty&version=0.2.0
#tool nuget:?package=SettableOnceProperty&version=0.2.0
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
interfaceor anabstract class(since C# 13)add
using SetOnceGenerator;namespaceAdd above such properties either
[SetOnce]attribute or[SetNTimes(n)]attribueOn any concrete classes that implement that given
interface, make sure to modify it to bepartialOr on any
abstract classhave such properties, make sure to declare them all aspartialincluding theabstract classitself (starting from C# 13).Optionally, you can add your own logic to handle warnings when trying to get or set the property extending
SettableNTimesProperty.GetWarning()andSettableNTimesProperty.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]
Abstract partial properties
Starting from C# 13.0, we can define partial properties, implemented in another location. We take advantage of that feature to offer using our [SetNTimes] and [SetOnce] attributes on properties definied in some abstract class :
using SetOnceGenerator;
public abstract partial class ADTO : IDTO
{
[SetOnce]
public partial bool IsFromAbstractClass {get; set;}
}
Now you can modify your class inheriting from the abstract class to not be partial any more and access the abstract class defined properties directly :
internal class DTO : ADTO
{
public DTO(int id, string name = "Default_DTO_Name")
{
((IDTO)this).ID = id;
((IDTO)this).Name = name;
IsFromAbstractClass = true;
}
}
*Do note that if the concrete class also directly implement some interfaces that define such of our settable properties, then it should be redefined as partial again 😗
internal partial class DTO : ADTO, IAnotherDTO
{
public DTO(int id, string name = "Default_DTO_Name", string aSettableProperty = "")
{
((IDTO)this).ID = id;
((IDTO)this).Name = name;
IsFromAbstractClass = true;
((IAnotherDTO)this).ASettableProperty = aSettableProperty;
}
}
Optional warning handling
By default, nothing warn you when you try to
Geta non already setted propertySetan 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 :
_propertyNamethe name of the property_maximumSettableTimesthe 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.");
}
Customize genrator behaviors
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 tutorial,
I ended up using a public attributes DLL to store and share my [SetNTimes(n)] and [SetOnce] attributes.
I still allowing 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>
Hide generated partial properties from abstract classes
If your project use a C# version prior to 13.0, you can't define partial properties, and so you cannot define settable properties in abstract class, only in interface.
Since you have control on your own code, you can prevent yourself to use the [SetNTimes] or [SetOnce] attribute on your own abstract class properties.
But, even since no corresponding code should be generated, as you doesn't have direct control on it, we expose a constant, HIDE_GENERATED_ABSTRACT_PROPERTIES, that you can define in your .csproj to hide the generated settable partial property :
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<LangVersion>12.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DefineConstants>HIDE_GENERATED_PARTIAL_PROPERTIES</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 | Versions 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. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.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. |
-
.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.
Added new C# 13.0 partial property feature allowing to define such partial settable properties in abstract classes in addition to interfaces.