VariantEnum
is C# Source Generator that automatically creates a Rust Enum-like record class
from an Enum
, where each variant can have a value.
The automatically created record
class makes use of .NET 8 and C# 12 language features (Incremental Generator
, ISpanParsable<T>
, static abstract interfaces and Collection expressions).
This library is distributed via NuGet.
PM> Install-Package VariantEnum
If the Enum
definition name ends with 'Variant', a record class is automatically created with the same name as the enum definition name without 'Variant'.
Enum members can be assigned the [TVariantValueType]
attribute so that they have their own specific data.. Values can be accessed as argsXXX.
For example, if an IpAddrVariant
Enum is defined:
public enum IpAddrVariant : byte
{
[VariantValueType(typeof(byte), typeof(byte), typeof(byte), typeof(byte))]
V4,
[VariantValueType(typeof(string))]
V6,
None
}
This creates IpAddr
record class.
var ip = new IpAddr.V4(127, 0, 0, 1);
var value = (IpAddr)ip switch
{
IpAddr.V4 v4 => $"{v4.args0}.{v4.args1}.{v4.args2}.{v4.args3}",
IpAddr.V6 v6 => v6.args0,
_ => throw new Exception(),
};
Console.WriteLine(value); // 127.0.0.1
IpAddr
record class automatically generates the following APIs.
public abstract record IpAddr : ISpanFormattable, ISpanParsable<IpAddr>
{
public sealed record V4(byte args0, byte args1, byte args2, byte args3) : IpAddr
{
public static V4 Default => new V4(args0: default, args1: default, args2: default, args3: default);
public override bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null);
}
public sealed record V6(string args0) : IpAddr
{
public static V6 Default => new V6(args0: default);
public override bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null);
}
public sealed record None : IpAddr
{
public static None Default => new None();
public override bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null);
}
// ISpanFormattable
public abstract bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null);
public string ToString(string? format, IFormatProvider? formatProvider) => ToString();
// ISpanParsable<T>
public static IpAddr Parse(ReadOnlySpan<char> s, IFormatProvider? provider = default);
public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [MaybeNullWhen(false)] out IpAddr result);
// IParsable<T>
public static IpAddr Parse(string s, IFormatProvider? provider = default);
public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out IpAddr result);
public static int Count { get; } => 3;
public static string? GetName(IpAddr t);
public static string[] GetNames() => [nameof(V4), nameof(V6), nameof(None)];
public static byte GetNumericValue(IpAddr t);
public static IpAddrVariant ConvertEnum(IpAddr t);
public static bool TryConvertEnum([NotNullWhen(true)] IpAddr? t, [MaybeNullWhen(false)] out IpAddrVariant result);
public static IpAddr Parse(ReadOnlySpan<char> s, bool ignoreCase, IFormatProvider? provider = default);
public static bool TryParse(ReadOnlySpan<char> s, out IpAddr result);
public static bool TryParse(ReadOnlySpan<char> s, bool ignoreCase, IFormatProvider? provider, [MaybeNullWhen(false)] out IpAddr result);
public static bool IsDefined(ReadOnlySpan<char> s);
public static bool IsDefined([NotNullWhen(true)] IpAddr? value);
}
Generated All Code(IpAddr.g.cs)
// <auto-generated> This .cs file is generated by VariantEnum. </auto-generated>
#nullable enable
#pragma warning disable CS0219 // The variable 'variable' is assigned but its value is never used
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning disable CS8601 // Possible null reference assignment.
#pragma warning disable CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8603 // Possible null reference return.
#pragma warning disable CS8604 // Possible null reference argument for parameter.
#pragma warning disable CS8619 // Possible null reference assignment fix
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace ConsoleApp;
public abstract record IpAddr :
ISpanFormattable,
ISpanParsable<IpAddr>
{
public sealed record V4(byte args0, byte args1, byte args2, byte args3) : IpAddr
{
public static V4 Default => new V4(args0: default, args1: default, args2: default, args3: default);
public override bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null)
{
var index = 0;
charsWritten = 0;
if (destination.Length < 5)
{
charsWritten += index;
return false;
}
destination[index++] = 'V';
destination[index++] = '4';
destination[index++] = ' ';
destination[index++] = '{';
destination[index++] = ' ';
var handler = new DefaultInterpolatedStringHandler();
handler.AppendLiteral("args0 = ");
handler.AppendFormatted(args0);
handler.AppendFormatted(", " );
handler.AppendLiteral("args1 = ");
handler.AppendFormatted(args1);
handler.AppendFormatted(", " );
handler.AppendLiteral("args2 = ");
handler.AppendFormatted(args2);
handler.AppendFormatted(", " );
handler.AppendLiteral("args3 = ");
handler.AppendFormatted(args3);
var print = handler.ToStringAndClear();
var printSpan = print.AsSpan();
if (destination.Length < printSpan.Length + index)
{
charsWritten += index;
return false;
}
printSpan.CopyTo(destination.Slice(index, printSpan.Length));
index += printSpan.Length;
if (destination.Length < 2 + index)
{
charsWritten += index;
return false;
}
destination[index++] = ' ';
destination[index++] = '}';
charsWritten = index;
return true;
}
}
public sealed record V6(string args0) : IpAddr
{
public static V6 Default => new V6(args0: default);
public override bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null)
{
var index = 0;
charsWritten = 0;
if (destination.Length < 5)
{
charsWritten += index;
return false;
}
destination[index++] = 'V';
destination[index++] = '6';
destination[index++] = ' ';
destination[index++] = '{';
destination[index++] = ' ';
var handler = new DefaultInterpolatedStringHandler();
handler.AppendLiteral("args0 = ");
handler.AppendFormatted(args0);
var print = handler.ToStringAndClear();
var printSpan = print.AsSpan();
if (destination.Length < printSpan.Length + index)
{
charsWritten += index;
return false;
}
printSpan.CopyTo(destination.Slice(index, printSpan.Length));
index += printSpan.Length;
if (destination.Length < 2 + index)
{
charsWritten += index;
return false;
}
destination[index++] = ' ';
destination[index++] = '}';
charsWritten = index;
return true;
}
}
public sealed record None : IpAddr
{
public static None Default => new None();
public override bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null)
{
var index = 0;
charsWritten = 0;
if (destination.Length < 8)
{
charsWritten += index;
return false;
}
destination[index++] = 'N';
destination[index++] = 'o';
destination[index++] = 'n';
destination[index++] = 'e';
destination[index++] = ' ';
destination[index++] = '{';
destination[index++] = ' ';
destination[index++] = '}';
charsWritten = index;
return true;
}
}
public abstract bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = null);
public string ToString(string? format, IFormatProvider? formatProvider) => ToString();
public static int Count => 3;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string? GetName(IpAddr ipaddr)
{
return ipaddr switch
{
V4 => nameof(V4),
V6 => nameof(V6),
None => nameof(None),
_ => null
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string[] GetNames() => [nameof(V4), nameof(V6), nameof(None)];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte GetNumericValue(IpAddr ipaddr)
{
return ipaddr switch
{
V4 => (byte)IpAddrVariant.V4,
V6 => (byte)IpAddrVariant.V6,
None => (byte)IpAddrVariant.None,
_ => ThrowInvalidType<byte>()
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IpAddrVariant ConvertEnum(IpAddr ipaddr)
{
return ipaddr switch
{
V4 => IpAddrVariant.V4,
V6 => IpAddrVariant.V6,
None => IpAddrVariant.None,
_ => ThrowInvalidType<IpAddrVariant>()
};
}
public static bool TryConvertEnum([NotNullWhen(true)] IpAddr? ipaddr, [MaybeNullWhen(false)] out IpAddrVariant result)
{
switch(ipaddr)
{
case V4:
result = IpAddrVariant.V4;
return true;
case V6:
result = IpAddrVariant.V6;
return true;
case None:
result = IpAddrVariant.None;
return true;
}
result = default;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IpAddr Parse(string s, IFormatProvider? provider = default)
{
return Parse(s.AsSpan(), false, provider);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IpAddr Parse(ReadOnlySpan<char> s, IFormatProvider? provider = default)
{
return Parse(s, false, provider);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IpAddr Parse(ReadOnlySpan<char> s, bool ignoreCase, IFormatProvider? provider = default)
{
if (TryParse(s, ignoreCase, provider, out var result))
{
return result;
}
else
{
ThrowRequestedValueNotFound(s);
return default!;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out IpAddr result)
{
return TryParse(s.AsSpan(), false, provider, out result);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParse(ReadOnlySpan<char> s, out IpAddr result)
{
return TryParse(s, false, null, out result);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [MaybeNullWhen(false)] out IpAddr result)
{
return TryParse(s, false, null, out result);
}
public static bool TryParse(ReadOnlySpan<char> s, bool ignoreCase, IFormatProvider? provider, [MaybeNullWhen(false)] out IpAddr result)
{
if (ignoreCase)
{
if (s.Equals(nameof(V4), StringComparison.OrdinalIgnoreCase))
{
result = V4.Default;
return true;
}
if (s.Equals(nameof(V6), StringComparison.OrdinalIgnoreCase))
{
result = V6.Default;
return true;
}
if (s.Equals(nameof(None), StringComparison.OrdinalIgnoreCase))
{
result = None.Default;
return true;
}
result = default;
return false;
}
else
{
switch (s)
{
case "V4":
result = V4.Default;
return true;
case "V6":
result = V6.Default;
return true;
case "None":
result = None.Default;
return true;
}
result = default;
return false;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsDefined(ReadOnlySpan<char> s)
{
return s switch
{
"V4" => true,
"V6" => true,
"None" => true,
_ => false
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsDefined([NotNullWhen(true)] IpAddr? value)
{
return value switch
{
V4 => true,
V6 => true,
None => true,
_ => false
};
}
[DoesNotReturn]
private static void ThrowRequestedValueNotFound(ReadOnlySpan<char> s)
{
throw new ArgumentException($"Requested value '{s}' was not found.");
}
[DoesNotReturn]
private static T ThrowInvalidType<T>()
{
throw new ArgumentException($"Requested value was invalid type.'");
}
}
If automatic generation is to be ignored, you assign the [IgnoreVariant]
attribute.
[IgnoreVariant]
public enum TestVariant
{}
var v4 = IpAddr.Parse("V4");
var v4IgnoreCase = IpAddr.Parse("v4", true);
if (IpAddr.TryParse("V4", out var v4))
{
Console.WriteLine(v4); // V4 { args0 = 0, args1 = 0, args2 = 0, args3 = 0 }
}
if (IpAddr.TryParse("v4", true, null, out var v4IgnoreCase))
{
Console.WriteLine(v4IgnoreCase); // V4 { args0 = 0, args1 = 0, args2 = 0, args3 = 0 }
}
var count = IpAddr.Count;
Console.WriteLine(count); // 3
var name = IpAddr.GetName(new IpAddr.V4(127, 0, 0, 1));
Console.WriteLine(name); // V4
var names = IpAddr.GetNames();
foreach (var name in names)
{
Console.WriteLine(name);
}
// V4
// V6
// None
var result = IpAddr.IsDefined("V4"); // true
var result2 = IpAddr.IsDefined(new IpAddr.V4(127, 0, 0, 1)); // true
var v4Number = IpAddr.GetNumericValue(new IpAddr.V4(127, 0, 0, 1)); // 0
var v6Number = IpAddr.GetNumericValue(new IpAddr.V6("::1")); // 1
Get the enumeration value of the original enum from the specified member.
IpAddrVariant variantEnum = IpAddr.ConvertEnum(new IpAddr.V4(127, 0, 0, 1));
if (IpAddr.TryConvertEnum(new IpAddr.V4(127, 0, 0, 1), out IpAddrVariant result))
{
}
MIT License.