Mutty 1.0.47
dotnet add package Mutty --version 1.0.47
NuGet\Install-Package Mutty -Version 1.0.47
<PackageReference Include="Mutty" Version="1.0.47"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Mutty" Version="1.0.47" />
<PackageReference Include="Mutty"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Mutty --version 1.0.47
#r "nuget: Mutty, 1.0.47"
#addin nuget:?package=Mutty&version=1.0.47
#tool nuget:?package=Mutty&version=1.0.47
Mutty
Immutable Record Mutation Made Easy
![]() |
Mutty is a C# Incremental Source Generator that provides a convenient way to work with immutable records by generating mutable wrappers for them. These wrappers allow you to modify properties of immutable records in a clean, controlled manner and then convert them back into immutable records. |
---|
📝 Table of Contents
📌 Features
Current Features
- Automated Mutable Wrappers: Automatically generates mutable wrapper classes for your immutable records using Roslyn's Incremental Source Generation.
- Deep Nesting Support: Easily handle complex nested structures without tedious and error-prone manual code.
- Immutable to Mutable Conversion: Seamlessly switch between immutable and mutable versions of your records using implicit conversions.
- Ideal for Flux Architecture: Works great with Flux architecture, allowing you to manage state changes in a predictable and immutable way.
- Helper Methods:
- Provides a
Produce
method to apply mutations to your immutable records using the generated mutable wrappers. - Also includes
CreateDraft
andFinishDraft
methods for more granular control... - ...and
AsMutable
andToImmutable
extension methods for collections.
- Provides a
How Mutty Works
Mutty uses a custom attribute [MutableGeneration]
to mark immutable records for which you want to generate mutable wrappers.
The Incremental Source Generator detects these records and generates corresponding mutable wrapper classes and extension methods.
The basic idea is that with Mutty, you will apply all your changes to a temporary mutable wrapper, which acts as a proxy of the immutable record. Once all your mutations are completed, Mutty will produce the next immutable state based on the mutations to the mutable wrapper. This means that you can interact with your data by simply modifying it while keeping all the benefits of immutable data.
Using Mutty is like having a personal assistant. The assistant takes a letter (the current state) and gives you a copy (mutable wrapper) to jot changes onto. Once you are done, the assistant will take your draft and produce the real immutable, final letter for you (the next state).
Example Usage
Suppose you have the following immutable records:
namespace Mutty.ConsoleApp;
[MutableGeneration]
public record Student(string Email, StudentDetails Details, ImmutableList<Enrollment> Enrollments);
[MutableGeneration]
public record StudentDetails(string Name, int Age);
[MutableGeneration]
public record Enrollment(Course Course, DateTime EnrollmentDate);
[MutableGeneration]
public record Course(string Title, string Description, ImmutableList<Module> Modules);
[MutableGeneration]
public record Module(string Name, ImmutableList<Lesson> Lessons);
[MutableGeneration]
public record Lesson(string Title, string Content);
Note: For simplicity, this example focuses on the
Student
record, but Mutty also generates similar mutable wrappers forStudentDetails
,Enrollment
,Course
,Module
, andLesson
.
When you add the [MutableGeneration]
attribute to your records, Mutty will automatically generate the corresponding mutable wrapper classes:
// <auto-generated />
// This file is auto-generated by Mutty.
using System.Collections.Immutable;
namespace Mutty.ConsoleApp
{
/// <summary>
/// The mutable wrapper for the <see cref="Student"/> record.
/// </summary>
public partial class MutableStudent
{
private Student _record;
/// <summary>
/// Initializes a new instance of the <see cref="MutableStudent"/> class.
/// </summary>
/// <param name="record">The record to wrap.</param>
public MutableStudent(Student record)
{
_record = record;
Email = _record.Email;
Details = _record.Details;
Enrollments = _record.Enrollments.AsMutable();
}
/// <summary>
/// Builds a new instance of the <see cref="Student"/> class.
/// </summary>
public Student Build()
{
return _record with
{
Email = this.Email,
Details = this.Details,
Enrollments = this.Enrollments.ToImmutable(),
};
}
/// <summary>
/// Performs an implicit conversion from <see cref="Student"/> to <see cref="MutableStudent"/>.
/// </summary>
public static implicit operator MutableStudent(Student record)
{
return new MutableStudent(record);
}
/// <summary>
/// Performs an implicit conversion from <see cref="MutableStudent"/> to <see cref="Student"/>.
/// </summary>
public static implicit operator Student(MutableStudent mutable)
{
return mutable.Build();
}
/// <summary>
/// Gets or sets the Email.
/// </summary>
public string Email { get; set; }
/// <summary>
/// Gets or sets the Record Details.
/// </summary>
public MutableStudentDetails Details { get; set; }
/// <summary>
/// Gets or sets the ImmutableCollection Enrollments.
/// </summary>
public List<MutableEnrollment> Enrollments { get; set; }
}
}
How to Use the Generated Code
Once the code is generated, you can use the mutable wrappers to modify your immutable records as needed.
Deep Nesting Example
Here's an example demonstrating how easy it is to handle deeply nested structures using Mutty:
public sealed class ExampleImmutableArray : ExampleBase
{
public override void Run()
{
DisplayHeader("ImmutableArray Example");
// Initialize original immutable objects
Student student = Factories.CreateJohnDoe();
// Use the Produce method to create an updated student object with mutations
Student updatedStudent = student.Produce(mutable =>
{
// Modify the title of the first lesson in the first module of the first course
mutable.Enrollments[0].Course.Modules[0].Lessons[0].Title = "=== NEW TITLE ===";
});
// Display the original and updated student objects
DisplayStudentTree(student, 4);
DisplayStudentTree(updatedStudent, 4);
}
}
Comparison with with
Notation
Without Mutty, updating deeply nested structures using the with
expression can become cumbersome and error-prone:
// Using 'with' notation
var updatedStudent = student with
{
Enrollments = student.Enrollments.SetItem(0, student.Enrollments[0] with
{
Course = student.Enrollments[0].Course with
{
Modules = student.Enrollments[0].Course.Modules.SetItem(0, student.Enrollments[0].Course.Modules[0] with
{
Lessons = student.Enrollments[0].Course.Modules[0].Lessons.SetItem(0, student.Enrollments[0].Course.Modules[0].Lessons[0] with
{
Title = "=== NEW TITLE ==="
})
})
}
})
};
Using Mutty, the same operation is simpler and more intuitive:
// Using Mutty
Student updatedStudent = student.Produce(mutable =>
{
mutable.Enrollments[0].Course.Modules[0].Lessons[0].Title = "=== NEW TITLE ===";
});
Ideal for Flux Architecture
Mutty is an excellent fit for state management patterns like Flux. With Mutty, you can maintain immutable state while easily applying updates through the mutable wrappers. This keeps your state management predictable and efficient, especially in complex applications with deeply nested state.
Installation
To use Mutty in your project:
Add the Mutty package:
- You can add it as a NuGet package (if it's available as a package).
Annotate Your Records:
- Simply annotate your records with
[MutableGeneration]
to indicate that Mutty should generate a mutable wrapper for them.
- Simply annotate your records with
Build Your Project:
- The Incremental Source Generator will automatically detect the annotated records and generate the corresponding mutable wrappers and extension methods during the build process.
Best Practices
- Immutable by Default: Use immutable records for your core data models to ensure thread safety and prevent unintended side effects.
- Mutate with Care: Use the generated mutable wrappers when you need to make changes, but remember to always convert back to the immutable form before exposing the data.
- Leverage the Implicit Conversion: Mutty provides implicit conversions between the immutable and mutable versions of your records, making it easy to switch between the two.
Contributing
If you want to contribute to Mutty or report issues:
- GitHub Repository: Mutty on GitHub
- Issues: Use the GitHub Issues tab to report bugs or request features.
License
Mutty is open-source software licensed under the Apache License 2.0.
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. |
.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
- Microsoft.CodeAnalysis.AnalyzerUtilities (>= 3.3.4)
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 |
---|---|---|
1.0.47 | 81 | 4/11/2025 |
1.0.46 | 123 | 4/10/2025 |
0.0.0-preview.0.32 | 60 | 2/27/2025 |