Yoyoyopo5.ValueWrapper 1.1.2

dotnet add package Yoyoyopo5.ValueWrapper --version 1.1.2
                    
NuGet\Install-Package Yoyoyopo5.ValueWrapper -Version 1.1.2
                    
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="Yoyoyopo5.ValueWrapper" Version="1.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Yoyoyopo5.ValueWrapper" Version="1.1.2" />
                    
Directory.Packages.props
<PackageReference Include="Yoyoyopo5.ValueWrapper" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Yoyoyopo5.ValueWrapper --version 1.1.2
                    
#r "nuget: Yoyoyopo5.ValueWrapper, 1.1.2"
                    
#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.
#:package Yoyoyopo5.ValueWrapper@1.1.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Yoyoyopo5.ValueWrapper&version=1.1.2
                    
Install as a Cake Addin
#tool nuget:?package=Yoyoyopo5.ValueWrapper&version=1.1.2
                    
Install as a Cake Tool

Yoyoyopo5.ValueWrapper

NuGet Version Build Status License Target

Eliminate primitive obsession in your codebase without unnecessary heap allocations or boilerplate. ValueWrapper makes it easy.

Usage

Create a Wrapper Type

[Wrapper<string>]
public readonly partial record struct ColorName; 

Generally, wrappers should be record struct types, but a wrapper can be any partial type and can wrap any type:

public record Person(string Name, uint Age);

[Wrapper<Person>]
public partial class InactivePerson;

Use Cases

Reduce Bugs with Constrained Signatures

Turn runtime exceptions into compile-time errors:

Before:

void UpdateColorBuggy(string colorName) { }
string username = "xX_username_Xx";

...

UpdateColorBuggy(username); // Compiles with bug!

After:

// I can only take a ColorName type.
void UpdateColor(ColorName color) { }
Username username = new("xX_username_Xx");

...

UpdateColor(username); // Compiler error! 

Create Enumerations

Have a set of possible values? Don't use an enum, use a wrapper with static definitions:

[Wrapper<string>]
public readonly partial record struct ReportStatus
{
    public static ReportStatus Accepted { get; } = new("accepted");
    public static ReportStatus Rejected { get; } = new("rejected");
    public static ReportStatus Unknown { get; } = new("unknown");

    public string Value { get; }
    private ReportStatus(string value) => Value = value;

    internal static ReportStatus Create(string value) => new(value);
}

You can also create unbounded value sets:

[Wrapper<string>]
public readonly partial record struct ColorName(string Value)
{
    public static ColorName SkyBlue { get; } = new("sky_blue");
    public static ColorName EggplantPurple { get; } = new("eggplant_purple");
}

ColorName _customColor = new("very_red");

Add Validation

Wrapper types can be used to put validation at the type level, instead of littered around your codebase:

[Wrapper<int>]
public readonly partial record struct EvenNumber
{
    public int Value { get; private init; }

    public static EvenNumber? ParseOrNull(int value)
        => value switch 
        {
            _ when value % 2 != 0 => null,
            _ => new() { Value = value }
        };

    internal static EvenNumber Create(int value)
        => ParseOrNull(value) is EvenNumber even ? even : throw new ArgumentException("value must be even.");
} 

Create Bounded Extensions

Don't extend primitives; extend wrappers instead:

public static BadExtensions
{
    public static decimal ConvertToMiles(this decimal kilometers) { }
}

[Wrapper<decimal>]
public partial record Kilometers;

[Wrapper<decimal>]
public partial record Miles;

public static KilometersExtensions
{
    public static Miles ConvertToMiles(this Kilometers kilometers) { }
}

Wrapper Features

Value Property

A required Value property is created by default:

ColorName color = new() { Value = "red" };
string name = color.Value;
Explicit In, Implicit Out

Wrapper types can be freely used as their wrapped type (but not the other way around).

ColorName color = new() { Value = "orange" };
string.IsNullOrEmpty(color);

ColorName badColor = "orange" // Does not compile!
ToString() Passthrough
ColorName color = new() { Value = "red" };
Console.WriteLine(color); // "red"
System.Text.Json Passthrough
ColorName color = new() { Value = "green" };
string json = JsonSerializer.Serialize(color);
Console.WriteLine(json); // "green"

ColorName deserializedColor = JsonSerializer.Deserialize<ColorName>(json);
Console.WriteLine(deserializedColor); // "green"
TypeConverter Support

A TypeConverter is added to the wrapper that can create the wrapper to and from its wrapped type. Conversion from string is also supported via TypeConverter passthrough.

This allows wrappers to be used as their wrapped type in many scenarios, like IConfigurationBinder binding or ASP.NET Core query parameter binding.

Nested Wrappers

Wrappers inside other classes are supported, as long as each containing class is marked partial:

public partial class Container
{
    [Wrapper<int>]
    private readonly partial record struct Cents;
}

Wrapper Customization

Record Primary Constructor

A record primary constructor can define the Value property, meaning the required property will not be automatically generated:

[Wrapper<Guid>]
public partial record UserId(Guid Value);
Disable Required Value Property

You can also disable the required property manually:

[Wrapper<double>]
public readonly partial record struct FeetPerSecond
{
    public double Value { get; init; }
}
Override Wrapper Features

You can override any generated wrapper behavior:

[Wrapper<string>]
[JsonConverter(typeof(IdeaConverter))] // Disables the default WrapperJsonConverter
public partial class Idea(string Value)
{
    public string? Value { get; set; } // Overrides the default Value property
    public override string ToString() => "Nobody can know!" // Overrides the ToString passthrough
    public static implicit operator string(Idea idea) => "Hey, that's my idea!" // Overrides the implicit out operator
}
Override JSON Creation Behavior

