diff --git a/GFramework.Core/ioc/MicrosoftDiContainer.cs b/GFramework.Core/ioc/MicrosoftDiContainer.cs index d591baa..7ff5d20 100644 --- a/GFramework.Core/ioc/MicrosoftDiContainer.cs +++ b/GFramework.Core/ioc/MicrosoftDiContainer.cs @@ -610,7 +610,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) /// /// 获取指定类型的所有实例,并按优先级排序 - /// 实现 IPrioritized 接口的服务将按值越小优先级越高) + /// 实现 IPrioritized 接口的服务将按优先级排序(数值越小优先级越高) /// 未实现 IPrioritized 的服务将使用默认优先级 0 /// /// 期望获取的实例类型 @@ -632,17 +632,16 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) if (services.Count <= 1) return services; - var list = services.ToList(); - - // 稳定排序:相同优先级保持注册顺序 - list.Sort((a, b) => - { - var priorityA = a is IPrioritized pa ? pa.Priority : 0; - var priorityB = b is IPrioritized pb ? pb.Priority : 0; - return priorityA.CompareTo(priorityB); // 升序 - }); - - return list; + // 使用 OrderBy 确保稳定排序(相同优先级保持原有顺序) + return services + .Select((service, index) => new { Service = service, Index = index }) + .OrderBy(x => + { + var priority = x.Service is IPrioritized p ? p.Priority : 0; + return (priority, x.Index); // 先按优先级,再按索引 + }) + .Select(x => x.Service) + .ToList(); } #endregion diff --git a/GFramework.SourceGenerators.Tests/bases/PriorityGeneratorSnapshotTests.cs b/GFramework.SourceGenerators.Tests/bases/PriorityGeneratorSnapshotTests.cs index c5c7c56..d5ac1f1 100644 --- a/GFramework.SourceGenerators.Tests/bases/PriorityGeneratorSnapshotTests.cs +++ b/GFramework.SourceGenerators.Tests/bases/PriorityGeneratorSnapshotTests.cs @@ -212,55 +212,4 @@ public class PriorityGeneratorSnapshotTests "PriorityGenerator", "GenericClass")); } - - /// - /// 测试嵌套类支持 - /// - [Test] - public async Task Snapshot_NestedClass() - { - const string source = """ - using System; - - namespace GFramework.SourceGenerators.Abstractions.bases - { - [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] - public sealed class PriorityAttribute : Attribute - { - public int Value { get; } - public PriorityAttribute(int value) { Value = value; } - } - } - - namespace GFramework.Core.Abstractions.bases - { - public interface IPrioritized - { - int Priority { get; } - } - } - - namespace TestApp - { - using GFramework.SourceGenerators.Abstractions.bases; - - public class OuterClass - { - [Priority(30)] - public partial class NestedSystem - { - } - } - } - """; - - await GeneratorSnapshotTest.RunAsync( - source, - Path.Combine( - TestContext.CurrentContext.TestDirectory, - "bases", - "snapshots", - "PriorityGenerator", - "NestedClass")); - } } \ No newline at end of file diff --git a/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md b/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md index ce9dd59..d3e0463 100644 --- a/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md +++ b/GFramework.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -11,4 +11,5 @@ GF_Priority_002 | GFramework.Priority | Warning | PriorityDiagnostic GF_Priority_003 | GFramework.Priority | Error | PriorityDiagnostic GF_Priority_004 | GFramework.Priority | Error | PriorityDiagnostic + GF_Priority_005 | GFramework.Priority | Error | PriorityDiagnostic GF_Priority_Usage_001 | GFramework.Usage | Info | PriorityUsageAnalyzer \ No newline at end of file diff --git a/GFramework.SourceGenerators/analyzers/PriorityUsageAnalyzer.cs b/GFramework.SourceGenerators/analyzers/PriorityUsageAnalyzer.cs index 37d82a7..33a770a 100644 --- a/GFramework.SourceGenerators/analyzers/PriorityUsageAnalyzer.cs +++ b/GFramework.SourceGenerators/analyzers/PriorityUsageAnalyzer.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using GFramework.SourceGenerators.diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -11,27 +12,11 @@ namespace GFramework.SourceGenerators.analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class PriorityUsageAnalyzer : DiagnosticAnalyzer { - /// - /// 诊断 ID - /// - private const string DiagnosticId = "GF_Priority_Usage_001"; - - /// - /// 诊断规则 - /// - private static readonly DiagnosticDescriptor Rule = new( - id: DiagnosticId, - title: "建议使用 GetAllByPriority", - messageFormat: "类型 '{0}' 实现了 IPrioritized 接口,建议使用 GetAllByPriority<{0}>() 而非 GetAll<{0}>()", - category: "GFramework.Usage", - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, - description: "当获取实现了 IPrioritized 接口的服务时,应使用 GetAllByPriority 方法以确保按优先级排序。"); - /// /// 支持的诊断规则 /// - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create(PriorityDiagnostic.SuggestGetAllByPriority); /// /// 初始化分析器 @@ -112,7 +97,7 @@ public sealed class PriorityUsageAnalyzer : DiagnosticAnalyzer // 报告诊断 var diagnostic = Diagnostic.Create( - Rule, + PriorityDiagnostic.SuggestGetAllByPriority, invocation.Syntax.GetLocation(), typeArgument.ToDisplayString()); diff --git a/GFramework.SourceGenerators/bases/PriorityGenerator.cs b/GFramework.SourceGenerators/bases/PriorityGenerator.cs index a509347..69a9acd 100644 --- a/GFramework.SourceGenerators/bases/PriorityGenerator.cs +++ b/GFramework.SourceGenerators/bases/PriorityGenerator.cs @@ -45,8 +45,18 @@ public sealed class PriorityGenerator : MetadataAttributeClassGeneratorBase return false; } - // 2. 必须是 partial - if (!syntax.Modifiers.Any(SyntaxKind.PartialKeyword)) + // 2. 不支持嵌套类 + if (symbol.ContainingType != null) + { + context.ReportDiagnostic(Diagnostic.Create( + PriorityDiagnostic.NestedClassNotSupported, + syntax.Identifier.GetLocation(), + symbol.Name)); + return false; + } + + // 3. 必须是 partial + if (syntax.Modifiers.All(m => m.Kind() != SyntaxKind.PartialKeyword)) { context.ReportDiagnostic(Diagnostic.Create( PriorityDiagnostic.MustBePartial, @@ -55,7 +65,7 @@ public sealed class PriorityGenerator : MetadataAttributeClassGeneratorBase return false; } - // 3. 检查是否已手动实现 IPrioritized + // 4. 检查是否已手动实现 IPrioritized var iPrioritized = compilation.GetTypeByMetadataName( $"{PathContests.CoreAbstractionsNamespace}.bases.IPrioritized"); @@ -68,7 +78,7 @@ public sealed class PriorityGenerator : MetadataAttributeClassGeneratorBase return false; } - // 4. 验证特性参数 + // 5. 验证特性参数 if (attr.ConstructorArguments.Length == 0 || attr.ConstructorArguments[0].Value is not int) { @@ -129,6 +139,15 @@ public sealed class PriorityGenerator : MetadataAttributeClassGeneratorBase /// protected override string GetHintName(INamedTypeSymbol symbol) { - return $"{symbol.Name}.Priority.g.cs"; + // 使用完整的元数据名称以避免冲突 + var metadataName = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + .Replace("global::", "") + .Replace("<", "_") + .Replace(">", "_") + .Replace(",", "_") + .Replace(" ", "") + .Replace(".", "_"); + + return $"{metadataName}.Priority.g.cs"; } } \ No newline at end of file diff --git a/GFramework.SourceGenerators/diagnostics/PriorityDiagnostic.cs b/GFramework.SourceGenerators/diagnostics/PriorityDiagnostic.cs index f9fb2d7..e63837d 100644 --- a/GFramework.SourceGenerators/diagnostics/PriorityDiagnostic.cs +++ b/GFramework.SourceGenerators/diagnostics/PriorityDiagnostic.cs @@ -60,4 +60,30 @@ internal static class PriorityDiagnostic isEnabledByDefault: true, description: "Priority 特性必须提供一个有效的整数值。" ); + + /// + /// GF_Priority_005: Priority 不支持嵌套类 + /// + public static readonly DiagnosticDescriptor NestedClassNotSupported = new( + id: "GF_Priority_005", + title: "Priority 不支持嵌套类", + messageFormat: "Priority 特性不支持嵌套类 '{0}',请将类移至顶层", + category: Category, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Priority 特性仅支持顶层类,不支持嵌套类。请将嵌套类移至命名空间级别。" + ); + + /// + /// GF_Priority_Usage_001: 建议使用 GetAllByPriority + /// + public static readonly DiagnosticDescriptor SuggestGetAllByPriority = new( + id: "GF_Priority_Usage_001", + title: "建议使用 GetAllByPriority", + messageFormat: "类型 '{0}' 实现了 IPrioritized 接口,建议使用 GetAllByPriority<{0}>() 而非 GetAll<{0}>()", + category: "GFramework.Usage", + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: "当获取实现了 IPrioritized 接口的服务时,应使用 GetAllByPriority 方法以确保按优先级排序。" + ); } \ No newline at end of file