在运行时获取未知 class 字段的输入
Get input for unknown class fields during runtime
过去几个月我一直在学习 C#,我们接到了一个小项目的任务,该项目有以下特定要求:
我们需要以这种方式设计项目的 UI 部分,我们可以找出一些随机的 class 字段,然后从用户那里获取输入以初始化这些字段。
例如,在一个 运行 程序中,我们有这个 class A,它有两个整数字段。
在UI部分,我们需要弄清楚classA有两个整数,那么我们需要从用户那里接收2个整数并传回进行初始化。
另一种情况:
我们有 class B,它有一个布尔值和一个枚举字段,我们需要做同样的事情。
我一直倾向于使用反射来收集 运行 时间内所需的数据,但我在弄清楚如何实际接收用户所需的输入时遇到了很多问题。
另一个障碍是我们被告知反思不会帮助我们 and/or 不需要完成这项任务。
我对这门语言相当缺乏经验,所以我想可能还有一些我不知道的其他方法来实现它,但从概念上讲 - 我真的无法理解它是如何实现的。
下面是一些基本的 classes 层次结构,以提供更好的示例:
public abstract class Shape
{
private Point location;
private string color;
}
public abstract class NonCircular : Shape
{
private int edgesNumber;
}
public class Circle : Shape
{
private float radius;
private float diameter;
}
public class Triangle : NonCircular
{
public enum AngleType { Right, Acute, Obtuse }
public enum EdgePropery { Equilateral, Isosceles, Scalene }
private AngleType angleType;
private EdgePropery edgePropery;
private float angle1, angle2, angle3;
}
继续这个例子 - 假设 class 'Triangle' 稍后会在项目完成后添加到解决方案中。
我们先用一些大家共享的基本字段构造抽象class'Shape',然后根据需求,UI需要接收字段:
angleType, edgePropery, and angles1-3
并将值传回项目的逻辑部分,以便正确初始化它们。
您可以向基础 class 添加一个方法,该方法 return 需要初始化的字段信息。然后每个形状都会覆盖此方法并 return 其各自的字段。
在 UI 了解必填字段并让用户输入值后,需要第二种方法来实际初始化字段。
那么主要的问题是子class 不知道其基class 中的任何私有字段并且无法初始化它们。这可以通过在每次覆盖中始终调用 GetFieldInfo()
和 InitFields()
的 base
实现来解决。
为确保正确“使用”所提供值的集合,您可以使用堆栈。每个基 class 将从集合中 Pop()
初始化自身所需的所有值,然后将其余值留给派生的 classes.
从基础 classes 和派生 classes 与 GetFieldInfo()
.
累加所有字段时使用相同的原理
当然,所有这一切只有在 UI 正确创建值的 Stack
时才有效,即它必须遵守顺序和通过 GetFieldInfo()
获得的 Type
s .
public abstract class Shape {
private Point location;
private string color;
public virtual IEnumerable<Type> GetFieldInfo() {
yield return location.GetType();
yield return color.GetType();
}
public virtual void InitFields(Stack<object> values) {
location = (Point)values.Pop();
color = (string)values.Pop();
}
}
public abstract class NonCircular : Shape {
private int edgesNumber;
public override IEnumerable<Type> GetFieldInfo() => base
.GetFieldInfo()
.Append(edgesNumber.GetType());
public override void InitFields(Stack<object> values) {
base.InitFields(values);
edgesNumber = (int)values.Pop();
}
}
public class Circle : Shape {
private float radius;
private float diameter;
public override IEnumerable<Type> GetFieldInfo() => base
.GetFieldInfo()
.Append(radius.GetType())
.Append(diameter.GetType());
public override void InitFields(Stack<object> values) {
base.InitFields(values);
radius = (float)values.Pop();
diameter = (float)values.Pop();
}
}
public class Triangle : NonCircular {
public enum AngleType { Right, Acute, Obtuse }
public enum EdgePropery { Equilateral, Isosceles, Scalene }
private AngleType angleType;
private EdgePropery edgePropery;
private float angle1, angle2, angle3;
public override IEnumerable<Type> GetFieldInfo() => base
.GetFieldInfo()
.Append(angleType.GetType())
.Append(edgePropery.GetType())
.Append(angle1.GetType())
.Append(angle2.GetType())
.Append(angle3.GetType());
public override void InitFields(Stack<object> values) {
base.InitFields(values);
angleType = (AngleType)values.Pop();
edgePropery = (EdgePropery)values.Pop();
angle1 = (float)values.Pop();
angle2 = (float)values.Pop();
angle3 = (float)values.Pop();
}
}
我刚刚想到使用 GetType()
可能算作反射。但是 GetFieldInfo()
也可以 return 直接从字段值创建 IEnumerable<object>
。 UI 然后可以使用 is
operator 检查字段类型并显示适当的 UI 元素(文本框、数字框、下拉列表等)。
顺便说一句,我认为这是一个相当丑陋的解决方案。 base 和 sub classes 之间的来回转换使得代码非常不可读。在真实世界的应用程序中,我可能宁愿使用反射来降低性能,而宁愿将类型检查和初始化逻辑放在一个地方。在上面的解决方案中,这个逻辑遍布整个模型 classes.
过去几个月我一直在学习 C#,我们接到了一个小项目的任务,该项目有以下特定要求:
我们需要以这种方式设计项目的 UI 部分,我们可以找出一些随机的 class 字段,然后从用户那里获取输入以初始化这些字段。
例如,在一个 运行 程序中,我们有这个 class A,它有两个整数字段。 在UI部分,我们需要弄清楚classA有两个整数,那么我们需要从用户那里接收2个整数并传回进行初始化。
另一种情况:
我们有 class B,它有一个布尔值和一个枚举字段,我们需要做同样的事情。
我一直倾向于使用反射来收集 运行 时间内所需的数据,但我在弄清楚如何实际接收用户所需的输入时遇到了很多问题。 另一个障碍是我们被告知反思不会帮助我们 and/or 不需要完成这项任务。
我对这门语言相当缺乏经验,所以我想可能还有一些我不知道的其他方法来实现它,但从概念上讲 - 我真的无法理解它是如何实现的。
下面是一些基本的 classes 层次结构,以提供更好的示例:
public abstract class Shape
{
private Point location;
private string color;
}
public abstract class NonCircular : Shape
{
private int edgesNumber;
}
public class Circle : Shape
{
private float radius;
private float diameter;
}
public class Triangle : NonCircular
{
public enum AngleType { Right, Acute, Obtuse }
public enum EdgePropery { Equilateral, Isosceles, Scalene }
private AngleType angleType;
private EdgePropery edgePropery;
private float angle1, angle2, angle3;
}
继续这个例子 - 假设 class 'Triangle' 稍后会在项目完成后添加到解决方案中。 我们先用一些大家共享的基本字段构造抽象class'Shape',然后根据需求,UI需要接收字段:
angleType, edgePropery, and angles1-3
并将值传回项目的逻辑部分,以便正确初始化它们。
您可以向基础 class 添加一个方法,该方法 return 需要初始化的字段信息。然后每个形状都会覆盖此方法并 return 其各自的字段。
在 UI 了解必填字段并让用户输入值后,需要第二种方法来实际初始化字段。
那么主要的问题是子class 不知道其基class 中的任何私有字段并且无法初始化它们。这可以通过在每次覆盖中始终调用 GetFieldInfo()
和 InitFields()
的 base
实现来解决。
为确保正确“使用”所提供值的集合,您可以使用堆栈。每个基 class 将从集合中 Pop()
初始化自身所需的所有值,然后将其余值留给派生的 classes.
从基础 classes 和派生 classes 与 GetFieldInfo()
.
当然,所有这一切只有在 UI 正确创建值的 Stack
时才有效,即它必须遵守顺序和通过 GetFieldInfo()
获得的 Type
s .
public abstract class Shape {
private Point location;
private string color;
public virtual IEnumerable<Type> GetFieldInfo() {
yield return location.GetType();
yield return color.GetType();
}
public virtual void InitFields(Stack<object> values) {
location = (Point)values.Pop();
color = (string)values.Pop();
}
}
public abstract class NonCircular : Shape {
private int edgesNumber;
public override IEnumerable<Type> GetFieldInfo() => base
.GetFieldInfo()
.Append(edgesNumber.GetType());
public override void InitFields(Stack<object> values) {
base.InitFields(values);
edgesNumber = (int)values.Pop();
}
}
public class Circle : Shape {
private float radius;
private float diameter;
public override IEnumerable<Type> GetFieldInfo() => base
.GetFieldInfo()
.Append(radius.GetType())
.Append(diameter.GetType());
public override void InitFields(Stack<object> values) {
base.InitFields(values);
radius = (float)values.Pop();
diameter = (float)values.Pop();
}
}
public class Triangle : NonCircular {
public enum AngleType { Right, Acute, Obtuse }
public enum EdgePropery { Equilateral, Isosceles, Scalene }
private AngleType angleType;
private EdgePropery edgePropery;
private float angle1, angle2, angle3;
public override IEnumerable<Type> GetFieldInfo() => base
.GetFieldInfo()
.Append(angleType.GetType())
.Append(edgePropery.GetType())
.Append(angle1.GetType())
.Append(angle2.GetType())
.Append(angle3.GetType());
public override void InitFields(Stack<object> values) {
base.InitFields(values);
angleType = (AngleType)values.Pop();
edgePropery = (EdgePropery)values.Pop();
angle1 = (float)values.Pop();
angle2 = (float)values.Pop();
angle3 = (float)values.Pop();
}
}
我刚刚想到使用 GetType()
可能算作反射。但是 GetFieldInfo()
也可以 return 直接从字段值创建 IEnumerable<object>
。 UI 然后可以使用 is
operator 检查字段类型并显示适当的 UI 元素(文本框、数字框、下拉列表等)。
顺便说一句,我认为这是一个相当丑陋的解决方案。 base 和 sub classes 之间的来回转换使得代码非常不可读。在真实世界的应用程序中,我可能宁愿使用反射来降低性能,而宁愿将类型检查和初始化逻辑放在一个地方。在上面的解决方案中,这个逻辑遍布整个模型 classes.