PropertyExt.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq.Expressions;
  4. using System.Reflection;
  5. namespace StandardLibrary
  6. {
  7. public static class PropertyPathExtractor
  8. {
  9. /// <summary>
  10. /// 获得类型下的所有基本类型的访问路径
  11. /// </summary>
  12. /// <param name="type"></param>
  13. /// <returns></returns>
  14. public static List<string> GetLeafPropertyPaths(Type type)
  15. {
  16. if (type == null) return new List<string>();
  17. var visited = new HashSet<Type>();
  18. var paths = new List<string>();
  19. Traverse(type, "", paths, visited);
  20. return paths;
  21. }
  22. private static void Traverse(Type type, string prefix, List<string> paths, HashSet<Type> visited)
  23. {
  24. if (type == null || IsSimpleType(type)) return;
  25. if (!visited.Add(type)) // 防止循环引用
  26. return;
  27. var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
  28. foreach (var prop in properties)
  29. {
  30. if (prop.GetIndexParameters().Length > 0) continue; // 跳过索引器
  31. string currentPath = string.IsNullOrEmpty(prefix)
  32. ? prop.Name
  33. : $"{prefix}.{prop.Name}";
  34. if (IsSimpleType(prop.PropertyType))
  35. {
  36. // 只有叶子节点(基本类型)才加入路径
  37. paths.Add(currentPath);
  38. }
  39. else
  40. {
  41. // 非简单类型:继续递归,但不记录 currentPath 本身
  42. Traverse(prop.PropertyType, currentPath, paths, visited);
  43. }
  44. }
  45. visited.Remove(type); // 回溯(可选)
  46. }
  47. private static bool IsSimpleType(Type type)
  48. {
  49. if (type == null) return true;
  50. if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal))
  51. return true;
  52. if (type.IsEnum)
  53. return true;
  54. if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
  55. return IsSimpleType(type.GetGenericArguments()[0]);
  56. return false;
  57. }
  58. }
  59. public static class PropertyPathHelper
  60. {
  61. //PropertyPathHelper.GetPath(()=>a.b.c),输出a.b.c
  62. public static string GetPath<T>(Expression<Func<T>> expression)
  63. {
  64. return GetMemberPath(expression.Body);
  65. }
  66. private static string GetMemberPath(Expression expression)
  67. {
  68. switch (expression)
  69. {
  70. case MemberExpression memberExpr:
  71. if (memberExpr.Expression is ParameterExpression)
  72. return memberExpr.Member.Name; // 顶层变量(实际不会出现,因为是 Func<T>)
  73. string parentPath = GetMemberPath(memberExpr.Expression);
  74. return parentPath + "." + memberExpr.Member.Name;
  75. case ConstantExpression constExpr:
  76. // 静态成员或 null 常量,无法构成路径
  77. //throw new ArgumentException("表达式不能包含常量或静态成员");
  78. return constExpr.Value.ToString();
  79. default:
  80. throw new ArgumentException($"不支持的表达式类型: {expression.GetType().Name}");
  81. }
  82. }
  83. }
  84. public static class PropertyFinder
  85. {
  86. /// <summary>
  87. /// 查找指定对象的(非静态)属性中,值为 T 类型(或其派生类)的所有实例。
  88. /// 不递归,仅检查当前对象的属性。
  89. /// </summary>
  90. public static List<T> FindPropertiesOfType<T>(object obj) where T : class
  91. {
  92. if (obj == null)
  93. return new List<T>();
  94. var result = new List<T>();
  95. var type = obj.GetType();
  96. // 获取所有实例属性(包括私有属性,如不需要可调整 BindingFlags)
  97. var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  98. foreach (var prop in properties)
  99. {
  100. if (!prop.CanRead)
  101. continue;
  102. // 跳过索引器(如 this[int i])
  103. if (prop.GetIndexParameters().Length > 0)
  104. continue;
  105. try
  106. {
  107. var value = prop.GetValue(obj) as T;
  108. if (value != null)
  109. {
  110. result.Add(value);
  111. }
  112. }
  113. catch (Exception)
  114. {
  115. // 忽略无法读取的属性(如抛异常的 lazy 属性等)
  116. }
  117. }
  118. return result;
  119. }
  120. }
  121. public static class ObjectPropertySetter
  122. {
  123. /// <summary>
  124. /// Sets the value of a nested property on the specified target object, navigating through the property path.
  125. /// </summary>
  126. /// <remarks>This method supports setting values on nested properties by navigating through the
  127. /// specified property path. If any intermediate property in the path is <see langword="null"/>, an instance of
  128. /// the corresponding type will be created, provided the type has a parameterless constructor. The final
  129. /// property in the path will be assigned the specified value, which will be converted to the property's type if
  130. /// necessary.</remarks>
  131. /// <param name="target">The object on which the property value will be set. Cannot be <see langword="null"/>.</param>
  132. /// <param name="propertyPath">The dot-separated path of the property to set. Each segment represents a property name in the hierarchy.</param>
  133. /// <param name="value">The value to assign to the specified property. The value will be converted to the property's type if
  134. /// necessary.</param>
  135. /// <exception cref="ArgumentException">Thrown if a property in the path does not exist on the corresponding object type.</exception>
  136. /// <exception cref="InvalidOperationException">Thrown if an intermediate property in the path is <see langword="null"/> and cannot be instantiated due to
  137. /// the absence of a parameterless constructor.</exception>
  138. public static void SetPropertyValue(object target, string propertyPath, object value)
  139. {
  140. var parts = propertyPath.Split('.');
  141. object current = target;
  142. for (int i = 0; i < parts.Length; i++)
  143. {
  144. var propName = parts[i];
  145. var prop = current.GetType().GetProperty(propName,
  146. BindingFlags.Public | BindingFlags.Instance);
  147. if (prop == null)
  148. throw new ArgumentException($"Property '{propName}' not found on type {current.GetType()}.");
  149. if (i == parts.Length - 1)
  150. {
  151. // 最后一级:赋值
  152. var convertedValue = ConvertValue(value, prop.PropertyType);
  153. prop.SetValue(current, convertedValue);
  154. }
  155. else
  156. {
  157. // 中间级:获取子对象,若为 null 则创建(仅支持无参构造)
  158. var subObject = prop.GetValue(current);
  159. if (subObject == null)
  160. {
  161. var subType = prop.PropertyType;
  162. if (subType.GetConstructor(Type.EmptyTypes) == null)
  163. throw new InvalidOperationException($"Cannot create instance of {subType} (no parameterless constructor).");
  164. subObject = Activator.CreateInstance(subType);
  165. prop.SetValue(current, subObject);
  166. }
  167. current = subObject;
  168. }
  169. }
  170. }
  171. /// <summary>
  172. /// Converts the specified value to the specified target type.
  173. /// </summary>
  174. /// <remarks>This method supports conversion between compatible types, including basic types and
  175. /// enums. If the target type is an enumeration, the method attempts to convert the value to the corresponding
  176. /// enum value.</remarks>
  177. /// <param name="value">The value to convert. Can be null.</param>
  178. /// <param name="targetType">The type to which the value should be converted. Must not be null.</param>
  179. /// <returns>The converted value as an object of the specified target type, or null if <paramref name="value"/> is null.</returns>
  180. /// <exception cref="InvalidOperationException">Thrown if the conversion cannot be performed, such as when the value is incompatible with the target type.</exception>
  181. private static object ConvertValue(object value, Type targetType)
  182. {
  183. if (value == null)
  184. {
  185. // 返回 targetType 的 default 值,而不是简单返回 null
  186. return GetDefaultValue(targetType);
  187. }
  188. var sourceType = value.GetType();
  189. if (targetType.IsAssignableFrom(sourceType))
  190. return value;
  191. // 尝试类型转换(支持基本类型)
  192. try
  193. {
  194. if (targetType.IsEnum)
  195. return Enum.ToObject(targetType, value);
  196. return Convert.ChangeType(value, targetType);
  197. }
  198. catch (Exception ex)
  199. {
  200. throw new InvalidOperationException($"Cannot convert {value} ({sourceType}) to {targetType}", ex);
  201. }
  202. }
  203. public static object GetDefaultValue(Type type)
  204. {
  205. if (type == null) throw new ArgumentNullException(nameof(type));
  206. // 引用类型(包括 string)或可空值类型(如 int?)的 default 是 null
  207. if (!type.IsValueType)
  208. return null;
  209. // 值类型:使用 Activator.CreateInstance 返回其零初始化值
  210. return Activator.CreateInstance(type);
  211. }
  212. }
  213. }