如何通过反射获取ref struct类型的属性?
你遇到的问题是ref struct类型的核心限制导致的:ref struct明确禁止装箱(也就是无法转换为System.Object),而常规反射API(比如PropertyInfo.GetValue)的返回值是object,所以根本无法处理这类成员。就像你测试Match.ValueSpan时遇到的情况,这段常规反射代码会直接抛出异常:
Match match = new Regex("\\d+").Match("testing 123"); // 创建测试数据 PropertyInfo[] props = match.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); var values = props.Select(s => (s.Name, s.GetValue(match, null))).ToArray();
运行这段代码时,当调用PropertyInfo.GetValue获取ValueSpan属性,就会触发System.NotSupportedException——因为方法需要把ref struct装箱成object返回,这是被CLR严格禁止的。
你想要实现能处理ref struct的对象检查器,理想中希望有一个方法接收被检查对象、属性名和返回类型,直接拿到ref struct属性值,这个需求是合理的,但需要避开“装箱”这个核心矛盾。
你尝试的表达式树方案:可行且可靠
你已经找到一个正确的方向——用表达式树规避装箱问题。因为表达式树可以直接生成强类型的访问逻辑,不需要把ref struct装箱成object。你写的泛型方法完全有效:
public static T GetProperty<T>(object target, string name) where T : allows ref struct { var arg = Expression.Parameter(typeof(object)); var expr = Expression.Property(Expression.Convert(arg, target.GetType()), name); var compiled = Expression.Lambda<Func<object, T>>(expr, arg).Compile(); return compiled(target); }
调用时只需指定泛型参数为目标ref struct类型即可,完全不会触发装箱:
Match match = new Regex("\\d+").Match("testing 123"); // 创建测试数据 var result = GetProperty<ReadOnlySpan<char>>(match, "ValueSpan");
如何支持把返回类型作为参数传入?
如果不想用泛型参数,而是要把Type作为方法参数动态处理,这里的核心限制是:我们无法用统一的非泛型类型接收ref struct返回值(因为ref struct不能装箱、不能继承)。不过可以通过「回调委托」的方式间接实现:
我们可以动态生成表达式树,将属性值传入回调方法中处理,这样就不需要返回ref struct:
public static void GetProperty(object target, string propertyName, Type returnType, Action<object> resultCallback) { // 基于返回类型动态生成泛型版本的调用逻辑 var genericMethod = typeof(YourHelperClass).GetMethod(nameof(GetPropertyGeneric)) .MakeGenericMethod(returnType); genericMethod.Invoke(null, new object[] { target, propertyName, resultCallback }); } // 内部泛型辅助方法 private static void GetPropertyGeneric<T>(object target, string propertyName, Action<object> resultCallback) where T : allows ref struct { var arg = Expression.Parameter(typeof(object)); var propertyExpr = Expression.Property(Expression.Convert(arg, target.GetType()), propertyName); // 生成lambda:将属性值装箱后传入回调(注意:这里仅在回调内部临时装箱,若回调不需要装箱可直接处理) var lambda = Expression.Lambda<Action<object>>( Expression.Call( Expression.Constant(resultCallback), resultCallback.GetType().GetMethod("Invoke"), Expression.Convert(propertyExpr, typeof(object)) ), arg ); lambda.Compile()(target); }
调用方式如下:
Match match = new Regex("\\d+").Match("testing 123"); GetProperty(match, "ValueSpan", typeof(ReadOnlySpan<char>), spanObj => { // 这里需要显式转换回原类型 var span = (ReadOnlySpan<char>)spanObj; Console.WriteLine($"Span内容:{span.ToString()},长度:{span.Length}"); });
关于你提到的「仿造基类+指针复制」的hack思路
这个思路理论上有可行性,但强烈不推荐用于生产环境:
- ref struct的内存布局是值类型布局,你可以通过
Unsafe类或指针直接读取对象中属性的内存区域,但需要精准获取属性在对象内存中的偏移量(属性本质是方法,背后的字段可能是隐式或命名不公开的,很难稳定获取)。 - 即使成功复制内存,自定义结构体的内存布局必须和目标ref struct完全一致,一旦.NET版本更新或ref struct内部实现变化,代码就会崩溃,兼容性和稳定性完全无法保障。
综上,表达式树方案是当前最可靠、最符合CLR规范的解决方式。
备注:内容来源于stack exchange,提问作者user3670011

