有没有办法通过反射获取一个ref struct的属性?

编程语言 2026-07-12

System.Text.RegularExpressions.Match 为例。它有一个属于rex struct的成员 ReadOnlySpan<char> ValueSpan

常用的反射方式行不通,因为ref struct的成员不能装箱(不能变成 System.Object)。

Match match = new Regex("\\d+").Match("testing 123"); // Create test data
PropertyInfo[] props = match.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
var values = props.Select(s => (s.Name, s.GetValue(match, null))).ToArray();

如果你尝试运行上面的代码,程序会以 System.NotSupportedException 退出。当对 Match.ValueSpan 的PropertyInfo调用 PropertyInfo.GetValue(object? obj, object?[]? index) 时,异常就会被触发。

我想构建的是一个对象检查器,能够查看对象的字段和属性。理想情况下,会有一种调用方法,它接受被检查对象(在本例中是 Match)、属性名的字符串(在本例中是 "ValueSpan")以及返回类型(在本例中是 typeof(ReadOnlySpan<char>))的方法。

像这样的:

Match match = new Regex("\\d+").Match("testing 123"); // Create test data
var result = GetProperty(match, "ValueSpan", typeof(ReadOnlySpan<char>))

不过问题在于,var result 的类型会解析成什么?它不能是object,因为 match.ValueSpan 是一个ref struct。有没有解决这个问题的方法?

我已经尝试过多种解决方式,最有前景的是使用表达式树。下面的方法将被检查的对象和属性名作为参数,但需要知道返回类型:

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);
}

然后可以这样调用:

Match match = new Regex("\\d+").Match("testing 123"); // Create test data
var result = GetProperty<ReadOnlySpan<char>>(match, "ValueSpan")

进一步完善这个解决方案时,我也想把返回类型作为一个函数参数来声明(而不是泛型参数)。

甚至连hacky的做法也欢迎。我在想,是否可以创建一个伪的“基类型”结构体,并通过指针拷贝数据(包括RuntimeHandle)。总之,我们需要创建一种可以充当任何类型(包括ref struct)的替身的类型。对此有何想法?

解决方案

may 不一定完全符合你的用例,但我猜它会。与其 返回 值,不如在 ref struct 规则的精神之内,通过 消费 值而不让它离开栈来实现。你可以用一个接受该值并将其格式化为常规类型(例如字符串)的委托来完成。你已经使用的表达式树方法随后就可以调用该委托,返回一个字符串。

All of this can be wrapped in a class which you can use to register formatters of different types. Here's a simple example, with a bunch of error checking and other things omitted (for example, it looks for a formatter of the exact type instead of using inheritance). It should be enough to get you going though.

internal sealed class PropertyFormatter
{
    private readonly Dictionary<Type, Delegate> formatters = new();

    public void RegisterFormatter<T>(Func<T, string> formatter) where T : allows ref struct
    {
        formatters.Add(typeof(T), formatter);
    }

    public string FormatProperty(object target, string name)
    {
        var property = target.GetType().GetProperty(name);
        if (property is null)
        {
            throw new ArgumentException("No such property");
        }
        var formatter = formatters[property.PropertyType];
        var arg = Expression.Parameter(typeof(object));
        var expr = Expression.Property(Expression.Convert(arg, target.GetType()), name);
        var call = Expression.Call(Expression.Constant(formatter.Target), formatter.Method, expr);
        var compiled = Expression.Lambda<Func<object, string>>(call, arg).Compile();
        return compiled(target);
    }
}

你可以这样使用它:

using System.Text.RegularExpressions;

// Create a formatter which can be used multiple times.
var propertyFormatter = new PropertyFormatter();
// You'd normally register a bunch of formatters here.
propertyFormatter.RegisterFormatter<ReadOnlySpan<char>>(span => new string(span));

Match match = new Regex("\\d+").Match("testing 123"); // Create test data
var result = propertyFormatter.FormatProperty(match, "ValueSpan");
Console.WriteLine(result); // Prints 123
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章