从反射中获取对字段的引用

Get Reference to Field from Reflection

我正在将 OpenGL 游戏引擎作为一个充满激情的项目,我正在使用 UI 库 "Dear ImGUI" 来显示和调试类似于 Unity 检查器的值。我无法想出一种方法来获取对我正在尝试调试的字段的引用。

这是我目前得到的代码,但问题是它不是对实际字段的引用,它只是对局部变量(值)的引用,因此,它实际上并没有设置变量我在 GUI 中调试。据我所知,没有明确的方法来获取参考。

protected override void OnImGUIRender(FrameEventArgs e)
        {
            ImGui.PushFont(font);

            ImGui.ShowDemoWindow();

            //Scene Window
            {
                ImGui.Begin("Scene");

                ImGui.BeginTabBar("1");
                ImGui.BeginTabItem("Heirachy");

                if (ImGui.TreeNode("Scene"))
                {
                    foreach (var obj in (LayerStack.Layers.FirstOrDefault(x => x.GetType() == typeof(GameLayer)) as GameLayer).scene.GameObjects)
                    {
                        if (ImGui.Selectable(obj.Name))
                            selectedGameObject = obj;
                    }
                    ImGui.TreePop();
                }

                ImGui.EndTabItem();
                ImGui.EndTabBar();

                ImGui.Dummy(new System.Numerics.Vector2(0, 40));

                ImGui.BeginTabBar("Properties");

                ImGui.BeginTabItem("Properties");


                if (selectedGameObject != null)
                {
                    ImGui.Text(selectedGameObject.Name);

                    foreach (var c in selectedGameObject.components)
                    {
                        if (ImGui.TreeNode(c.GetType().Name))
                        {
                            var fields = c.GetType().GetFields();
                            foreach (var field in fields)
                            {
                                ImGui.DragFloat3(field.Name, field.refValue); <-- Focused line
                            }

                            ImGui.TreePop();
                        }
                    }
                }
                else
                    ImGui.Text("No Currently Selected Gameobject");

                ImGui.EndTabItem();

                ImGui.EndTabBar();

                ImGui.End();

                ImGui.Begin("Debug");

                ImGui.Text("Gameobjects: " + LayerStack.GameObjectCount);

                ImGui.End();
            }

            base.OnImGUIRender(e);
        }

有什么方法可以获取对在 foreach 中循环的实际字段的引用?在我的脑海里,我会想象它看起来像这样:

ImGui.DragFloat3(field.Name, field.Reference);

谢谢!

编辑:

我发现我的个人解决方案在下面的代码中,但非常感谢@pinkfloydx33 帮助我更好地理解问题并提供高质量的答案。

var fields = c.GetType().GetFields();
foreach (var field in fields)
{
    var value = (field.FieldType)field.GetValue(c);
    ImGui.DragFloat3(field.Name, field.refValue);
    field.SetValue(c, value);
}
您遇到的问题的

部分 是因为这些字段值是结构。您最终只会对它们的副本进行操作。但是我们可以通过构建一个委托来解决这个问题,该委托接受包含类型(您正在检查的字段的类型)的对象作为其唯一参数。该委托将依次调用您尝试调用的方法,并使用 ref.

在后台传递对象的字段

下面的解决方案假设您要调用的方法(ImGui.Drag3ImGui.Checkbox)始终有两个参数 -- string nameref T value。换句话说,对 int 字段进行操作的假设方法必须声明为 ImGui.DoSomethingToInt(string name, ref int value)

using System.Linq.Expressions;
using System.Reflection;
using System.Collection.Generic;

public static class ComponentHelpers
{
    // helper function to get the MethodInfo for the method we want to call
    private static MethodInfo GetStaticMethod(Expression<Action> expression)
    {
        if (expression.Body is MethodCallExpression body && body.Method.IsStatic)
            return body.Method;

        throw new InvalidOperationException("Expression must represent a static method");
    }

    // helper field we can use in calls to GetStaticMethod
    private static class Ref<T>
    {
        public static T Value;
    }

    // Define which method we want to call based on the field's type
    // each of these methods must take 2 parameters (string + ref T)
    private static readonly Dictionary<Type, MethodInfo> Methods = new Dictionary<Type, MethodInfo>
    {
        [typeof(Vector3)] = GetStaticMethod(() => ImGui.Drag3(default, ref Ref<Vector3>.Value)),
        [typeof(bool)] = GetStaticMethod(() => ImGui.Checkbox(default, ref Ref<bool>.Value))
    };

    // store the compiled delegates so that we only build/compile them once
    private static readonly Dictionary<FieldInfo, Action<Component>> Delegates = new Dictionary<FieldInfo, Action<Component>>();

    // this method will either build us a delegate, return one we've already built
    // or will return null if we have not defined a method for the specific type
    public static Action<Component> GetActionFor(FieldInfo field)
    {
        if (!Methods.TryGetValue(field.FieldType, out var method))
            return null;

        if (Delegates.TryGetValue(field, out var del))
            return del;

        // type the parameter as the base class Component
        var param = Expression.Parameter(typeof(Component), "x");

        var lambda = Expression.Lambda<Action<Component>>(
            Expression.Call(
                method,
                // Pass the field's name as the first parameter
                Expression.Constant(field.Name, typeof(string)),
                // pass the field as the second parameter
                Expression.Field(
                    // cast to the actual type so we can access fields of inherited types
                    Expression.Convert(param, field.ReflectedType),
                    field
                )
            ),
            param
        );

        return Delegates[field] = lambda.Compile();

    }
}

完成后,我们可以将您的主循环更新为如下所示:

var fields = c.GetType().GetFields();
foreach (var field in fields)
{
    var action = ComponentHelpers.GetActionFor(field);
    if (action == null) // no method defined
       continue;
    // invoke the function passing in the object itself
    action(c); 
}