using System.Text;
using Dpz.Core.SourceGenerator.Models;
namespace Dpz.Core.SourceGenerator;
/// <summary>
/// 为服务实现生成缓存前缀、方法标签和缓存键元数据。
/// </summary>
internal static class CacheMetadataSourceGenerator
{
internal static string Generate(ServiceRegistration registration)
{
var defaultPrefix = GetCachePrefix(registration.ImplementationType);
var source = new StringBuilder();
source.AppendLine("// <auto-generated />");
source.AppendLine("#nullable enable");
source.AppendLine();
source.Append("namespace ");
source.Append(registration.ImplementationNamespace);
source.AppendLine(";");
source.AppendLine();
source.Append("internal static class ");
source.AppendLine(registration.CacheMetadataName);
source.AppendLine("{");
source.Append(" public const string DefaultPrefix = ");
source.Append(SymbolDisplay.ToCSharpStringLiteral(defaultPrefix));
source.AppendLine(";");
source.AppendLine();
foreach (var method in GetDistinctCachedMethods(registration))
{
var prefix = method.Options.Prefix ?? defaultPrefix;
source.Append(" public const string ");
source.Append(method.Name);
source.Append("Prefix = ");
source.Append(SymbolDisplay.ToCSharpStringLiteral(prefix));
source.AppendLine(";");
source.Append(" public const string ");
source.Append(method.Name);
source.Append("MethodTag = ");
source.Append(SymbolDisplay.ToCSharpStringLiteral(prefix + ":" + method.Name));
source.AppendLine(";");
source.AppendLine();
}
AppendPrefixMethods(source, registration, defaultPrefix);
foreach (var method in registration.CachedMethods)
{
AppendCacheKeyMethod(source, method, method.Options.Prefix ?? defaultPrefix);
}
if (registration.CachedMethods.Length > 0)
{
AppendCacheKeyHelpers(source);
}
source.AppendLine("}");
return source.ToString();
}
private static void AppendPrefixMethods(
StringBuilder source,
ServiceRegistration registration,
string defaultPrefix
)
{
var distinctMethods = GetDistinctCachedMethods(registration).ToList();
var customPrefixes = distinctMethods
.Where(method =>
!string.IsNullOrWhiteSpace(method.Options.Prefix)
&& method.Options.Prefix != defaultPrefix
)
.ToList();
source.AppendLine(" public static string GetPrefix(string methodName)");
source.AppendLine(" {");
if (customPrefixes.Count == 0)
{
source.AppendLine(" return DefaultPrefix;");
}
else
{
source.AppendLine(" return methodName switch");
source.AppendLine(" {");
foreach (var method in customPrefixes)
{
source.Append(" ");
source.Append(SymbolDisplay.ToCSharpStringLiteral(method.Name));
source.Append(" => ");
source.Append(method.Name);
source.AppendLine("Prefix,");
}
source.AppendLine(" _ => DefaultPrefix,");
source.AppendLine(" };");
}
source.AppendLine(" }");
source.AppendLine();
source.AppendLine(" public static string GetMethodTag(string methodName)");
source.AppendLine(" {");
if (distinctMethods.Count == 0)
{
source.AppendLine(" return DefaultPrefix + \":\" + methodName;");
}
else
{
source.AppendLine(" return methodName switch");
source.AppendLine(" {");
foreach (var method in distinctMethods)
{
source.Append(" ");
source.Append(SymbolDisplay.ToCSharpStringLiteral(method.Name));
source.Append(" => ");
source.Append(method.Name);
source.AppendLine("MethodTag,");
}
source.AppendLine(" _ => GetPrefix(methodName) + \":\" + methodName,");
source.AppendLine(" };");
}
source.AppendLine(" }");
source.AppendLine();
source.AppendLine(" public static string[] GetPrefixes()");
source.AppendLine(" {");
source.Append(" return new[] { ");
AppendDistinctPrefixes(source, registration, defaultPrefix);
source.AppendLine(" };");
source.AppendLine(" }");
source.AppendLine();
}
private static void AppendCacheKeyMethod(
StringBuilder source,
CachedMethod method,
string prefix
)
{
source.Append(" public static string Build");
source.Append(method.Name);
source.Append("CacheKey(");
AppendCacheKeyParameterList(source, method);
source.AppendLine(")");
source.AppendLine(" {");
source.Append(" return ");
AppendCacheKeyExpression(source, method, prefix);
source.AppendLine(";");
source.AppendLine(" }");
source.AppendLine();
}
private static void AppendCacheKeyExpression(
StringBuilder source,
CachedMethod method,
string prefix
)
{
var baseKey = prefix + ":" + method.Name;
if (!string.IsNullOrWhiteSpace(method.Options.CacheKey))
{
source.Append(SymbolDisplay.ToCSharpStringLiteral(baseKey + ":"));
source.Append(" + EscapeCacheKeySegment(");
source.Append(SymbolDisplay.ToCSharpStringLiteral(method.Options.CacheKey!));
source.Append(")");
return;
}
var parameters = GetCacheKeyParameters(method).ToList();
if (parameters.Count == 0)
{
source.Append(SymbolDisplay.ToCSharpStringLiteral(baseKey));
return;
}
source.Append("string.Concat(");
source.Append(SymbolDisplay.ToCSharpStringLiteral(baseKey + ":"));
source.Append(", ");
var segments = parameters
.SelectMany(parameter =>
parameter.CacheKeySegments.IsDefaultOrEmpty
?
[
new CachedKeySegment(
parameter.Name,
"FormatCacheKeyValue(" + parameter.Name + ")"
),
]
: parameter.CacheKeySegments
)
.ToList();
for (var i = 0; i < segments.Count; i++)
{
if (i > 0)
{
source.Append(", \"&\", ");
}
var segment = segments[i];
source.Append(SymbolDisplay.ToCSharpStringLiteral(segment.Name + "="));
source.Append(", ");
source.Append(segment.ValueExpression);
}
source.Append(")");
}
private static void AppendCacheKeyParameterList(StringBuilder source, CachedMethod method)
{
var keyParameters = GetCacheKeyParameters(method).ToList();
for (var i = 0; i < keyParameters.Count; i++)
{
if (i > 0)
{
source.Append(", ");
}
source.Append(keyParameters[i].TypeName);
source.Append(" ");
source.Append(keyParameters[i].Name);
}
}
private static IEnumerable<CachedParameter> GetCacheKeyParameters(CachedMethod method)
{
if (!string.IsNullOrWhiteSpace(method.Options.CacheKey))
{
return [];
}
return method.Parameters.Where(parameter =>
parameter.TypeName != "global::System.Threading.CancellationToken"
);
}
private static void AppendCacheKeyHelpers(StringBuilder source)
{
source.AppendLine(" private static string FormatCacheKeyValue<T>(T value)");
source.AppendLine(" {");
source.AppendLine(" if (value is null)");
source.AppendLine(" {");
source.AppendLine(" return string.Empty;");
source.AppendLine(" }");
source.AppendLine();
source.AppendLine(" if (value is string text)");
source.AppendLine(" {");
source.AppendLine(" return EscapeCacheKeySegment(text);");
source.AppendLine(" }");
source.AppendLine();
source.AppendLine(" if (value is global::System.Collections.IEnumerable values)");
source.AppendLine(" {");
source.AppendLine(" return FormatCacheKeyCollection(values);");
source.AppendLine(" }");
source.AppendLine();
source.AppendLine(" return FormatCacheKeySingleValue(value);");
source.AppendLine(" }");
source.AppendLine();
source.AppendLine(
" private static string FormatCacheKeyCollection(global::System.Collections.IEnumerable values)"
);
source.AppendLine(" {");
source.AppendLine(
" var segments = new global::System.Collections.Generic.List<string>();"
);
source.AppendLine(" foreach (var value in values)");
source.AppendLine(" {");
source.AppendLine(" var segment = FormatCacheKeySingleValue(value);");
source.AppendLine(" if (!string.IsNullOrEmpty(segment))");
source.AppendLine(" {");
source.AppendLine(" segments.Add(segment);");
source.AppendLine(" }");
source.AppendLine(" }");
source.AppendLine();
source.AppendLine(" segments.Sort(global::System.StringComparer.Ordinal);");
source.AppendLine(" return string.Join(\",\", segments);");
source.AppendLine(" }");
source.AppendLine();
source.AppendLine(
" private static string FormatCacheKeyCollection<T>(global::System.Collections.Generic.IEnumerable<T> values, global::System.Func<T, string> formatter)"
);
source.AppendLine(" {");
source.AppendLine(
" var segments = new global::System.Collections.Generic.List<string>();"
);
source.AppendLine(" foreach (var value in values)");
source.AppendLine(" {");
source.AppendLine(" var segment = formatter(value);");
source.AppendLine(" if (!string.IsNullOrEmpty(segment))");
source.AppendLine(" {");
source.AppendLine(" segments.Add(EscapeCacheKeySegment(segment));");
source.AppendLine(" }");
source.AppendLine(" }");
source.AppendLine();
source.AppendLine(" segments.Sort(global::System.StringComparer.Ordinal);");
source.AppendLine(" return string.Join(\",\", segments);");
source.AppendLine(" }");
source.AppendLine();
source.AppendLine(" private static string FormatCacheKeySingleValue(object? value)");
source.AppendLine(" {");
source.AppendLine(" if (value is null)");
source.AppendLine(" {");
source.AppendLine(" return string.Empty;");
source.AppendLine(" }");
source.AppendLine();
source.AppendLine(" return value switch");
source.AppendLine(" {");
source.AppendLine(
" global::System.DateTime dateTime => EscapeCacheKeySegment(dateTime.ToString(\"O\", global::System.Globalization.CultureInfo.InvariantCulture)),"
);
source.AppendLine(
" global::System.DateTimeOffset dateTimeOffset => EscapeCacheKeySegment(dateTimeOffset.ToString(\"O\", global::System.Globalization.CultureInfo.InvariantCulture)),"
);
source.AppendLine(
" global::System.IFormattable formattable => EscapeCacheKeySegment(formattable.ToString(null, global::System.Globalization.CultureInfo.InvariantCulture) ?? string.Empty),"
);
source.AppendLine(
" _ => EscapeCacheKeySegment(value.ToString() ?? string.Empty),"
);
source.AppendLine(" };");
source.AppendLine(" }");
source.AppendLine();
source.AppendLine(" private static string EscapeCacheKeySegment(string value)");
source.AppendLine(" {");
source.AppendLine(" return value");
source.AppendLine(" .Replace(\"%\", \"%25\")");
source.AppendLine(" .Replace(\":\", \"%3A\")");
source.AppendLine(" .Replace(\"&\", \"%26\")");
source.AppendLine(" .Replace(\"=\", \"%3D\")");
source.AppendLine(" .Replace(\",\", \"%2C\");");
source.AppendLine(" }");
source.AppendLine();
}
private static void AppendDistinctPrefixes(
StringBuilder source,
ServiceRegistration registration,
string defaultPrefix
)
{
var prefixes = new[] { defaultPrefix }
.Concat(
registration
.CachedMethods.Select(method => method.Options.Prefix)
.Where(static prefix => !string.IsNullOrWhiteSpace(prefix))
.Select(static prefix => prefix!)
)
.Distinct(StringComparer.Ordinal)
.ToList();
for (var i = 0; i < prefixes.Count; i++)
{
if (i > 0)
{
source.Append(", ");
}
source.Append(SymbolDisplay.ToCSharpStringLiteral(prefixes[i]));
}
}
private static IEnumerable<CachedMethod> GetDistinctCachedMethods(
ServiceRegistration registration
)
{
return registration
.CachedMethods.GroupBy(method => method.Name)
.Select(static group => group.First());
}
private static string GetCachePrefix(string fullyQualifiedTypeName)
{
return fullyQualifiedTypeName.Replace("global::", string.Empty);
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个 C# 源代码生成器,专门用于为服务类生成缓存相关的元数据代码。它是 Roslyn 源代码生成器的一部分,在编译时自动生成辅助代码。
主要功能
1. Generate 方法 - 核心生成逻辑
internal static string Generate(ServiceRegistration registration)
- 为给定的服务注册信息生成一个静态类
- 生成的类包含:
- 默认缓存前缀常量
- 每个缓存方法的前缀和方法标签常量
- 动态获取前缀/方法标签的方法
- 缓存键生成方法
- 辅助工具方法
2. AppendPrefixMethods - 生成前缀相关方法
生成三个方法:
GetPrefix(string methodName)- 根据方法名获取缓存前缀GetMethodTag(string methodName)- 获取方法标签(前缀:方法名)GetPrefixes()- 获取所有不重复的前缀数组
3. AppendCacheKeyMethod - 生成缓存键构建方法
为每个缓存方法生成如 BuildXxxCacheKey(...) 的方法:
public static string BuildGetUserCacheKey(int userId)
{
return "prefix:GetUser:" + userId;
}
4. AppendCacheKeyExpression - 生成缓存键表达式
根据不同情况生成缓存键:
- 自定义缓存键:使用
CacheKey属性指定的键 - 无参数方法:仅使用 "前缀:方法名"
- 有参数方法:拼接所有参数为 "前缀:方法名:参数1=值1&参数2=值2"
5. AppendCacheKeyHelpers - 生成辅助方法
生成多个辅助方法用于格式化缓存键值:
FormatCacheKeyValue<T>- 泛型格式化FormatCacheKeyCollection- 处理集合类型FormatCacheKeySingleValue- 处理单个值(DateTime、IFormattable等)EscapeCacheKeySegment- 转义特殊字符(%、:、&、=、,)
生成代码示例
假设有一个服务类 UserService,带有缓存方法 GetUser(int userId),生成的代码类似:
// <auto-generated />
#nullable enable
namespace MyApp.Services;
internal static class UserServiceCacheMetadata
{
public const string DefaultPrefix = "MyApp.Services.UserService";
public const string GetUserPrefix = "MyApp.Services.UserService";
public const string GetUserMethodTag = "MyApp.Services.UserService:GetUser";
public static string GetPrefix(string methodName) { ... }
public static string GetMethodTag(string methodName) { ... }
public static string[] GetPrefixes() { ... }
public static string BuildGetUserCacheKey(int userId)
{
return string.Concat("MyApp.Services.UserService:GetUser:", "userId=", FormatCacheKeyValue(userId));
}
private static string FormatCacheKeyValue<T>(T value) { ... }
private static string EscapeCacheKeySegment(string value) { ... }
}
设计亮点
- 编译时生成:避免运行时反射,提高性能
- 类型安全:生成强类型方法
- 智能处理:自动处理集合、日期时间等复杂类型
- URL安全:转义特殊字符确保缓存键有效
- 可扩展:支持自定义前缀和缓存键
AI 正在分析代码…
评论加载中...