171 lines
6.7 KiB
C#
171 lines
6.7 KiB
C#
|
||
|
||
using LiveForum.Code.AttributeExtend;
|
||
|
||
using System.Collections;
|
||
using System.Collections.Concurrent;
|
||
using System.Linq.Expressions;
|
||
using System.Reflection;
|
||
|
||
namespace LiveForum.Code.Utility;
|
||
|
||
|
||
/// <summary>
|
||
/// object转数据字典
|
||
/// </summary>
|
||
public static class ObjectExtensions
|
||
{
|
||
/// <summary>
|
||
/// 用于存储每种类型的属性访问器数组的线程安全缓存。
|
||
/// </summary>
|
||
private static readonly ConcurrentDictionary<Type, PropertyAccessor[]> PropertyCache = new();
|
||
|
||
/// <summary>
|
||
/// 缓存每个属性是否具有 ImagesAttribute 特性。
|
||
/// </summary>
|
||
public static readonly ConcurrentDictionary<PropertyInfo, bool> _PropertyCache = new();
|
||
|
||
/// <summary>
|
||
/// 判断对象是否为原始类型或字符串类型。
|
||
/// </summary>
|
||
/// <param name="obj">要检查的对象。</param>
|
||
/// <returns>如果对象是原始类型或字符串,返回 true;否则返回 false。</returns>
|
||
public static bool IsPrimitiveType(object obj) =>
|
||
obj is not null && (obj.GetType().IsPrimitive || obj is string or ValueType);
|
||
|
||
/// <summary>
|
||
/// 判断对象是否为集合类型(不包括字符串)。
|
||
/// </summary>
|
||
/// <param name="obj">要检查的对象。</param>
|
||
/// <returns>如果对象是集合类型(但不是字符串),返回 true;否则返回 false。</returns>
|
||
public static bool IsCollectionType(object obj) => obj is IEnumerable && obj is not string;
|
||
|
||
/// <summary>
|
||
/// 根据对象类型,将对象转换为字典或列表格式,支持可选的路径前缀。
|
||
/// </summary>
|
||
/// <param name="obj">要转换的对象。</param>
|
||
/// <param name="prefix">属性路径的可选前缀。</param>
|
||
/// <returns>对象的字典或列表表示形式。</returns>
|
||
public static object ToDictionaryOrList(this object obj, bool isEnumerable, string prefix = "", Func<int, string>? imageFunc = null, Func<string, string, string, bool, string>? languageFunc = null)
|
||
{
|
||
if (obj == null) return null;
|
||
|
||
return obj switch
|
||
{
|
||
_ when IsPrimitiveType(obj) => obj,
|
||
IEnumerable enumerable => TransformCollection(enumerable, prefix, imageFunc, languageFunc),
|
||
_ => TransformObject(obj, isEnumerable, prefix, imageFunc, languageFunc)
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将集合对象转换为包含转换项的列表,每个项保留其路径前缀。
|
||
/// </summary>
|
||
/// <param name="enumerable">要转换的集合。</param>
|
||
/// <param name="prefix">集合中每个属性路径的前缀。</param>
|
||
/// <returns>转换后的项列表。</returns>
|
||
private static List<object> TransformCollection(IEnumerable enumerable, string prefix = "", Func<int, string>? imageFunc = null, Func<string, string, string, bool, string>? languageFunc = null)
|
||
{
|
||
var list = new List<object>(enumerable is ICollection collection ? collection.Count : 10);
|
||
int index = 0;
|
||
foreach (var item in enumerable)
|
||
{
|
||
list.Add(ToDictionaryOrList(item, true, $"{prefix}.[{index}]", imageFunc, languageFunc)); // 为集合中每个项添加路径
|
||
index++;
|
||
}
|
||
return list;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将对象的属性转换为带有路径前缀的字典,并应用前缀规则。
|
||
/// </summary>
|
||
/// <param name="obj">要转换的对象。</param>
|
||
/// <param name="prefix">每个属性路径的前缀。</param>
|
||
/// <returns>包含属性名和属性值的字典。</returns>
|
||
private static Dictionary<string, object> TransformObject(object obj, bool isEnumerable, string prefix = "", Func<int, string>? imageFunc = null, Func<string, string, string, bool, string>? languageFunc = null)
|
||
{
|
||
if (obj == null)
|
||
{
|
||
return null;
|
||
}
|
||
var type = obj.GetType();
|
||
var accessors = PropertyCache.GetOrAdd(type, CreatePropertyAccessors);
|
||
var keyValuePairs = new Dictionary<string, object>(accessors.Length);
|
||
|
||
foreach (var accessor in accessors)
|
||
{
|
||
var propertyPath = $"{prefix}.{accessor.PropertyName}"; // 构建完整的属性路径
|
||
|
||
// 使用访问器获取属性值
|
||
var propertyValue = accessor.Getter(obj);
|
||
|
||
// 如果属性是字符串,在其值前添加 "test"
|
||
if (propertyValue is string stringValue)
|
||
{
|
||
if (stringValue == null)
|
||
{
|
||
keyValuePairs[accessor.PropertyName] = "";
|
||
continue;
|
||
}
|
||
if (!string.IsNullOrEmpty(stringValue) && languageFunc != null)
|
||
{
|
||
stringValue = languageFunc?.Invoke(stringValue, propertyPath, accessor.PropertyName, isEnumerable) ?? "";
|
||
}
|
||
keyValuePairs[accessor.PropertyName] = stringValue;
|
||
continue;
|
||
}
|
||
|
||
// 如果属性具有 ImagesAttribute,在其值前添加 "image"
|
||
// 否则,如果是集合类型,则递归转换
|
||
keyValuePairs[accessor.PropertyName] = accessor.HasImagesAttribute
|
||
? imageFunc?.Invoke((int)propertyValue) ?? ""
|
||
: ToDictionaryOrList(propertyValue, isEnumerable, propertyPath, imageFunc, languageFunc); // IsCollectionType(propertyValue) ?: propertyValue;
|
||
}
|
||
|
||
return keyValuePairs;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 为给定类型创建属性访问器数组。
|
||
/// </summary>
|
||
/// <param name="type">要创建属性访问器的类型。</param>
|
||
/// <returns>属性访问器数组。</returns>
|
||
private static PropertyAccessor[] CreatePropertyAccessors(Type type)
|
||
{
|
||
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||
return properties.Select(property =>
|
||
{
|
||
// 创建用于访问属性值的委托
|
||
var getter = CreatePropertyGetter(type, property);
|
||
// 检查属性是否具有 ImagesAttribute,并将结果存储在缓存中
|
||
var isImagesAttribute = _PropertyCache.GetOrAdd(property, p => p.GetCustomAttribute<ImagesAttribute>() != null);
|
||
return new PropertyAccessor(property.Name, getter, isImagesAttribute);
|
||
}).ToArray();
|
||
}
|
||
|
||
|
||
|
||
private static Func<object, object> CreatePropertyGetter(Type type, PropertyInfo property)
|
||
{
|
||
var parameter = Expression.Parameter(typeof(object), "obj");
|
||
var castParameter = Expression.Convert(parameter, type);
|
||
var propertyAccess = Expression.Property(castParameter, property);
|
||
var convertPropertyAccess = Expression.Convert(propertyAccess, typeof(object));
|
||
return Expression.Lambda<Func<object, object>>(convertPropertyAccess, parameter).Compile();
|
||
}
|
||
|
||
private class PropertyAccessor
|
||
{
|
||
public string PropertyName { get; }
|
||
public Func<object, object> Getter { get; }
|
||
public bool HasImagesAttribute { get; }
|
||
|
||
public PropertyAccessor(string propertyName, Func<object, object> getter, bool hasImagesAttribute)
|
||
{
|
||
PropertyName = propertyName;
|
||
Getter = getter;
|
||
HasImagesAttribute = hasImagesAttribute;
|
||
}
|
||
}
|
||
}
|