You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过反射获取ref struct类型的属性?

如何通过反射获取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 18:09:35