从反射中获取对字段的引用
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.Drag3
、ImGui.Checkbox
)始终有两个参数 -- string name
和 ref 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);
}
我正在将 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.Drag3
、ImGui.Checkbox
)始终有两个参数 -- string name
和 ref 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);
}