Добавьте файлы проекта.

This commit is contained in:
melekhin
2026-05-28 10:45:35 +07:00
parent 9aba1f1c00
commit d6bb2ff84b
5 changed files with 662 additions and 0 deletions

View File

@@ -0,0 +1,226 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace QWERTYkez.Mensura.Generator;
[Generator(LanguageNames.CSharp)]
internal class CollectionsOperatorsGenerator : IIncrementalGenerator
{
private const string AttributeShortName = "CollectionsOperatorsGenerator";
private const string AttributeFullName = AttributeShortName + "Attribute";
private const string AttributeSource = @"namespace MetricSystem.Generator;
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
internal sealed class CollectionsOperatorsGeneratorAttribute : System.Attribute { }";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(context =>
context.AddSource(
$"{AttributeFullName}.g",
SourceText.From(AttributeSource, Encoding.UTF8)));
var operatorsPipeline =
context.SyntaxProvider.CreateSyntaxProvider<KeyValuePair<ClassData, ImmutableArray<Operation>>>(
(node, _) =>
{
if (node is ClassDeclarationSyntax cds)
{
SyntaxNode sn = cds;
while (sn.Parent is not null &&
sn.Parent is not FileScopedNamespaceDeclarationSyntax)
{
sn = sn.Parent;
}
if (sn.Parent is FileScopedNamespaceDeclarationSyntax
&& cds.TypeParameterList is null
&&
((cds.Members.OfType<OperatorDeclarationSyntax>()
.Where(m => m.AttributeLists.SelectMany(al => al.Attributes)
.Any(a => a.Name.GetText().ToString().Contains(AttributeShortName)))
.Any())
||
(cds.Modifiers.Any(m => m.Text == "partial")
&& cds.AttributeLists
.SelectMany(al => al.Attributes)
.Any(a => a.Name
.GetText()
.ToString()
.Contains(AttributeShortName)))))
{
return true;
}
}
return false;
},
(syntax, _) =>
{
string nameSpace;
var cds = (ClassDeclarationSyntax)syntax.Node;
{
SyntaxNode sn = cds;
while (sn.Parent is not FileScopedNamespaceDeclarationSyntax)
{
sn = sn.Parent!;
}
var nds = (FileScopedNamespaceDeclarationSyntax)sn.Parent;
nameSpace = nds.Name.ToString();
}
var Res = new StringBuilder();
{
Res.Append(cds.Modifiers);
Res.Append(" class ");
Res.Append(cds.Identifier.Text);
Res.Append(" ");
Res.Append(cds.BaseList);
Res.Append(" ");
Res.Append(cds.ConstraintClauses);
}
var operators = cds.Members.OfType<OperatorDeclarationSyntax>()
.Where(m => m.AttributeLists
.SelectMany(al => al.Attributes)
.Any(a => a.Name.GetText().ToString().Contains(AttributeShortName)))
.Where(mb => mb.ParameterList.Parameters.Count == 2)
.Select(mb => new Operation()
{
ReturnType = mb.ReturnType.ToString(),
OperatorToken = mb.OperatorToken.Text,
TypeA = mb.ParameterList.Parameters[0].Type!.ToString(),
TypeB = mb.ParameterList.Parameters[1].Type!.ToString(),
});
return new(new(nameSpace, cds.Identifier.Text, Res.ToString()), [.. operators]);
})
.Collect();
context.RegisterSourceOutput(operatorsPipeline, GenerateOperators);
}
readonly static string[] CollectionTypes = ["MetricArray", "MetricList", "MetricObservableCollection"];
static void GenerateOperators(SourceProductionContext context, ImmutableArray<KeyValuePair<ClassData, ImmutableArray<Operation>>> pairs)
{
foreach (var ng in pairs.GroupBy(c => c.Key.NameSpace))
{
StringBuilder document = new("namespace ");
document.Append(ng.Key);
document.Append(";");
var classes = ng.ToList().Select(c => (ClassData?)c.Key).ToList();
var operations = ng.ToList().SelectMany(c => c.Value).ToList();
var multiplications = operations.Where(op => op.OperatorToken == "*").ToList();
var divisions = operations.Where(op => op.OperatorToken == "/").ToList();
foreach (var ops in multiplications.GroupBy(op => op.TypeA))
{
var Class = classes.FirstOrDefault(cl => cl!.Value.ClassName == ops.Key);
if (Class is not null)
{
document.AppendLine();
document.AppendLine();
document.AppendLine(Class.Value.ClassHeader);
document.AppendLine("{");
foreach (var op in ops)
foreach (var ct in CollectionTypes)
{
document.AppendLine(@$"
public static {ct}<{op.ReturnType}> operator *({op.TypeA} left, {ct}<{op.TypeB}> right) => right.MetricSelect(UU => left * UU);
public static {ct}<{op.ReturnType}> operator *({ct}<{op.TypeB}> left, {op.TypeA} right) => left.MetricSelect(UU => UU * right);
");
}
document.Append("}");
}
else
{
context.ReportDiagnostic(Diagnostic.Create(new(
"MSG0001",
"Need a class with an attribute",
$"It is necessary to have a empty partial class \"{ops.Key}\" with the attribute \"{AttributeShortName}\"",
"category",
DiagnosticSeverity.Error,
true), null, ops.Key));
}
}
foreach (var ops in divisions.GroupBy(op => op.TypeA))
{
var Class = classes.FirstOrDefault(cl => cl!.Value.ClassName == ops.Key);
if (Class is not null)
{
document.AppendLine();
document.AppendLine();
document.AppendLine(Class.Value.ClassHeader);
document.AppendLine("{");
foreach (var op in ops)
foreach (var ct in CollectionTypes)
{
document.AppendLine(@$"
public static {ct}<{op.ReturnType}> operator /({op.TypeA} left, {ct}<{op.TypeB}> right) => right.MetricSelect(UU => left / UU);
");
}
document.Append("}");
}
else
{
context.ReportDiagnostic(Diagnostic.Create(new(
"MSG0001",
"Need a class with an attribute",
$"It is necessary to have a empty partial class \"{ops.Key}\" with the attribute \"{AttributeShortName}\"",
"category",
DiagnosticSeverity.Error,
true), null, ops.Key));
}
}
foreach (var ops in divisions.GroupBy(op => op.TypeB))
{
var Class = classes.FirstOrDefault(cl => cl!.Value.ClassName == ops.Key);
if (Class is not null)
{
document.AppendLine();
document.AppendLine();
document.AppendLine(Class.Value.ClassHeader);
document.AppendLine("{");
foreach (var op in ops)
foreach (var ct in CollectionTypes)
{
document.AppendLine(@$"
public static {ct}<{op.ReturnType}> operator /({ct}<{op.TypeA}> left, {op.TypeB} right) => left.MetricSelect(UU => UU / right);
");
}
document.Append("}");
}
else
{
context.ReportDiagnostic(Diagnostic.Create(new(
"MSG0001",
"Need a class with an attribute",
$"It is necessary to have a empty partial class \"{ops.Key}\" with the attribute \"{AttributeShortName}\"",
"category",
DiagnosticSeverity.Error,
true), null, ops.Key));
}
}
context.AddSource($"operations.{ng.Key}.g", document.ToString());
}
}
}
public struct ClassData(string nameSpace, string className, string classHeader)
{
public string NameSpace = nameSpace;
public string ClassName = className;
public string ClassHeader = classHeader;
}
public struct Operation(string operatorToken, string typeA, string typeB, string returnType)
{
public string ReturnType = returnType;
public string OperatorToken = operatorToken;
public string TypeA = typeA;
public string TypeB = typeB;
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;IDE0079;MVVMTK0034</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn>1701;1702;IDE0079;MVVMTK0034</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,397 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace QWERTYkez.Mensura.Generator;
[Generator(LanguageNames.CSharp)]
public class UnitOperatorsGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Регистрируем сам атрибут для использования в коде
context.RegisterPostInitializationOutput(ctx =>
{
ctx.AddSource("UnitOperatorsGeneratorAttribute.g.cs",
SourceText.From(@"namespace MetricSystem.Generator
{
[System.AttributeUsage(System.AttributeTargets.Struct | System.AttributeTargets.RecordStruct, AllowMultiple = false)]
public sealed class UnitOperatorsGeneratorAttribute : System.Attribute { }
}", Encoding.UTF8));
});
// Находим все record struct с атрибутом
var unitStructs = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (node, _) => IsCandidate(node),
transform: static (ctx, _) => GetUnitInfo(ctx))
.Where(info => info != null)
.Select((info, _) => info!.Value)
.Collect();
context.RegisterSourceOutput(unitStructs, Execute);
}
private static bool IsCandidate(SyntaxNode node)
{
return node is RecordDeclarationSyntax record &&
record.Modifiers.Any(SyntaxKind.ReadOnlyKeyword) &&
record.Modifiers.Any(SyntaxKind.PartialKeyword) &&
record.Keyword.IsKind(SyntaxKind.RecordKeyword) &&
record.ClassOrStructKeyword.IsKind(SyntaxKind.StructKeyword);
}
private static UnitInfo? GetUnitInfo(GeneratorSyntaxContext context)
{
var record = (RecordDeclarationSyntax)context.Node;
var semanticModel = context.SemanticModel;
if (semanticModel.GetDeclaredSymbol(record) is not INamedTypeSymbol typeSymbol) return null;
// Проверяем наличие атрибута [UnitOperatorsGenerator]
bool hasAttribute = typeSymbol.GetAttributes()
.Any(attr => attr.AttributeClass?.Name == "UnitOperatorsGeneratorAttribute");
if (!hasAttribute) return null;
string ns = typeSymbol.ContainingNamespace?.ToString();
if (string.IsNullOrEmpty(ns)) return null;
string typeName = typeSymbol.Name;
// Собираем имена уже существующих членов, чтобы не генерировать дубликаты
var existingMemberNames = new HashSet<string>(
typeSymbol.GetMembers()
.Where(m => m.Kind == SymbolKind.Method || m.Kind == SymbolKind.Property || m.Kind == SymbolKind.Field)
.Select(m => m.Name));
var existingOperatorNames = new HashSet<string>(
typeSymbol.GetMembers()
.Where(m => m is IMethodSymbol method && method.MethodKind == MethodKind.UserDefinedOperator)
.Select(m => m.Name));
return new UnitInfo(ns, typeName, existingMemberNames, existingOperatorNames);
}
private static void Execute(SourceProductionContext context, ImmutableArray<UnitInfo> units)
{
// Группируем по полному имени типа, чтобы избежать дублирования hintName
var distinctUnits = units
.GroupBy(u => $"{u.Namespace}.{u.TypeName}")
.Select(g => g.First())
.ToImmutableArray();
foreach (var unit in distinctUnits)
{
string code = GenerateFullCode(unit);
string hintName = $"{unit.Namespace}.{unit.TypeName}.Generated.cs"
.Replace('<', '_')
.Replace('>', '_')
.Replace('[', '_')
.Replace(']', '_')
.Replace(' ', '_');
context.AddSource(hintName, SourceText.From(code, Encoding.UTF8));
}
}
private static string GenerateFullCode(UnitInfo info)
{
string t = info.TypeName;
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated />");
sb.AppendLine("using System.Globalization;");
sb.AppendLine("using System.Runtime.Serialization;");
sb.AppendLine("using System.Numerics;");
sb.AppendLine();
sb.AppendLine($"namespace {info.Namespace};");
sb.AppendLine();
sb.AppendLine($"[System.Text.Json.Serialization.JsonConverter(typeof({t}Converter))]");
sb.AppendLine($"public readonly partial record struct {t}");
sb.AppendLine("{");
// 1. Поле и конструктор
sb.AppendLine($" [System.Text.Json.Serialization.JsonInclude, DataMember, System.Text.Json.Serialization.JsonPropertyName(\"v\")]");
sb.AppendLine($" internal double Value {{ get => _Value; init => _Value = value; }}");
sb.AppendLine($" internal readonly double _Value;");
sb.AppendLine($" internal {t}(double value) => _Value = value;");
sb.AppendLine();
// 2. Базовые методы
if (!info.ExistingMemberNames.Contains("GetHashCode"))
sb.AppendLine($" public override int GetHashCode() => _Value.GetHashCode();");
if (!info.ExistingMemberNames.Contains("CompareTo"))
{
sb.AppendLine($" public int CompareTo({t}? other) => _Value.CompareTo(other is null ? 0d : other._Value);");
sb.AppendLine($" public int CompareTo({t} other) => _Value.CompareTo(other._Value);");
}
if (!info.ExistingMemberNames.Contains("Equals"))
sb.AppendLine($" public bool Equals({t}? other) => _Value.Equals(other?._Value);");
sb.AppendLine();
// 3. Свойства IsPositive и т.д.
if (!info.ExistingMemberNames.Contains("IsPositive"))
sb.AppendLine($" [System.Text.Json.Serialization.JsonIgnore, IgnoreDataMember] public bool IsPositive => _Value >= 0;");
if (!info.ExistingMemberNames.Contains("IsGreaterThanZero"))
sb.AppendLine($" [System.Text.Json.Serialization.JsonIgnore, IgnoreDataMember] public bool IsGreaterThanZero => _Value > 0;");
if (!info.ExistingMemberNames.Contains("IsNegative"))
sb.AppendLine($" [System.Text.Json.Serialization.JsonIgnore, IgnoreDataMember] public bool IsNegative => _Value < 0;");
if (!info.ExistingMemberNames.Contains("IsZero"))
sb.AppendLine($" [System.Text.Json.Serialization.JsonIgnore, IgnoreDataMember] public bool IsZero => _Value == 0;");
sb.AppendLine();
// 4. Статические свойства
if (!info.ExistingMemberNames.Contains("Zero"))
sb.AppendLine($" public static {t} Zero => new(0d);");
if (!info.ExistingMemberNames.Contains("Min"))
sb.AppendLine($" public static {t} Min => new(double.MinValue);");
if (!info.ExistingMemberNames.Contains("Max"))
sb.AppendLine($" public static {t} Max => new(double.MaxValue);");
if (!info.ExistingMemberNames.Contains("NegativeInfinity"))
sb.AppendLine($" public static {t} NegativeInfinity => new(double.NegativeInfinity);");
if (!info.ExistingMemberNames.Contains("PositiveInfinity"))
sb.AppendLine($" public static {t} PositiveInfinity => new(double.PositiveInfinity);");
sb.AppendLine();
// 5. Операторы сравнения (с поддержкой null)
if (!info.ExistingOperatorNames.Contains("op_Equality"))
sb.AppendLine($" public static bool operator ==({t}? left, {t}? right) => (left is null ? 0d : left._Value) == (right is null ? 0d : right._Value);");
if (!info.ExistingOperatorNames.Contains("op_Inequality"))
sb.AppendLine($" public static bool operator !=({t}? left, {t}? right) => (left is null ? 0d : left._Value) != (right is null ? 0d : right._Value);");
if (!info.ExistingOperatorNames.Contains("op_LessThan"))
sb.AppendLine($" public static bool operator <({t}? left, {t}? right) => (left is null ? 0d : left._Value) < (right is null ? 0d : right._Value);");
if (!info.ExistingOperatorNames.Contains("op_LessThanOrEqual"))
sb.AppendLine($" public static bool operator <=({t}? left, {t}? right) => (left is null ? 0d : left._Value) <= (right is null ? 0d : right._Value);");
if (!info.ExistingOperatorNames.Contains("op_GreaterThan"))
sb.AppendLine($" public static bool operator >({t}? left, {t}? right) => (left is null ? 0d : left._Value) > (right is null ? 0d : right._Value);");
if (!info.ExistingOperatorNames.Contains("op_GreaterThanOrEqual"))
sb.AppendLine($" public static bool operator >=({t}? left, {t}? right) => (left is null ? 0d : left._Value) >= (right is null ? 0d : right._Value);");
sb.AppendLine();
// 6. Унарные операторы
if (!info.ExistingOperatorNames.Contains("op_UnaryPlus"))
sb.AppendLine($" public static {t} operator +({t} x) => x;");
if (!info.ExistingOperatorNames.Contains("op_UnaryNegation"))
sb.AppendLine($" public static {t} operator -({t} x) => new(-x._Value);");
sb.AppendLine();
// 7. Бинарные +, - с тем же типом
if (!info.ExistingOperatorNames.Contains("op_Addition"))
sb.AppendLine($" public static {t} operator +({t} a, {t} b) => new(a._Value + b._Value);");
if (!info.ExistingOperatorNames.Contains("op_Subtraction"))
sb.AppendLine($" public static {t} operator -({t} a, {t} b) => new(a._Value - b._Value);");
sb.AppendLine();
// 8. Умножение на числа (все числовые типы)
var numericTypes = new[] { "double", "double?", "byte", "byte?", "sbyte", "sbyte?",
"short", "short?", "ushort", "ushort?", "int", "int?", "uint", "uint?",
"long", "long?", "ulong", "ulong?", "float", "float?", "decimal", "decimal?" };
if (!info.ExistingOperatorNames.Contains("op_Multiply"))
{
foreach (var nt in numericTypes)
{
bool isNullable = nt.EndsWith("?");
string baseType = isNullable ? nt.TrimEnd('?') : nt;
if (baseType == "decimal")
{
string conv = isNullable ? "(double)(b ?? 0m)" : "(double)b";
sb.AppendLine($" public static {t} operator *({t} a, {nt} b) => new(a._Value * {conv});");
sb.AppendLine($" public static {t} operator *({nt} a, {t} b) => new({conv} * b._Value);");
}
else if (isNullable)
{
sb.AppendLine($" public static {t} operator *({t} a, {nt} b) => new(a._Value * (b ?? 0d));");
sb.AppendLine($" public static {t} operator *({nt} a, {t} b) => new((a ?? 0d) * b._Value);");
}
else
{
sb.AppendLine($" public static {t} operator *({t} a, {nt} b) => new(a._Value * b);");
sb.AppendLine($" public static {t} operator *({nt} a, {t} b) => new(a * b._Value);");
}
}
}
// 9. Деление на число
if (!info.ExistingOperatorNames.Contains("op_Division"))
{
foreach (var nt in numericTypes)
{
bool isNullable = nt.EndsWith("?");
string baseType = isNullable ? nt.TrimEnd('?') : nt;
if (baseType == "decimal")
{
string conv = isNullable ? "(double)(b ?? 0m)" : "(double)b";
sb.AppendLine($" public static {t} operator /({t} a, {nt} b) => new(a._Value / {conv});");
}
else if (isNullable)
{
sb.AppendLine($" public static {t} operator /({t} a, {nt} b) => new(a._Value / (b ?? 0d));");
}
else
{
sb.AppendLine($" public static {t} operator /({t} a, {nt} b) => new(a._Value / b);");
}
}
}
// 10. Деление двух метрик (возвращает double)
if (!info.ExistingOperatorNames.Contains("op_Division"))
sb.AppendLine($" public static double operator /({t} a, {t} b) => a._Value / b._Value;");
sb.AppendLine();
// 11. Методы SetArray / SetList
GenerateArrayAndListMethods(sb, t);
sb.AppendLine("}");
// 12. Конвертер JSON
GenerateJsonConverter(sb, t);
return sb.ToString();
}
private static void GenerateArrayAndListMethods(StringBuilder sb, string t)
{
// SetArray
sb.AppendLine($@" /// <summary>With Multiply</summary>
public {t}[] SetArray(IEnumerable<double> nums)
{{
if (nums is not null)
{{
var result = new {t}[nums.Count()];
int i = 0;
foreach (var num in nums)
result[i++] = new(_Value * num);
return result;
}}
return [];
}}
/// <summary>With Multiply</summary>
public {t}[] SetArray(IEnumerable<double?> nums)
{{
if (nums is not null)
{{
var result = new {t}[nums.Count()];
int i = 0;
foreach (var num in nums)
result[i++] = new((num ?? 0d) * _Value);
return result;
}}
return [];
}}
/// <summary>With Multiply</summary>
public {t}[] SetArray<N>(IEnumerable<N> nums) where N : INumber<N>
{{
if (nums is not null)
{{
var result = new {t}[nums.Count()];
int i = 0;
foreach (var num in nums)
result[i++] = new(num.ToDouble() * _Value);
return result;
}}
return [];
}}
/// <summary>With Multiply</summary>
public {t}[] SetArray<N>(IEnumerable<N?> nums) where N : struct, INumber<N>
{{
if (nums is not null)
{{
var result = new {t}[nums.Count()];
int i = 0;
foreach (var num in nums)
result[i++] = new(num.ToDouble() * _Value);
return result;
}}
return [];
}}");
// SetList
sb.AppendLine($@"
/// <summary>With Multiply</summary>
public List<{t}> SetList(IEnumerable<double> nums)
{{
if (nums is not null)
{{
var result = new List<{t}>(nums.Count());
foreach (var num in nums)
result.Add(new(_Value * num));
return result;
}}
return [];
}}
/// <summary>With Multiply</summary>
public List<{t}> SetList(IEnumerable<double?> nums)
{{
if (nums is not null)
{{
var result = new List<{t}>(nums.Count());
foreach (var num in nums)
result.Add(new((num ?? 0d) * _Value));
return result;
}}
return [];
}}
/// <summary>With Multiply</summary>
public List<{t}> SetList<N>(IEnumerable<N> nums) where N : INumber<N>
{{
if (nums is not null)
{{
var result = new List<{t}>(nums.Count());
foreach (var num in nums)
result.Add(new(_Value * num.ToDouble()));
return result;
}}
return [];
}}
/// <summary>With Multiply</summary>
public List<{t}> SetList<N>(IEnumerable<N?> nums) where N : struct, INumber<N>
{{
if (nums is not null)
{{
var result = new List<{t}>(nums.Count());
foreach (var num in nums)
result.Add(new(_Value * num.ToDouble()));
return result;
}}
return [];
}}");
}
private static void GenerateJsonConverter(StringBuilder sb, string t)
{
sb.AppendLine($@"
public class {t}Converter : System.Text.Json.Serialization.JsonConverter<{t}>
{{
private static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
public override {t} Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{{
double doubleValue;
if (reader.TokenType == JsonTokenType.String)
{{
if (!double.TryParse(reader.GetString(), Culture, out doubleValue))
throw new JsonException($""Не удалось преобразовать строковое значение в double для метрики {t}."");
}}
else
{{
doubleValue = reader.GetDouble();
}}
return new(doubleValue);
}}
public override void Write(Utf8JsonWriter writer, {t} value, JsonSerializerOptions options)
{{
writer.WriteNumberValue(value._Value);
}}
public override void WriteAsPropertyName(Utf8JsonWriter writer, {t} value, JsonSerializerOptions options)
{{
writer.WritePropertyName(value._Value.ToString(""R"", Culture));
}}
public override {t} ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{{
string propertyName = reader.GetString()!;
if (!double.TryParse(propertyName, Culture, out double doubleValue))
throw new JsonException($""Невалидное числовое значение в ключе свойства JSON: '{{propertyName}}' для метрики {t}."");
return new(doubleValue);
}}
}}");
}
private readonly struct UnitInfo(string ns, string typeName, HashSet<string> existingMemberNames, HashSet<string> existingOperatorNames)
{
public string Namespace { get; } = ns;
public string TypeName { get; } = typeName;
public HashSet<string> ExistingMemberNames { get; } = existingMemberNames;
public HashSet<string> ExistingOperatorNames { get; } = existingOperatorNames;
}
}