Think you need a custom JsonConverter? Maybe not! The converter will use a constructor with a single wrapped type parameter:

[Wrapper<int>]
public partial record NegativeNumber 
{
    public int Value { get; }

    public NegativeNumber(int value)
    {
        if (value > 0)
            throw new ArgumentException("Number must be negative!");
        Value = value;
    }
}

Don't like constructors? No problem. You can configure the static Create(value) method, and the default WrapperJsonConverter will use it:

[Wrapper<int>]
public readonly partial record struct NegativeNumber
{
    public int Value { get; private init; }

    public static NegativeNumber Create(int value)
        => value switch 
        {
            _ when value > 0 => throw new ArgumentException("Number must be negative!"),
            _ => new() { Value = value }
        };
}

Benchmarks (v1.1.2)

ToString Passthrough

Wrapped struct
Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
WrapperToString 47.31 ns 0.730 ns 0.683 ns 1.04 0.02 0.0172 288 B 1.00
PrimitiveToString 45.67 ns 0.694 ns 0.615 ns 1.00 0.02 0.0172 288 B 1.00
Wrapped double
Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
WrapperToString 39.27 ns 0.647 ns 0.605 ns 1.00 0.02 0.0014 24 B 1.00
PrimitiveToString 39.19 ns 0.509 ns 0.425 ns 1.00 0.01 0.0014 24 B 1.00
Wrapped string
Method Mean Error StdDev Ratio RatioSD Allocated Alloc Ratio
WrapperToString 0.1560 ns 0.0190 ns 0.0177 ns 0.32 0.04 - NA
PrimitiveToString 0.4907 ns 0.0186 ns 0.0165 ns 1.00 0.05 - NA

System.Text.Json Round-Trip

Non-built-in wrapped types incur a penalty.

Wrapped struct
Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
WrapperRoundTripJson 410.1 ns 5.10 ns 4.52 ns 1.82 0.06 0.0057 96 B 1.00
PrimitiveRoundTripJson 225.4 ns 4.00 ns 7.01 ns 1.00 0.04 0.0057 96 B 1.00
Wrapped double
Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
WrapperRoundTripJson 160.5 ns 2.49 ns 2.33 ns 1.04 0.02 0.0019 32 B 1.00
PrimitiveRoundTripJson 154.3 ns 1.26 ns 1.18 ns 1.00 0.01 0.0019 32 B 1.00
Wrapped string
Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
WrapperRoundTripJson 136.1 ns 2.08 ns 1.95 ns 1.00 0.02 0.0043 72 B 1.00
PrimitiveRoundTripJson 136.3 ns 1.49 ns 1.32 ns 1.00 0.01 0.0043 72 B 1.00

System.Text.Json Round-Trip Dictionary with Wrapper Keys

Method Mean Error StdDev Ratio Gen0 Allocated Alloc Ratio
StringKeyDictionaryRoundTripJson 271.0 ns 2.17 ns 1.92 ns 1.00 0.0200 336 B 1.00
WrapperKeyDictionaryRoundTripJson 277.7 ns 3.20 ns 2.99 ns 1.02 0.0219 368 B 1.10

TypeConverter Round-Trip (to/from string)

Wrapped struct
Method Mean Error StdDev Ratio Gen0 Allocated Alloc Ratio
WrapperRoundTripString 248.5 ns 3.40 ns 3.01 ns 1.05 0.0086 144 B 1.20
PrimitiveRoundTripString 235.9 ns 1.84 ns 1.54 ns 1.00 0.0072 120 B 1.00
Wrapped double
Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
WrapperRoundTripString 76.56 ns 1.262 ns 1.054 ns 1.03 0.02 0.0057 96 B 1.33
PrimitiveRoundTripString 74.03 ns 0.826 ns 0.773 ns 1.00 0.01 0.0043 72 B 1.00
Wrapped string
Method Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
WrapperRoundTripString 14.56 ns 0.169 ns 0.149 ns 1.27 0.02 0.0029 48 B 2.00
PrimitiveRoundTripString 11.43 ns 0.175 ns 0.155 ns 1.00 0.02 0.0014 24 B 1.00

Source Generator

Method Mean Error StdDev Gen0 Gen1 Allocated
RunGenerator 83.77 us 1.661 us 1.912 us 4.8828 0.4883 83.97 KB
RunGeneratorCached 30.59 us 0.218 us 0.204 us 1.3428 0.0305 21.99 KB
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  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 is compatible.  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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Yoyoyopo5.ValueWrapper:

Package Downloads
PolyhydraGames.TwitchySharp.Core

Fork build of TwitchySharp (TwitchySharp.Core) by Yoyoyopo5, published by Polyhydra Games until upstream ships its own packages. Upstream: https://github.com/Yoyoyopo5/TwitchySharp

PolyhydraGames.TwitchySharp.EventSub

Fork build of TwitchySharp (TwitchySharp.EventSub) by Yoyoyopo5, published by Polyhydra Games until upstream ships its own packages. Upstream: https://github.com/Yoyoyopo5/TwitchySharp

PolyhydraGames.TwitchySharp.Api

Fork build of TwitchySharp (TwitchySharp.Api) by Yoyoyopo5, published by Polyhydra Games until upstream ships its own packages. Upstream: https://github.com/Yoyoyopo5/TwitchySharp

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.2 228 8/22/2026
1.1.1 217 5/5/2026
1.1.0 145 4/25/2026
1.0.1 119 4/22/2026
1.0.0 116 4/21/2026