Immutype by Nikolay Pianikov
Nuget / site data
Details
Info
Name: Immutype
Immutable for .NET.
Author: Nikolay Pianikov
NuGet: https://www.nuget.org/packages/Immutype/
You can find more details at https://github.com/DevTeam/Immutype
Source : https://github.com/DevTeam/Immutype
Original Readme
Immutype
Immutype is .NET code generator creating extension methods for records, structures, and classes marked by the attribute [Immutype.Target]
to efficiently operate with instances of these types like with immutable ones.
For instance, for the type Foo for the constructor parameter values of type IEnumerable<int>
following extension methods are generated:
Foo WithValues(this Foo it, params int[] values)
- to replace values by the new ones using a method with variable number of argumentsFoo WithValues(this Foo it, IEnumerable<int> values)
- to replace values by the new onesFoo AddValues(this Foo it, params int[] values)
- to add values using a method with variable number of argumentsFoo AddValues(this Foo it, IEnumerable<int> values)
- to add valuesFoo RemoveValues(this Foo it, params int[] values)
- to remove values using a method with variable number of argumentsFoo RemoveValues(this Foo it, IEnumerable<int> values)
- to remove valuesFoo ClearValues(this Foo it)
- to clear all values
For the type Foo for the constructor parameter value of other types, like int
, with default value 99
following extension methods are generated:
Foo WithValue(this Foo it, int value)
- to replace a value by the new oneFoo WithDefaultValue(this Foo it)
- to replace a value by the default value 99
The extensions methods above are generating automatically for each public
or internal
type, like Foo marked by the attribute [Immutype.Target]
in the static class named as FooExtensions. This generated class FooExtensions is static, has the same accessibility level and the same namespace like a target class Foo. Each generated static extension method has two attributes:
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
- to improve performance[Pure]
- to indicate that this method is pure, that is, it does not make any visible state changes
Immutype supports nullable reference and value types and the following list of enumerable types:
- Arrays
IEnumerable<T>
List<T>
IList<T>
IReadOnlyCollection<T>
IReadOnlyList<T>
ICollection<T>
HashSet<T>
ISet<T>
Queue<T>
Stack<T>
IReadOnlyCollection<T>
IReadOnlyList<T>
IReadOnlySet<T>
ImmutableList<T>
IImmutableList<T>
ImmutableArray<T>
ImmutableQueue<T>
IImmutableQueue<T>
ImmutableStack<T>
IImmutableStack<T>
Immutype supports IIncrementalGenerator as well as ISourceGenerator so it works quite effective.
NuGet package
Package Manager
Install-Package Immutype
.NET CLI
dotnet add package Immutype
Development environment requirements
Supported frameworks
- .NET and .NET Core 1.0+
- .NET Standard 1.0+
- .NET Framework 3.5+
- UWP/XBOX
- .NET IoT
- Xamarin
- .NET Multi-platform App UI (MAUI)
Usage Scenarios
- Basics
Sample scenario
[Immutype.Target]
internal record Person(
string Name,
bool HasPassport = true,
int Age = 0,
ImmutableArray<Person> Friends = default);
public class SampleScenario
{
public void Run()
{
var john = new Person("John", false, 15)
.AddFriends(
new Person("David").WithAge(16),
new Person("James").WithAge(17)
.WithFriends(new Person("Tyler").WithAge(16)));
john.Friends.Length.ShouldBe(2);
john = john.WithAge(16).WithDefaultHasPassport();
john.Age.ShouldBe(16);
john.HasPassport.ShouldBeTrue();
john = john.AddFriends(
new Person("Daniel").WithAge(17),
new Person("Sophia").WithAge(18));
john.Friends.Length.ShouldBe(4);
john = john.RemoveFriends(new Person("David").WithAge(16));
john.Friends.Length.ShouldBe(3);
}
}
Array
[Immutype.Target]
internal readonly record struct Person(string Name, int Age = 0, params Person[] Friends);
public class Array
{
public void Run()
{
var john = new Person("John")
.WithAge(15)
.AddFriends(new Person("David").WithAge(16))
.AddFriends(
new Person("James"),
new Person("Daniel").WithAge(17));
john.Friends.Length.ShouldBe(3);
}
}
Applying defaults
[Immutype.Target]
internal readonly record struct Person(string Name = "John", int Age = 17);
public class ApplyingDefaults
{
public void Run()
{
var john = new Person("David", 15)
.WithDefaultAge()
.WithDefaultName();
john.Name.ShouldBe("John");
john.Age.ShouldBe(17);
}
}
Clearing
[Immutype.Target]
internal readonly record struct Person(
string Name,
int Age = 0,
params Person[] Friends);
public class Clearing
{
public void Run()
{
var john = new Person("John",15, new Person("David").WithAge(16))
.AddFriends(new Person("James"));
john = john.ClearFriends();
john.Friends.Length.ShouldBe(0);
}
}
Immutable collection
[Immutype.Target]
internal readonly struct Person
{
public readonly string Name;
public readonly int Age;
public readonly IImmutableList<Person> Friends;
public Person(
string name,
int age = 0,
IImmutableList<Person>? friends = default)
{
Name = name;
Age = age;
Friends = friends ?? ImmutableList<Person>.Empty;
}
};
public class ImmutableCollection
{
public void Run()
{
var john = new Person("John",15)
.WithFriends(
new Person("David").WithAge(16),
new Person("James").WithAge(17))
.AddFriends(
new Person("David").WithAge(22));
john.Friends.Count.ShouldBe(3);
}
}
Removing
[Immutype.Target]
internal readonly record struct Person(
string Name,
int Age = 0,
params Person[] Friends);
public class Removing
{
public void Run()
{
var john = new Person("John",15, new Person("David").WithAge(16))
.AddFriends(new Person("James"));
john = john.RemoveFriends(new Person("James"));
john.Friends.Length.ShouldBe(1);
}
}
Generic types
It is possible to use generic types including any generic constraints.
[Immutype.Target]
internal record Person<TAge>(string Name, TAge Age = default, IEnumerable<Person<TAge>>? Friends = default)
where TAge : struct;
public class GenericTypes
{
public void Run()
{
var john = new Person<int>("John")
.WithAge(15)
.WithFriends(new Person<int>("David").WithAge(16))
.AddFriends(
new Person<int>("James"),
new Person<int>("Daniel").WithAge(17));
john.Friends?.Count().ShouldBe(3);
}
}
Nullable collection
[Immutype.Target]
internal record Person(
string Name,
int? Age = default,
ICollection<Person>? Friends = default);
public class NullableCollection
{
public void Run()
{
var john = new Person("John",15)
.AddFriends(
new Person("David").WithAge(16),
new Person("James").WithAge(17)
.WithFriends(new Person("Tyler").WithAge(16)));
john.Friends?.Count.ShouldBe(2);
}
}
Set
[Immutype.Target]
internal record Person(
string Name,
int Age = 0,
ISet<Person>? Friends = default);
public class Set
{
public void Run()
{
var john = new Person("John",15)
.AddFriends(
new Person("David").WithAge(16),
new Person("David").WithAge(16),
new Person("James").WithAge(17)
.WithFriends(new Person("Tyler").WithAge(16)));
john.Friends?.Count.ShouldBe(2);
}
}
Record with constructor
[Immutype.Target]
internal record Person
{
public Person(
string name,
int? age = default,
ICollection<Person>? friends = default)
{
Name = name;
Age = age;
Friends = friends;
}
public string Name { get; }
public int? Age { get; }
public ICollection<Person>? Friends { get; }
public void Deconstruct(
out string name,
out int? age,
out ICollection<Person>? friends)
{
name = Name;
age = Age;
friends = Friends;
}
}
public class RecordWithConstructor
{
public void Run()
{
var john = new Person("John",15)
.WithFriends(
new Person("David").WithAge(16),
new Person("James").WithAge(17)
.WithFriends(new Person("Tyler").WithAge(16)));
john.Friends?.Count.ShouldBe(2);
}
}
Explicit constructor choice
[Immutype.Target]
internal readonly struct Person
{
public readonly string Name;
public readonly int Age;
public readonly IImmutableList<Person> Friends;
// You can explicitly select a constructor by marking it with the [Immutype.Target] attribute
[Immutype.Target]
public Person(
string name,
int age = 0,
IImmutableList<Person>? friends = default)
{
Name = name;
Age = age;
Friends = friends ?? ImmutableList<Person>.Empty;
}
public Person(
string name,
int age,
IImmutableList<Person>? friends,
int someArg = 99)
{
Name = name;
Age = age;
Friends = friends ?? ImmutableList<Person>.Empty;
}
};
public class ExplicitConstructorChoice
{
public void Run()
{
var john = new Person("John",15)
.WithFriends(
new Person("David").WithAge(16),
new Person("James").WithAge(17))
.AddFriends(
new Person("David").WithAge(22));
john.Friends.Count.ShouldBe(3);
}
}
About
Immutable from constructors
How to use
Example ( source csproj, source files )
- CSharp Project
- Program.cs
- Person.cs
This is the CSharp Project that references Immutype
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Immutype" Version="1.0.14" OutputItemType="Analyzer" >
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>
This is the use of Immutype in Program.cs
using ImmutypeDemo;
Person p = new("Andrei","Ignat");
var p2= p.WithFirstName("Test");
Console.WriteLine(p2.LastName);
This is the use of Immutype in Person.cs
namespace ImmutypeDemo;
[Immutype.Target]
internal class Person
{
public string? FirstName;
public Person()
{
}
public Person(string? FirstName,string LastName)
{
this.FirstName = FirstName;
this.LastName = LastName;
}
public int ID { get; set; }
public string? LastName { get; set;}
}
Generated Files
Those are taken from $(BaseIntermediateOutputPath)\GX
- Immutype.Components.Contracts.cs
- ImmutypeDemo.Person.cs
// ReSharper disable CheckNamespace
// ReSharper disable ClassNeverInstantiated.Global
namespace Immutype
{
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor, Inherited = false)]
public class TargetAttribute: Attribute { }
}
namespace ImmutypeDemo;
using System.Collections.Generic;
using System.Linq;
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal static partial class PersonExtensions{
[System.Runtime.CompilerServices.MethodImplAttribute((System.Runtime.CompilerServices.MethodImplOptions)256),System.Diagnostics.Contracts.PureAttribute]
public static ImmutypeDemo.Person WithFirstName(this ImmutypeDemo.Person it,string? FirstName){
if( it==default(ImmutypeDemo.Person))throw new System.ArgumentNullException("it");
return new ImmutypeDemo.Person(FirstName, it.LastName );}
[System.Runtime.CompilerServices.MethodImplAttribute((System.Runtime.CompilerServices.MethodImplOptions)256),System.Diagnostics.Contracts.PureAttribute]
public static ImmutypeDemo.Person WithLastName(this ImmutypeDemo.Person it,string LastName){
if( it==default(ImmutypeDemo.Person))throw new System.ArgumentNullException("it");
if(LastName==default(string ))throw new System.ArgumentNullException("LastName");
return new ImmutypeDemo.Person( it.FirstName,LastName);}}
Usefull
Download Example (.NET C# )
Share Immutype
https://ignatandrei.github.io/RSCG_Examples/v2/docs/Immutype