using System.Collections;
using System.Collections.Concurrent;
using System.Linq.Expressions;
namespace Dpz.Core.Service;
[Obsolete("请使用 CacheAttribute 配合源生成缓存装饰器。")]
public static class GenerateCacheKey
{
private static readonly ConcurrentDictionary<
Type,
Func<object, IEnumerable<string>>
> GetterCache = new();
public static string Build(string cachePrefixKey, string method, object? parameters = null)
{
var baseKey = $"{cachePrefixKey}:{method}";
if (parameters == null)
{
return baseKey;
}
var getter = GetterCache.GetOrAdd(parameters.GetType(), BuildGetterExpression);
var queryParams = getter(parameters).Where(p => !string.IsNullOrEmpty(p));
return $"{baseKey}?{string.Join("&", queryParams)}";
}
private static Func<object, IEnumerable<string>> BuildGetterExpression(Type type)
{
var param = Expression.Parameter(typeof(object), "obj");
var typedParam = Expression.Convert(param, type);
var expression = type.GetProperties()
.Where(p => p.CanRead)
.Select(p => BuildPropertyExpression(typedParam, p));
var arrayExpr = Expression.NewArrayInit(typeof(string), expression);
var lambda = Expression.Lambda<Func<object, IEnumerable<string>>>(arrayExpr, param);
return lambda.Compile();
}
#region 反射缓存
private static readonly Lazy<MethodInfo> ConcatMethod = new(() =>
typeof(string).GetMethod(
nameof(string.Concat),
BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy,
Type.DefaultBinder,
[typeof(string), typeof(string)],
null
) ?? throw new NotSupportedException("string.Concat not found")
);
private static readonly Lazy<MethodInfo> JoinMethod = new(() =>
typeof(string).GetMethod(nameof(string.Join), [typeof(string), typeof(IEnumerable<string>)])
?? throw new NotSupportedException("string.Join not found")
);
private static readonly Lazy<MethodInfo> CastMethod = new(() =>
typeof(Enumerable)
.GetMethod(nameof(Enumerable.Cast), BindingFlags.Static | BindingFlags.Public)
?.MakeGenericMethod(typeof(object))
?? throw new NotSupportedException("Enumerable.Cast not found")
);
private static readonly Lazy<MethodInfo> SelectMethod = new(() =>
typeof(Enumerable)
.GetMethods()
.FirstOrDefault(x =>
x.Name == nameof(Enumerable.Select)
&& x.GetParameters().Length == 2
&& x.GetParameters()[0].ParameterType.GetGenericTypeDefinition()
== typeof(IEnumerable<>)
&& x.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>)
)
?.MakeGenericMethod(typeof(object), typeof(string))
?? throw new NotSupportedException("Enumerable.Select not found")
);
private static readonly Lazy<MethodInfo> WhereMethod = new(() =>
typeof(Enumerable)
.GetMethods()
.FirstOrDefault(x =>
x.Name == nameof(Enumerable.Where)
&& x.GetParameters().Length == 2
&& x.GetParameters()[0].ParameterType.GetGenericTypeDefinition()
== typeof(IEnumerable<>)
&& x.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>)
)
?.MakeGenericMethod(typeof(string))
?? throw new NotSupportedException("Enumerable.Where not found")
);
private static readonly Lazy<MethodInfo> OrderByMethod = new(() =>
typeof(Enumerable)
.GetMethods()
.FirstOrDefault(x =>
x.Name == nameof(Enumerable.OrderBy)
&& x.GetParameters().Length == 2
&& x.GetParameters()[0].ParameterType.GetGenericTypeDefinition()
== typeof(IEnumerable<>)
)
?.MakeGenericMethod(typeof(string), typeof(string))
?? throw new NotSupportedException("Enumerable.OrderBy not found")
);
private static readonly Lazy<MethodInfo> IsNullOrEmptyMethod = new(() =>
typeof(string).GetMethod(nameof(string.IsNullOrEmpty), [typeof(string)])
?? throw new InvalidOperationException("string.IsNullOrEmpty method not found")
);
private static readonly Lazy<MethodInfo> ToStringMethod = new(() =>
typeof(object).GetMethod(nameof(ToString), [])
?? throw new InvalidOperationException("object.ToString method not found")
);
private static readonly Lazy<MethodInfo> ReplaceMethod = new(() =>
typeof(string).GetMethod(nameof(string.Replace), [typeof(string), typeof(string)])
?? throw new InvalidOperationException("string.Replace method not found")
);
#endregion
private static readonly ConstantExpression EmptyStringExpr = Expression.Constant(string.Empty);
private static readonly ConstantExpression CommaExpr = Expression.Constant(",");
private static readonly ConstantExpression EncodedCommaExpr = Expression.Constant("%2C");
/// <summary>
/// 构建属性表达式用于生成缓存键
/// </summary>
/// <param name="paramExpression">参数表达式</param>
/// <param name="property">属性信息</param>
/// <returns>方法调用表达式</returns>
private static MethodCallExpression BuildPropertyExpression(
UnaryExpression paramExpression,
PropertyInfo property
)
{
var propExpr = Expression.Property(paramExpression, property);
var nameExpr = Expression.Constant($"{property.Name}=");
// 检查属性是否派生自IEnumerable(排除字符串类型)
if (
typeof(IEnumerable).IsAssignableFrom(property.PropertyType)
&& property.PropertyType != typeof(string)
)
{
// 将属性转换为IEnumerable
var castToObject = Expression.Call(CastMethod.Value, propExpr);
// 创建ToString的lambda表达式,并在此处进行转义
var itemParam = Expression.Parameter(typeof(object), "item");
var nullCheck = Expression.Condition(
Expression.Equal(itemParam, Expression.Constant(null)),
EmptyStringExpr,
Expression.Call(
Expression.Call(itemParam, ToStringMethod.Value),
ReplaceMethod.Value,
CommaExpr,
EncodedCommaExpr
)
);
var toStringLambda = Expression.Lambda<Func<object, string>>(nullCheck, itemParam);
// 对集合中的每个元素调用ToString并转义
var selectCall = Expression.Call(SelectMethod.Value, castToObject, toStringLambda);
// 创建 x => !string.IsNullOrEmpty(x) 表达式
var whereParam = Expression.Parameter(typeof(string), "x");
var whereBody = Expression.Not(Expression.Call(IsNullOrEmptyMethod.Value, whereParam));
var whereLambda = Expression.Lambda<Func<string, bool>>(whereBody, whereParam);
// 添加 Where 调用
var whereCall = Expression.Call(WhereMethod.Value, selectCall, whereLambda);
// 添加 OrderBy 调用,按字符串本身排序
var orderByParam = Expression.Parameter(typeof(string), "s");
var orderByLambda = Expression.Lambda<Func<string, string>>(orderByParam, orderByParam);
var orderByCall = Expression.Call(OrderByMethod.Value, whereCall, orderByLambda);
// 将集合连接为字符串
var joinedString = Expression.Call(JoinMethod.Value, CommaExpr, orderByCall);
var collectionNullCheck = Expression.Condition(
Expression.Equal(propExpr, Expression.Constant(null)),
EmptyStringExpr,
joinedString
);
var concatExpr = Expression.Call(ConcatMethod.Value, nameExpr, collectionNullCheck);
return concatExpr;
}
else
{
// 根据属性类型处理null检查
Expression valueExpr;
if (IsNullableType(property.PropertyType))
{
// 可空类型
valueExpr = Expression.Condition(
Expression.Equal(propExpr, Expression.Constant(null)),
EmptyStringExpr,
Expression.Call(propExpr, ToStringMethod.Value)
);
}
else if (property.PropertyType.IsValueType)
{
// 值类型,直接调用ToString
valueExpr = Expression.Call(propExpr, ToStringMethod.Value);
}
else
{
// 引用类型
valueExpr = Expression.Condition(
Expression.Equal(propExpr, Expression.Constant(null)),
EmptyStringExpr,
Expression.Call(propExpr, ToStringMethod.Value)
);
}
var concatExpr = Expression.Call(ConcatMethod.Value, nameExpr, valueExpr);
return concatExpr;
}
}
/// <summary>
/// 检查类型是否为可空值类型
/// </summary>
private static bool IsNullableType(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个缓存键生成器的工具类,用于根据方法名和参数对象动态生成缓存键字符串。该类已被标记为过时(Obsolete),建议使用新的 CacheAttribute 配合源生成器。
核心功能
1. 主入口方法 Build
public static string Build(string cachePrefixKey, string method, object? parameters = null)
- 生成格式:
{前缀}:{方法名}?参数1=值1&参数2=值2 - 如果参数为 null,返回
{前缀}:{方法名} - 使用缓存的 getter 函数提取参数对象的所有属性值
2. 表达式树缓存机制
private static readonly ConcurrentDictionary<Type, Func<object, IEnumerable<string>>> GetterCache
- 使用
ConcurrentDictionary缓存每种参数类型的属性读取委托 - 首次遇到某类型时,通过
BuildGetterExpression动态编译表达式树生成高性能 getter
3. 动态编译属性读取器
BuildGetterExpression 方法:
- 使用 Expression API 为参数类型的所有可读属性生成读取逻辑
- 编译为
Func<object, IEnumerable<string>>委托(避免反射性能损耗) - 返回格式为
属性名=值的字符串数组
关键技术点
集合属性处理(BuildPropertyExpression 方法)
当属性是 IEnumerable 类型(非 string)时:
- 类型转换:Cast 为
IEnumerable<object> - 逗号转义:将元素的逗号替换为
%2C(避免破坏缓存键格式) - 过滤空值:Where 过滤空字符串
- 排序:OrderBy 确保集合元素顺序一致(重要:避免
[1,2]和[2,1]生成不同缓存键) - 连接:用逗号连接为单个字符串
普通属性处理
- 值类型:直接调用
ToString() - 可空类型/引用类型:空值检查,null 返回空字符串
反射缓存优化
使用 Lazy<MethodInfo> 延迟缓存常用方法:
string.Concat/string.JoinEnumerable.Cast/Select/Where/OrderBystring.IsNullOrEmpty/Replace
示例
假设调用:
var param = new { UserId = 123, Tags = new[] { "A", "B" }, Name = (string?)null };
var key = GenerateCacheKey.Build("user", "GetProfile", param);
生成结果:
user:GetProfile?UserId=123&Tags=A,B&Name=
设计优点
- 高性能:表达式树编译 + 类型级缓存,避免反射开销
- 一致性:集合排序确保参数顺序不影响缓存键
- 安全性:逗号转义防止键格式混淆
注意事项
- 已过时,建议迁移到源生成方案
- 集合元素需支持
ToString() - 不支持嵌套对象(仅处理顶层属性)
AI 正在分析代码…
评论加载中...