Monify by Paul Martin
NuGet / site data
Details
Info
Name: Monify
Monify, a .NET Roslyn source generator, automates the creation of strongly-typed wrappers around single values, improving semantic clarity, type safety, and maintainability.
Author: Paul Martin
NuGet: https://www.nuget.org/packages/Monify/
You can find more details at https://github.com/MooVC/monify
Source: https://github.com/MooVC/monify
Author
Paul Martin

Original Readme
Monify

Monify, a .NET Roslyn source generator, automates the creation of strongly-typed wrappers around single values, improving semantic clarity, type safety, and maintainability.
Requirements
- C# v2.0 or later.
- Visual Studio 2022 v17.4 or later, or any compatible IDE that supports Roslyn source generators.
Installation
To install Monify, use the following command in your package manager console:
install-package Monify
Usage
Monify turns a class, record, or struct into a strongly typed wrapper around a single value. To opt in, annotate a partial type with the Monify attribute and specify the encapsulated value type.
using Monify;
[Monify<int>]
public partial struct Age;
For language versions earlier than C# 11:
using Monify;
[Monify(Type = typeof(int))]
public partial struct Age;
Generated Members
When applied, Monify generates the boilerplate needed for a lightweight value object:
- A private readonly field
_valuethat stores the encapsulated value. - A constructor accepting the underlying value:
Age(int value). - Implicit conversion operators to and from the encapsulated type.
- Implementations of
IEquatable<Age>andIEquatable<int>. - Equality (
==) and inequality (!=) operators for comparing with other instances or with the underlying value. - Overrides of
Equals(object),GetHashCode(), andToString().
Example
Age age = 42; // implicit conversion from int
int value = age; // implicit conversion to int
if (age == value)
{
// comparisons work with both Age and int
}
Classes annotated with Monify are sealed automatically to preserve immutability. Types must be partial and cannot declare additional state; the included analyzers will warn when these guidelines are not followed.
Nested wrappers and circular guards
Monify also follows chains of nested wrappers and automatically emits the necessary implicit conversions so the outermost wrapper can convert directly to and from the innermost value type. For example, if Money wraps Amount and Amount wraps decimal, conversions will be generated for every hop, allowing callers to cast Money to decimal (and vice versa) without manual glue code.
To keep the generator safe, conversion discovery halts as soon as a type repeats in the chain. This prevents circular relationships (e.g. Outer wrapping Inner while Inner wraps Outer) from producing infinite loops or ambiguous operators while still supporting arbitrarily deep, non-circular nesting.
Analyzers
Monify includes several analyzers to assist engineers with its usage. These are:
| Rule ID | Category | Severity | Notes |
|---|---|---|---|
| MONFY01 | Usage | Warning | Type is not compatible with Monify |
| MONFY02 | Usage | Warning | Type is not Partial |
| MONFY03 | Design | Error | Type captures State |
| MONFY04 | Design | Error | Self Referencing Type |
Contributing
Contributions are welcome - see the CONTRIBUTING.md file for details.
License
This project is licensed under the MIT License - see the LICENSE.md file for details.
About
Generate primitive strongly typed wrapper around a single value object
How to use
Example (source csproj, source files)
- CSharp Project
- Program.cs
- Person.cs
This is the CSharp Project that references Monify
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Monify" Version="1.3.1">
<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 Monify in Program.cs
// See https://aka.ms/new-console-template for more information
using PrimitiveDemo;
Console.WriteLine("Hello, World!");
Person p= new ();
p.Age = 55;
Console.WriteLine($"Person's age is {p.Age}");
Console.WriteLine($"Is adult: {p.IsAdult}");
This is the use of Monify in Person.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace PrimitiveDemo;
[Monify.Monify<int>]
public partial struct Age;
internal class Person
{
public Age Age \{ get; set; }
public bool IsAdult => Age >= 18;
}
Generated Files
Those are taken from $(BaseIntermediateOutputPath)\GX
- MonifyAttribute.Generic.g.cs
- MonifyAttribute.NonGeneric.g.cs
- Monify.Internal.HashCode.g.cs
- Monify.Internal.SequenceEqualityComparer.g.cs
- PrimitiveDemo.Age.ConvertFrom.g.cs
- PrimitiveDemo.Age.ConvertTo.g.cs
- PrimitiveDemo.Age.ctor.g.cs
- PrimitiveDemo.Age.Equality.Self.g.cs
- PrimitiveDemo.Age.Equality.Value.g.cs
- PrimitiveDemo.Age.Equals.g.cs
- PrimitiveDemo.Age.GetHashCode.g.cs
- PrimitiveDemo.Age.IEquatable.Self.Equals.g.cs
- PrimitiveDemo.Age.IEquatable.Self.g.cs
- PrimitiveDemo.Age.IEquatable.Value.Equals.g.cs
- PrimitiveDemo.Age.IEquatable.Value.g.cs
- PrimitiveDemo.Age.Inequality.Self.g.cs
- PrimitiveDemo.Age.Inequality.Value.g.cs
- PrimitiveDemo.Age.ToString.g.cs
- PrimitiveDemo.Age._value.g.cs
namespace Monify
{
using System;
using System.Diagnostics.CodeAnalysis;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
internal sealed class MonifyAttribute<T>
: Attribute
{
}
}
namespace Monify
{
using System;
using System.Diagnostics.CodeAnalysis;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
internal sealed class MonifyAttribute
: Attribute
{
private Type _type;
public Type Type
{
get
{
return _type;
}
set
{
_type = value;
}
}
}
}
namespace Monify.Internal
{
using System;
using System.Collections;
internal static class HashCode
{
private const int HashSeed = 0x1505;
private const int HashPrime = -1521134295;
public static int Combine(params object[] values)
{
int hash = HashSeed;
foreach (object value in values)
{
if (value is IEnumerable && !(value is string))
{
IEnumerable enumerable = (IEnumerable)value;
foreach (object element in enumerable)
{
hash = PerformCombine(hash, element);
}
}
else
{
hash = PerformCombine(hash, value);
}
}
return hash;
}
private static int PerformCombine(int hash, object value)
{
int other = GetHashCode(value);
unchecked
{
return (other * HashPrime) + hash;
}
}
private static int GetHashCode(object value)
{
int code = 0;
if (value != null)
{
code = value.GetHashCode();
}
return code;
}
}
}
namespace Monify.Internal
{
using System;
using System.Collections;
internal sealed class SequenceEqualityComparer
{
public static readonly SequenceEqualityComparer Default = new SequenceEqualityComparer();
public bool Equals(IEnumerable left, IEnumerable right)
{
if (ReferenceEquals(left, right))
{
return true;
}
if (ReferenceEquals(left, null) || ReferenceEquals(null, right))
{
return false;
}
return Equals(left.GetEnumerator(), right.GetEnumerator());
}
public int GetHashCode(IEnumerable enumerable)
{
return HashCode.Combine(enumerable);
}
private static bool Equals(IEnumerator left, IEnumerator right)
{
while (left.MoveNext())
{
if (!right.MoveNext())
{
return false;
}
if (!Equals(left.Current, right.Current))
{
return false;
}
}
return !right.MoveNext();
}
}
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age
{
public static implicit operator int(Age subject)
{
if (ReferenceEquals(subject, null))
{
throw new ArgumentNullException("subject");
}
return subject._value;
}
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age
{
public static implicit operator Age(int value)
{
return new Age(value);
}
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age
{
public Age(int value)
{
_value = value;
}
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age
{
public static bool operator ==(Age left, Age right)
{
if (ReferenceEquals(left, right))
{
return true;
}
if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
{
return false;
}
return left.Equals(right);
}
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age
{
public static bool operator ==(Age left, int right)
{
if (ReferenceEquals(left, right))
{
return true;
}
if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
{
return false;
}
return left.Equals(right);
}
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age
{
public override bool Equals(object other)
{
if (other is Age)
{
return Equals((Age)other);
}
return false;
}
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age
{
public override int GetHashCode()
{
return global::Monify.Internal.HashCode.Combine(_value);
}
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age
{
public bool Equals(Age other)
{
if (ReferenceEquals(this, other))
{
return true;
}
if (ReferenceEquals(other, null))
{
return false;
}
return Equals(other._value);
}
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age : IEquatable<Age>
{
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age
{
public bool Equals(int other)
{
if (ReferenceEquals(this, other))
{
return true;
}
if (ReferenceEquals(other, null))
{
return false;
}
return global::System.Collections.Generic.EqualityComparer<int>.Default.Equals(_value, other);
}
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age : IEquatable<int>
{
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age
{
public static bool operator !=(Age left, Age right)
{
return !(left == right);
}
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age
{
public static bool operator !=(Age left, int right)
{
return !(left == right);
}
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age
{
public override string ToString()
{
return string.Format("Age {{ {0} }}", _value);
}
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
namespace PrimitiveDemo
{
using System;
using System.Collections.Generic;
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable disable
#endif
partial struct Age
{
private readonly int _value;
}
#if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
#nullable restore
#endif
}
Useful
Download Example (.NET C#)
Share Monify
https://ignatandrei.github.io/RSCG_Examples/v2/docs/Monify
Category "PrimitiveObsession" has the following generators:
1 DomainPrimitives
2024-01-11
2 Monify
2025-12-12
3 Strongly
2023-08-19
4 StronglyTypedUid
2024-04-07
5 UnitGenerator
2023-10-15
6 Vogen
2023-04-16