从反射中获取对领域的参考 [英] Get Reference to Field from Reflection

查看:81
本文介绍了从反射中获取对领域的参考的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在作为一个激情项目开发OpenGL游戏引擎,并使用UI库"Dear ImGUI"来显示和调试类似于Unity检查器的值.我在想办法获取对我要调试的字段的引用时遇到麻烦.

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.

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

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

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

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

谢谢!

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

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

您遇到的问题的

推荐答案

部分是由于这些字段值是结构这一事实.您最终只能对它们的副本进行操作.但是我们可以通过构建一个委托来解决此问题,该委托将包含类型(您要检查的字段的类型)的对象作为唯一参数.该委托将依次调用您尝试调用的方法,并使用 ref 在幕后传递对象的字段.

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.

下面的此解决方案假定您要调用的方法( ImGui.Drag3 ImGui.Checkbox )始终具有两个参数-字符串名称 ref T值.换句话说,对 int 字段进行操作的假设方法必须声明为 ImGui.DoSomethingToInt(字符串名称,ref int值)

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

这篇关于从反射中获取对领域的参考的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