在运行时获取未知 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() 获得的 Types .

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.