From 50a71deaa7930eab18b72b5804b9f26b47e40ecd Mon Sep 17 00:00:00 2001 From: GwWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 10 Dec 2025 08:35:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(generator):=20=E6=B7=BB=E5=8A=A0=E6=9E=9A?= =?UTF-8?q?=E4=B8=BE=E6=89=A9=E5=B1=95=E6=96=B9=E6=B3=95=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=99=A8=E5=8F=8A=E7=9B=B8=E5=85=B3=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 EnumExtensionsGenerator 源生成器 - 实现 GenerateEnumExtensionsAttribute 特性标注 - 为标记的枚举自动生成 IsXXX 和 IsIn 扩展方法 - 配置项目引用及 Analyzer 打包设置 - 更新解决方案文件包含新增项目 - 调整主项目配置排除生成器相关文件编译 --- .../GFramework.Generator.Attributes.csproj | 7 + .../enums/EnumExtensionsAttribute.cs | 22 +++ .../GFramework.Generator.csproj | 39 ++++++ .../enums/EnumExtensionsGenerator.cs | 125 ++++++++++++++++++ GFramework.csproj | 22 +++ GFramework.sln | 12 ++ framework/property/BindableProperty.cs | 24 ++-- 7 files changed, 240 insertions(+), 11 deletions(-) create mode 100644 GFramework.Generator.Attributes/GFramework.Generator.Attributes.csproj create mode 100644 GFramework.Generator.Attributes/generator/enums/EnumExtensionsAttribute.cs create mode 100644 GFramework.Generator/GFramework.Generator.csproj create mode 100644 GFramework.Generator/generator/enums/EnumExtensionsGenerator.cs diff --git a/GFramework.Generator.Attributes/GFramework.Generator.Attributes.csproj b/GFramework.Generator.Attributes/GFramework.Generator.Attributes.csproj new file mode 100644 index 0000000..2b946ab --- /dev/null +++ b/GFramework.Generator.Attributes/GFramework.Generator.Attributes.csproj @@ -0,0 +1,7 @@ + + + netstandard2.0 + GeWuYou.GFramework.Generator.Attributes + 1.0.0 + + diff --git a/GFramework.Generator.Attributes/generator/enums/EnumExtensionsAttribute.cs b/GFramework.Generator.Attributes/generator/enums/EnumExtensionsAttribute.cs new file mode 100644 index 0000000..f78769c --- /dev/null +++ b/GFramework.Generator.Attributes/generator/enums/EnumExtensionsAttribute.cs @@ -0,0 +1,22 @@ + +using System; + +namespace GFramework.Generator.Attributes +{ + /// + /// 标注在 enum 上,Source Generator 会为该 enum 生成扩展方法。 + /// + [AttributeUsage(AttributeTargets.Enum)] + public sealed class GenerateEnumExtensionsAttribute : Attribute + { + /// + /// 是否为每个枚举项生成单独的 IsXXX 方法(默认 true)。 + /// + public bool GenerateIsMethods { get; set; } = true; + + /// + /// 是否生成一个 IsIn(params T[]) 方法以简化多值判断(默认 true)。 + /// + public bool GenerateIsInMethod { get; set; } = true; + } +} \ No newline at end of file diff --git a/GFramework.Generator/GFramework.Generator.csproj b/GFramework.Generator/GFramework.Generator.csproj new file mode 100644 index 0000000..2dc5062 --- /dev/null +++ b/GFramework.Generator/GFramework.Generator.csproj @@ -0,0 +1,39 @@ + + + + netstandard2.0 + GeWuYou.GFramework.Generator + latest + true + + + true + + + false + + + true + Generated + + + + + + + + + + + + + + + + + + + diff --git a/GFramework.Generator/generator/enums/EnumExtensionsGenerator.cs b/GFramework.Generator/generator/enums/EnumExtensionsGenerator.cs new file mode 100644 index 0000000..3bc6df3 --- /dev/null +++ b/GFramework.Generator/generator/enums/EnumExtensionsGenerator.cs @@ -0,0 +1,125 @@ +using System; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace GFramework.Generator.generator.enums +{ + [Generator] + public class EnumExtensionsGenerator : IIncrementalGenerator + { + private const string AttributeFullName = "GFramework.Generator.Attributes.GenerateEnumExtensionsAttribute"; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // 1. 找到所有 EnumDeclarationSyntax 节点 + var enumDecls = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: (s, _) => s is EnumDeclarationSyntax, + transform: (ctx, _) => + (EnumDecl: (EnumDeclarationSyntax)ctx.Node, ctx.SemanticModel)) + .Where(t => t.EnumDecl != null); + + // 2. 解析为 symbol 并过滤带 Attribute 的 enum + var enumSymbols = enumDecls + .Select((t, _) => + { + var model = t.SemanticModel; + var enumDecl = t.EnumDecl; + var symbol = model.GetDeclaredSymbol(enumDecl) as INamedTypeSymbol; + return symbol; + }) + .Where(symbol => symbol != null) + .Select((symbol, _) => + { + // 判断是否包含我们的 Attribute + var hasAttr = symbol.GetAttributes().Any(ad => + ad.AttributeClass?.ToDisplayString() == AttributeFullName || + ad.AttributeClass?.ToDisplayString().EndsWith(".GenerateEnumExtensionsAttribute") == true); + return (Symbol: symbol, HasAttr: hasAttr); + }) + .Where(x => x.HasAttr) + .Collect(); + + // 3. 为每个 enum 生成代码 + context.RegisterSourceOutput(enumSymbols, (spc, list) => + { + foreach (var enumSymbol in list.Select(item => item.Symbol)) + { + try + { + var src = GenerateForEnum(enumSymbol); + var hintName = $"{enumSymbol.Name}.EnumExtensions.g.cs"; + spc.AddSource(hintName, SourceText.From(src, Encoding.UTF8)); + } + catch (Exception ex) + { + // 发生异常时生成一个注释文件(避免完全静默失败) + var err = $"// EnumExtensionsGenerator failed for {enumSymbol?.Name}: {ex.Message}"; + spc.AddSource($"{enumSymbol?.Name}.EnumExtensions.Error.g.cs", + SourceText.From(err, Encoding.UTF8)); + } + } + }); + } + + private static string GenerateForEnum(INamedTypeSymbol enumSymbol) + { + var ns = enumSymbol.ContainingNamespace.IsGlobalNamespace + ? null + : enumSymbol.ContainingNamespace.ToDisplayString(); + var enumName = enumSymbol.Name; + var fullEnumName = enumSymbol.ToDisplayString(); // 包含命名空间 + var members = enumSymbol.GetMembers().OfType().Where(f => f.ConstantValue != null).ToArray(); + + var sb = new StringBuilder(); + sb.AppendLine("// "); + sb.AppendLine("using System;"); + if (!string.IsNullOrEmpty(ns)) + { + sb.AppendLine($"namespace {ns}"); + sb.AppendLine("{"); + } + else + { + sb.AppendLine("namespace EnumExtensionsGenerated"); + sb.AppendLine("{"); + } + + sb.AppendLine($" public static partial class {enumName}Extensions"); + sb.AppendLine(" {"); + + // 1. 单项 IsX 方法 +// 替换原第93行开始的 foreach 块 + var memberChecks = members.Select(m => + { + var memberName = m.Name; + var safeMethodName = $"Is{memberName}"; + return $@" /// Auto-generated: 是否为 {memberName} + public static bool {safeMethodName}(this {fullEnumName} value) => value == {fullEnumName}.{memberName}; + +"; + }).ToArray(); + + sb.Append(string.Join("", memberChecks)); + + + // 2. IsIn(params ...) 方法 + sb.AppendLine($" /// Auto-generated: 判断是否属于指定集合"); + sb.AppendLine( + $" public static bool IsIn(this {fullEnumName} value, params {fullEnumName}[] values)"); + sb.AppendLine(" {"); + sb.AppendLine(" if (values == null) return false;"); + sb.AppendLine(" foreach (var v in values) if (value == v) return true;"); + sb.AppendLine(" return false;"); + sb.AppendLine(" }"); + + sb.AppendLine(" }"); + sb.AppendLine("}"); // namespace + + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/GFramework.csproj b/GFramework.csproj index cfc7d53..df9c63e 100644 --- a/GFramework.csproj +++ b/GFramework.csproj @@ -22,7 +22,29 @@ README.md net9.0;net8.0 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GFramework.sln b/GFramework.sln index 7a7a807..c2b50d7 100644 --- a/GFramework.sln +++ b/GFramework.sln @@ -2,6 +2,10 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework ", "GFramework.csproj", "{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Generator", "GFramework.Generator\GFramework.Generator.csproj", "{E9D51809-0351-4B83-B85B-B5F469AAB3B8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Generator.Attributes", "GFramework.Generator.Attributes\GFramework.Generator.Attributes.csproj", "{84C5C3C9-5620-4924-BA04-92F813F2B70F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +16,13 @@ Global {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|Any CPU.Build.0 = Debug|Any CPU {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|Any CPU.ActiveCfg = Release|Any CPU {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|Any CPU.Build.0 = Release|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|Any CPU.Build.0 = Release|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/framework/property/BindableProperty.cs b/framework/property/BindableProperty.cs index 6880b83..5d591b3 100644 --- a/framework/property/BindableProperty.cs +++ b/framework/property/BindableProperty.cs @@ -1,4 +1,4 @@ -using GFramework.framework.events; +using GFramework.framework.events; namespace GFramework.framework.property; @@ -8,14 +8,14 @@ namespace GFramework.framework.property; /// /// 属性值的类型 /// 属性的默认值 -public class BindableProperty(T defaultValue = default) : IBindableProperty +public class BindableProperty(T defaultValue = default!) : IBindableProperty { protected T MValue = defaultValue; /// /// 获取或设置属性值比较器,默认使用Equals方法进行比较 /// - public static Func Comparer { get; set; } = (a, b) => a.Equals(b); + public static Func Comparer { get; set; } = (a, b) => a!.Equals(b)!; /// /// 设置自定义比较器 @@ -37,12 +37,12 @@ public class BindableProperty(T defaultValue = default) : IBindableProperty.Default.Equals(value, default) && - EqualityComparer.Default.Equals(MValue, default)) + if (EqualityComparer.Default.Equals(value, default!) && + EqualityComparer.Default.Equals(MValue, default!)) return; // 若新值与旧值相等则不执行后续操作 - if (!EqualityComparer.Default.Equals(value, default) && Comparer(value, MValue)) + if (!EqualityComparer.Default.Equals(value, default!) && Comparer(value, MValue)) return; SetValue(value); @@ -68,7 +68,7 @@ public class BindableProperty(T defaultValue = default) : IBindableProperty新的属性值 public void SetValueWithoutEvent(T newValue) => MValue = newValue; - private Action _mOnValueChanged = (_) => { }; + private Action? _mOnValueChanged = null; /// /// 注册属性值变化事件回调 @@ -106,13 +106,15 @@ public class BindableProperty(T defaultValue = default) : IBindableProperty onEvent(); + void Action(T _) + { + onEvent(); + } } /// /// 返回属性值的字符串表示形式 /// /// 属性值的字符串表示 - public override string ToString() => Value.ToString(); -} - + public override string ToString() => Value?.ToString() ?? string.Empty; +} \ No newline at end of file