Get Reference to Field from Reflection [duplicate]

三世轮回 提交于 2020-06-09 05:38:04

问题


I'm working on an OpenGL game engine as a passion project and im using the UI library "Dear ImGUI" to display and debug values similar to Unity's inspector. I'm having trouble thinking of a way to get a reference to the field I'm trying to debug.

Here is the code i have got at the moment but the problem is that its not a reference to the actual field, its just a reference to a local variable (value) and as such, it doesnt actually set the variable that I debug in the GUI. From what i've been able to see, there is no clean cut way to get the reference.

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

Is there any way I can get a reference to the actual field that is being looped over in the foreach? In my head I would imagine it looking something like this:

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

Thanks!

Edit:

I found my personal solution to be in the code below but massive thanks to @pinkfloydx33 for helping me to understand the problem better and provide a high quality answer.

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

回答1:


Part of the problem you are experiencing is due to the fact those field values are structs. You only end up operating on copies of them. But we can get around this by building a delegate that accepts as its only parameter an object of the containing type (the type who's fields you are inspecting). This delegate will in turn call the method you are trying to invoke, passing the object's field under the hood with ref.

This solution below assumes that the methods you want to invoke (ImGui.Drag3, ImGui.Checkbox) always have two parameters -- string name and ref T value. In other words, a hypothetical method that operated on int fields would have to be declared as 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();

    }
}

Once we've done that, we can update your main loop to look like the following:

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


来源:https://stackoverflow.com/questions/62234090/get-reference-to-field-from-reflection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!