Skip to content

Instantly share code, notes, and snippets.

@teoadal
Created October 10, 2025 16:18
Show Gist options
  • Select an option

  • Save teoadal/a4e2e1b7f2f2954f1d08402f42102a7c to your computer and use it in GitHub Desktop.

Select an option

Save teoadal/a4e2e1b7f2f2954f1d08402f42102a7c to your computer and use it in GitHub Desktop.
Переменные без боксинга
using System.Dynamic;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#pragma warning disable CS8618, CS9264
namespace ConsoleApp1;
[SimpleJob(RuntimeMoniker.Net90)]
[MeanColumn, MemoryDiagnoser]
public class GenericValueBench
{
private const bool BoolValue = true;
private static readonly DateTime DateTimeValue = DateTime.Now;
private const double DoubleValue = 3.3d;
private const decimal DecimalValue = 4.4M;
private const float FloatValue = 2.2f;
private const int IntValue = 1;
private const long LongValue = 5L;
private const string StringValue = "it's string";
private static readonly object EmptyObject = new();
private static readonly char[] Buffer = new char[1024];
private static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
[Benchmark(Baseline = true)]
public int Simple()
{
var values = GC.AllocateUninitializedArray<GenericValue>(9);
values[0] = new GenericValue(IntValue);
values[1] = new GenericValue(FloatValue);
values[2] = new GenericValue(DoubleValue);
values[3] = new GenericValue(DecimalValue);
values[4] = new GenericValue(LongValue);
values[5] = new GenericValue(StringValue);
values[6] = new GenericValue(DateTimeValue);
values[7] = new GenericValue(BoolValue);
values[8] = new GenericValue(string.Empty);
Span<char> buffer = Buffer;
var result = 0;
foreach (var value in values)
{
value.TryFormat(ref buffer, out var charsWritten, Culture);
result += charsWritten;
}
return result;
}
[Benchmark]
public int Better()
{
var values = GC.AllocateUninitializedArray<GenericValueBetter>(9);
values[0] = new GenericValueBetter(IntValue);
values[1] = new GenericValueBetter(FloatValue);
values[2] = new GenericValueBetter(DoubleValue);
values[3] = new GenericValueBetter(DecimalValue);
values[4] = new GenericValueBetter(LongValue);
values[5] = new GenericValueBetter(StringValue);
values[6] = new GenericValueBetter(DateTimeValue);
values[7] = new GenericValueBetter(BoolValue);
values[8] = new GenericValueBetter(string.Empty);
Span<char> buffer = Buffer;
var result = 0;
foreach (var value in values)
{
value.TryFormat(ref buffer, out var charsWritten, Culture);
result += charsWritten;
}
return result;
}
[Benchmark]
public int Optimal()
{
var values = GC.AllocateUninitializedArray<GenericValueOptimal>(9);
values[0] = new GenericValueOptimal(IntValue);
values[1] = new GenericValueOptimal(FloatValue);
values[2] = new GenericValueOptimal(DoubleValue);
values[3] = new GenericValueOptimal(DecimalValue);
values[4] = new GenericValueOptimal(LongValue);
values[5] = new GenericValueOptimal(StringValue);
values[6] = new GenericValueOptimal(DateTimeValue);
values[7] = new GenericValueOptimal(BoolValue);
values[8] = new GenericValueOptimal(string.Empty);
Span<char> buffer = Buffer;
var result = 0;
foreach (var value in values)
{
value.TryFormat(ref buffer, out var charsWritten, Culture);
result += charsWritten;
}
return result;
}
[Benchmark]
public int Best()
{
var values = GC.AllocateUninitializedArray<object>(9);
values[0] = IntValue;
values[1] = FloatValue;
values[2] = DoubleValue;
values[3] = DecimalValue;
values[4] = LongValue;
values[5] = StringValue;
values[6] = DateTimeValue;
values[7] = BoolValue;
values[8] = EmptyObject;
Span<char> buffer = Buffer;
var result = 0;
foreach (var value in values)
{
if (value is ISpanFormattable spanFormattable)
{
spanFormattable.TryFormat(
buffer,
out var charsWritten,
ReadOnlySpan<char>.Empty,
Culture);
result += charsWritten;
}
else
{
result += value.ToString()?.Length ?? 0;
}
}
return result;
}
[Benchmark]
public int Poor_Dynamic()
{
dynamic values = new ExpandoObject();
values.Int = IntValue;
values.Float = FloatValue;
values.Double = DoubleValue;
values.Decimal = DecimalValue;
values.Long = LongValue;
values.String = StringValue;
values.DateTime = DateTimeValue;
values.Bool = BoolValue;
values.Null = string.Empty;
Span<char> buffer = Buffer;
var result = 0;
foreach (var value in values)
{
if (value is ISpanFormattable spanFormattable)
{
spanFormattable.TryFormat(
buffer,
out var charsWritten,
ReadOnlySpan<char>.Empty,
Culture);
result += charsWritten;
}
else
{
result += value.ToString().Length;
}
}
return result;
}
}
public readonly struct GenericValue
{
private readonly float _floatValue;
private readonly double _doubleValue;
private readonly DateTime _dateTimeValue;
private readonly int _intValue;
private readonly decimal _decimalValue;
private readonly long _longValue;
private readonly string _stringValue;
private readonly GenericValueType _type;
private readonly bool _boolValue;
#region Constructors
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GenericValue() => Unsafe.SkipInit(out this);
public GenericValue(int? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_intValue = value.Value;
_type = GenericValueType.Int;
}
}
public GenericValue(float? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_floatValue = value.Value;
_type = GenericValueType.Float;
}
}
public GenericValue(double? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_doubleValue = value.Value;
_type = GenericValueType.Double;
}
}
public GenericValue(decimal? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_decimalValue = value.Value;
_type = GenericValueType.Decimal;
}
}
public GenericValue(string? value) : this()
{
if (string.IsNullOrEmpty(value))
{
_type = GenericValueType.Null;
}
else
{
_stringValue = value;
_type = GenericValueType.String;
}
}
public GenericValue(DateTime? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_dateTimeValue = value.Value;
_type = GenericValueType.DateTime;
}
}
public GenericValue(long? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_longValue = value.Value;
_type = GenericValueType.Long;
}
}
public GenericValue(bool? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_boolValue = value.Value;
_type = GenericValueType.Bool;
}
}
#endregion
public override string ToString() => ToString(CultureInfo.InvariantCulture);
public string ToString(CultureInfo cultureInfo)
{
if (_type == GenericValueType.String) return _stringValue;
Span<char> buffer = stackalloc char[256];
return TryFormat(ref buffer, out var written, cultureInfo)
? new string(buffer[..written])
: "EMPTY";
}
public bool TryFormat(ref Span<char> buffer, out int charsWritten, CultureInfo cultureInfo)
{
switch (_type)
{
case GenericValueType.Null:
const string nullValue = "EMPTY";
nullValue.CopyTo(buffer);
charsWritten = nullValue.Length;
return true;
case GenericValueType.Bool:
return _boolValue.TryFormat(buffer, out charsWritten);
case GenericValueType.DateTime:
return _dateTimeValue.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.Double:
return _doubleValue.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.Decimal:
return _decimalValue.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.Float:
return _floatValue.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.Int:
return _intValue.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.Long:
return _longValue.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.String:
_stringValue.CopyTo(buffer);
charsWritten = _stringValue.Length;
return true;
default:
const string emptyValue = "EMPTY";
emptyValue.CopyTo(buffer);
charsWritten = emptyValue.Length;
return true;
}
}
}
[StructLayout(LayoutKind.Auto)]
public readonly struct GenericValueBetter
{
private readonly double _doubleValue; // + float + DateTime
private readonly decimal _decimalValue; // + int + long + bool + DateOnly
private readonly string _stringValue;
private readonly GenericValueType _type;
#region Constructors
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GenericValueBetter() => Unsafe.SkipInit(out this);
public GenericValueBetter(int? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_decimalValue = value.Value;
_type = GenericValueType.Int;
}
}
public GenericValueBetter(float? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_doubleValue = value.Value;
_type = GenericValueType.Float;
}
}
public GenericValueBetter(double? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_doubleValue = value.Value;
_type = GenericValueType.Double;
}
}
public GenericValueBetter(decimal? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_decimalValue = value.Value;
_type = GenericValueType.Decimal;
}
}
public GenericValueBetter(string? value) : this()
{
if (string.IsNullOrEmpty(value))
{
_type = GenericValueType.Null;
}
else
{
_stringValue = value;
_type = GenericValueType.String;
}
}
public GenericValueBetter(DateTime? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_doubleValue = value.Value.ToOADate();
_type = GenericValueType.DateTime;
}
}
public GenericValueBetter(bool? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_decimalValue = value.Value ? 1 : 0;
_type = GenericValueType.Bool;
}
}
public GenericValueBetter(long? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_decimalValue = value.Value;
_type = GenericValueType.Long;
}
}
#endregion
public override string ToString() => ToString(CultureInfo.InvariantCulture);
public string ToString(CultureInfo cultureInfo)
{
if (_type == GenericValueType.String) return _stringValue;
Span<char> buffer = stackalloc char[256];
return TryFormat(ref buffer, out var written, cultureInfo)
? new string(buffer[..written])
: "EMPTY";
}
public bool TryFormat(ref Span<char> buffer, out int charsWritten, CultureInfo cultureInfo)
{
switch (_type)
{
case GenericValueType.Null:
const string nullValue = "EMPTY";
nullValue.CopyTo(buffer);
charsWritten = nullValue.Length;
return true;
case GenericValueType.Bool:
return (_decimalValue == 1).TryFormat(buffer, out charsWritten);
case GenericValueType.DateTime:
return DateTime.FromOADate(_doubleValue).TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.Double:
case GenericValueType.Float:
return _doubleValue.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.Decimal:
case GenericValueType.Int:
case GenericValueType.Long:
return _decimalValue.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.String:
_stringValue.CopyTo(buffer);
charsWritten = _stringValue.Length;
return true;
default:
const string emptyValue = "EMPTY";
emptyValue.CopyTo(buffer);
charsWritten = emptyValue.Length;
return true;
}
}
}
[StructLayout(LayoutKind.Auto)]
public readonly struct GenericValueOptimal
{
private readonly string _stringValue;
private readonly GenericValueNumber _numberValue;
private readonly GenericValueType _type;
#region Constructors
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GenericValueOptimal() => Unsafe.SkipInit(out this);
public GenericValueOptimal(int? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_numberValue = new GenericValueNumber(value.Value);
_type = GenericValueType.Int;
}
}
public GenericValueOptimal(float? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_numberValue = new GenericValueNumber(value.Value);
_type = GenericValueType.Float;
}
}
public GenericValueOptimal(double? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_numberValue = new GenericValueNumber(value.Value);
_type = GenericValueType.Double;
}
}
public GenericValueOptimal(decimal? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_numberValue = new GenericValueNumber(value.Value);
_type = GenericValueType.Decimal;
}
}
public GenericValueOptimal(string? value) : this()
{
if (string.IsNullOrEmpty(value))
{
_type = GenericValueType.Null;
}
else
{
_stringValue = value;
_type = GenericValueType.String;
}
}
public GenericValueOptimal(DateTime? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_numberValue = new GenericValueNumber(value.Value.ToOADate());
_type = GenericValueType.DateTime;
}
}
public GenericValueOptimal(bool? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_numberValue = new GenericValueNumber(value.Value ? 1 : 0);
_type = GenericValueType.Bool;
}
}
public GenericValueOptimal(long? value) : this()
{
if (value == null)
{
_type = GenericValueType.Null;
}
else
{
_numberValue = new GenericValueNumber(value.Value);
_type = GenericValueType.Long;
}
}
#endregion
public override string ToString() => ToString(CultureInfo.InvariantCulture);
public string ToString(CultureInfo cultureInfo)
{
if (_type == GenericValueType.String) return _stringValue;
Span<char> buffer = stackalloc char[256];
return TryFormat(ref buffer, out var written, cultureInfo)
? new string(buffer[..written])
: "EMPTY";
}
public bool TryFormat(ref Span<char> buffer, out int charsWritten, CultureInfo cultureInfo)
{
switch (_type)
{
case GenericValueType.Null:
const string nullValue = "EMPTY";
nullValue.CopyTo(buffer);
charsWritten = nullValue.Length;
return true;
case GenericValueType.Bool:
return (_numberValue.IntValue == 1).TryFormat(buffer, out charsWritten);
case GenericValueType.DateTime:
return DateTime
.FromOADate(_numberValue.DoubleValue)
.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.Double:
return _numberValue.DoubleValue.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.Float:
return _numberValue.FloatValue.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.Decimal:
return _numberValue.DecimalValue.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.Int:
return _numberValue.IntValue.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.Long:
return _numberValue.LongValue.TryFormat(buffer, out charsWritten, provider: cultureInfo);
case GenericValueType.String:
_stringValue.CopyTo(buffer);
charsWritten = _stringValue.Length;
return true;
default:
const string emptyValue = "EMPTY";
emptyValue.CopyTo(buffer);
charsWritten = emptyValue.Length;
return true;
}
}
}
public enum GenericValueType : byte
{
Null = 0,
Bool,
DateTime,
Decimal,
Double,
Float,
Int,
Long,
String
}
[StructLayout(LayoutKind.Explicit)]
public readonly struct GenericValueNumber
{
[FieldOffset(0)] public readonly int IntValue;
[FieldOffset(0)] public readonly float FloatValue;
[FieldOffset(0)] public readonly double DoubleValue;
[FieldOffset(0)] public readonly decimal DecimalValue;
[FieldOffset(0)] public readonly long LongValue;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GenericValueNumber() => Unsafe.SkipInit(out this);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GenericValueNumber(int value) : this() => IntValue = value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GenericValueNumber(float value) : this() => FloatValue = value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GenericValueNumber(double value) : this() => DoubleValue = value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GenericValueNumber(decimal value) : this() => DecimalValue = value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GenericValueNumber(long value) : this() => LongValue = value;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment