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