refactor(ioc): 优化依赖注入容器的优先级排序实现

- 修改 MicrosoftDiContainer 中 GetAllByPriority 方法的排序逻辑,使用 OrderBy 确保稳定排序
- 修正注释中的中文描述,明确优先级排序规则
- 将 PriorityUsageAnalyzer 中的硬编码诊断规则替换为统一的 PriorityDiagnostic
- 在 PriorityGenerator 中添加对嵌套类的支持检查,报告 GF_Priority_005 错误
- 改进生成文件的命名策略,使用完整元数据名称避免冲突
- 更新 AnalyzerReleases.Unshipped.md 文档,添加新的诊断规则说明
- 移除 PriorityGeneratorSnapshotTests 中关于嵌套类的测试用例
This commit is contained in:
GeWuYou 2026-03-05 22:45:45 +08:00 committed by gewuyou
parent 330bd8b0b0
commit 16d8cad4f3
6 changed files with 66 additions and 87 deletions

View File

@ -610,7 +610,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// <summary> /// <summary>
/// 获取指定类型的所有实例,并按优先级排序 /// 获取指定类型的所有实例,并按优先级排序
/// 实现 IPrioritized 接口的服务将按值越小优先级越高) /// 实现 IPrioritized 接口的服务将按优先级排序(数值越小优先级越高)
/// 未实现 IPrioritized 的服务将使用默认优先级 0 /// 未实现 IPrioritized 的服务将使用默认优先级 0
/// </summary> /// </summary>
/// <param name="type">期望获取的实例类型</param> /// <param name="type">期望获取的实例类型</param>
@ -632,17 +632,16 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
if (services.Count <= 1) if (services.Count <= 1)
return services; return services;
var list = services.ToList(); // 使用 OrderBy 确保稳定排序(相同优先级保持原有顺序)
return services
// 稳定排序:相同优先级保持注册顺序 .Select((service, index) => new { Service = service, Index = index })
list.Sort((a, b) => .OrderBy(x =>
{ {
var priorityA = a is IPrioritized pa ? pa.Priority : 0; var priority = x.Service is IPrioritized p ? p.Priority : 0;
var priorityB = b is IPrioritized pb ? pb.Priority : 0; return (priority, x.Index); // 先按优先级,再按索引
return priorityA.CompareTo(priorityB); // 升序 })
}); .Select(x => x.Service)
.ToList();
return list;
} }
#endregion #endregion

View File

@ -212,55 +212,4 @@ public class PriorityGeneratorSnapshotTests
"PriorityGenerator", "PriorityGenerator",
"GenericClass")); "GenericClass"));
} }
/// <summary>
/// 测试嵌套类支持
/// </summary>
[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<PriorityGenerator>.RunAsync(
source,
Path.Combine(
TestContext.CurrentContext.TestDirectory,
"bases",
"snapshots",
"PriorityGenerator",
"NestedClass"));
}
} }

View File

@ -11,4 +11,5 @@
GF_Priority_002 | GFramework.Priority | Warning | PriorityDiagnostic GF_Priority_002 | GFramework.Priority | Warning | PriorityDiagnostic
GF_Priority_003 | GFramework.Priority | Error | PriorityDiagnostic GF_Priority_003 | GFramework.Priority | Error | PriorityDiagnostic
GF_Priority_004 | 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 GF_Priority_Usage_001 | GFramework.Usage | Info | PriorityUsageAnalyzer

View File

@ -1,4 +1,5 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using GFramework.SourceGenerators.diagnostics;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Operations;
@ -11,27 +12,11 @@ namespace GFramework.SourceGenerators.analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)] [DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class PriorityUsageAnalyzer : DiagnosticAnalyzer public sealed class PriorityUsageAnalyzer : DiagnosticAnalyzer
{ {
/// <summary>
/// 诊断 ID
/// </summary>
private const string DiagnosticId = "GF_Priority_Usage_001";
/// <summary>
/// 诊断规则
/// </summary>
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 方法以确保按优先级排序。");
/// <summary> /// <summary>
/// 支持的诊断规则 /// 支持的诊断规则
/// </summary> /// </summary>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(PriorityDiagnostic.SuggestGetAllByPriority);
/// <summary> /// <summary>
/// 初始化分析器 /// 初始化分析器
@ -112,7 +97,7 @@ public sealed class PriorityUsageAnalyzer : DiagnosticAnalyzer
// 报告诊断 // 报告诊断
var diagnostic = Diagnostic.Create( var diagnostic = Diagnostic.Create(
Rule, PriorityDiagnostic.SuggestGetAllByPriority,
invocation.Syntax.GetLocation(), invocation.Syntax.GetLocation(),
typeArgument.ToDisplayString()); typeArgument.ToDisplayString());

View File

@ -45,8 +45,18 @@ public sealed class PriorityGenerator : MetadataAttributeClassGeneratorBase
return false; return false;
} }
// 2. 必须是 partial // 2. 不支持嵌套类
if (!syntax.Modifiers.Any(SyntaxKind.PartialKeyword)) 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( context.ReportDiagnostic(Diagnostic.Create(
PriorityDiagnostic.MustBePartial, PriorityDiagnostic.MustBePartial,
@ -55,7 +65,7 @@ public sealed class PriorityGenerator : MetadataAttributeClassGeneratorBase
return false; return false;
} }
// 3. 检查是否已手动实现 IPrioritized // 4. 检查是否已手动实现 IPrioritized
var iPrioritized = compilation.GetTypeByMetadataName( var iPrioritized = compilation.GetTypeByMetadataName(
$"{PathContests.CoreAbstractionsNamespace}.bases.IPrioritized"); $"{PathContests.CoreAbstractionsNamespace}.bases.IPrioritized");
@ -68,7 +78,7 @@ public sealed class PriorityGenerator : MetadataAttributeClassGeneratorBase
return false; return false;
} }
// 4. 验证特性参数 // 5. 验证特性参数
if (attr.ConstructorArguments.Length == 0 || if (attr.ConstructorArguments.Length == 0 ||
attr.ConstructorArguments[0].Value is not int) attr.ConstructorArguments[0].Value is not int)
{ {
@ -129,6 +139,15 @@ public sealed class PriorityGenerator : MetadataAttributeClassGeneratorBase
/// </summary> /// </summary>
protected override string GetHintName(INamedTypeSymbol symbol) 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";
} }
} }

View File

@ -60,4 +60,30 @@ internal static class PriorityDiagnostic
isEnabledByDefault: true, isEnabledByDefault: true,
description: "Priority 特性必须提供一个有效的整数值。" description: "Priority 特性必须提供一个有效的整数值。"
); );
/// <summary>
/// GF_Priority_005: Priority 不支持嵌套类
/// </summary>
public static readonly DiagnosticDescriptor NestedClassNotSupported = new(
id: "GF_Priority_005",
title: "Priority 不支持嵌套类",
messageFormat: "Priority 特性不支持嵌套类 '{0}',请将类移至顶层",
category: Category,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "Priority 特性仅支持顶层类,不支持嵌套类。请将嵌套类移至命名空间级别。"
);
/// <summary>
/// GF_Priority_Usage_001: 建议使用 GetAllByPriority
/// </summary>
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 方法以确保按优先级排序。"
);
} }