ChouBox/Utile/HuanMeng.DotNetCore/Utility/ObjectExtensions.cs
2025-04-23 19:20:23 +08:00

169 lines
7.1 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using HuanMeng.DotNetCore.AttributeExtend;
using System.Collections;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
namespace HuanMeng.DotNetCore.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;
}
}
}