AutoParsable
Generates IParsable<T>, ISpanParsable<T>, and optionally IUtf8SpanParsable<T> from a user-defined core TryParse method.
Installation
dotnet add package VoloGen.Parsable
dotnet add package VoloGen.Parsable.Generator
Requirements
The annotated type must:
- Be declared as
partial - Not be
staticorabstract - Define one core method:
static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out T result)– your parsing logic
Attribute Properties
| Property | Type | Default | Description |
|---|---|---|---|
ImplementUtf8 |
bool |
false |
Generate IUtf8SpanParsable<T> overloads |
ImplementExact |
bool |
false |
Generate ParseExact / TryParseExact overloads |
ThrowOnNull |
bool |
true |
Emit a null check in Parse(string s, ...) and ParseExact(string s, ...) overloads |
ThrowOnNull
When ThrowOnNull is true (the default), the generated Parse(string s, ...) and ParseExact(string s, ...) overloads throw ArgumentNullException if s is null:
// ThrowOnNull = true (default)
var result = Amount.Parse((string)null!, null); // throws ArgumentNullException
Set ThrowOnNull = false to skip the null guard — useful when the type is only ever parsed from non-null sources or when you handle null in the core TryParse method:
[AutoParsable(ThrowOnNull = false)]
public partial struct Amount { ... }
// null is forwarded to TryParse via AsSpan() — returns default on failure
Amount.Parse((string)null!, null); // no ArgumentNullException
What Gets Generated
Base Overloads (always generated)
Nullable args:
Parse(string s, ...)overloads accept a non-nullablestringand throwArgumentNullExceptionwhensisnull(controlled byThrowOnNull).TryParse(string? s, ...)overloads acceptnulland convert it to an empty span viaAsSpan().
| Generated Member | Delegates To |
|---|---|
static T Parse(string s, IFormatProvider? provider) |
TryParse → throw on failure; throws ArgumentNullException if s is null (when ThrowOnNull is true) |
static T Parse(ReadOnlySpan<char> s, IFormatProvider? provider) |
TryParse → throw on failure |
static T Parse(string s) |
TryParse(s, null, ...) |
static T Parse(ReadOnlySpan<char> s) |
TryParse(s, null, ...) |
static bool TryParse(string? s, IFormatProvider? provider, out T result) |
Core TryParse via AsSpan() — null becomes empty span |
static bool TryParse(string? s, out T result) |
TryParse(s, null, ...) |
static bool TryParse(ReadOnlySpan<char> s, out T result) |
TryParse(s, null, ...) |
UTF-8 Overloads (ImplementUtf8 = true)
| Generated Member | Delegates To |
|---|---|
static T Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider) |
Decodes UTF-8 -> TryParse |
static bool TryParse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider, out T result) |
Decodes UTF-8 -> TryParse |
Exact Overloads (ImplementExact = true)
When ImplementExact is enabled, you must also provide:
public static bool TryParseExact(ReadOnlySpan<char> s, ReadOnlySpan<char> format,
IFormatProvider? provider, out T result)
| Generated Member | Delegates To |
|---|---|
static T ParseExact(string s, string format, IFormatProvider? provider) |
TryParseExact → throw on failure; throws ArgumentNullException if s is null (when ThrowOnNull is true) |
static T ParseExact(ReadOnlySpan<char> s, ReadOnlySpan<char> format, IFormatProvider? provider) |
TryParseExact → throw on failure |
static bool TryParseExact(string? s, string? format, IFormatProvider? provider, out T result) |
Core TryParseExact — null args accepted |
| Providerless and spanless convenience overloads | Delegate with null defaults |
Example
Basic
using VoloGen;
[AutoParsable]
public partial struct Temperature
{
private readonly double _celsius;
public Temperature(double celsius)
{
_celsius = celsius;
}
public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out Temperature result)
{
if (double.TryParse(s, provider, out double value))
{
result = new Temperature(value);
return true;
}
result = default;
return false;
}
}
With UTF-8 Support
[AutoParsable(ImplementUtf8 = true)]
public partial struct Amount
{
private readonly decimal _value;
public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out Amount result)
{
if (decimal.TryParse(s, provider, out var value))
{
result = new Amount { _value = value };
return true;
}
result = default;
return false;
}
public static bool TryParse(ReadOnlySpan<byte> s, IFormatProvider? provider, out Amount result)
{
if (decimal.TryParse(s, provider, out var value))
{
result = new Amount { _value = value };
return true;
}
result = default;
return false;
}
}
With Exact Parsing
[AutoParsable(ImplementExact = true)]
public partial struct Date
{
private readonly DateOnly _value;
public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out Date result)
{
if (DateOnly.TryParse(s, provider, out var value))
{
result = new Date { _value = value };
return true;
}
result = default;
return false;
}
public static bool TryParseExact(ReadOnlySpan<char> s, ReadOnlySpan<char> format,
IFormatProvider? provider, out Date result)
{
if (DateOnly.TryParseExact(s, format, provider, System.Globalization.DateTimeStyles.None, out var value))
{
result = new Date { _value = value };
return true;
}
result = default;
return false;
}
}
Usage
// Parse from string
var temp = Temperature.Parse("36.6");
// TryParse with error handling
if (Temperature.TryParse("not-a-number", out var bad))
{
Console.WriteLine(bad);
}
else
{
Console.WriteLine("Failed to parse"); // this branch
}
// Parse with provider
var amount = Amount.Parse("1,234.56", CultureInfo.InvariantCulture);
// UTF-8 parsing (when ImplementUtf8 = true)
var utf8 = "42.5"u8;
var parsed = Amount.Parse(utf8, null);
Diagnostics
| ID | Trigger |
|---|---|
| VG0001 | Missing core TryParse method |
| VG0002 | Type is not partial |
| VG0003 | Type is static |
| VG0004 | Type is abstract |