在运行时获取未知类字段的输入

Posted

技术标签:

【中文标题】在运行时获取未知类字段的输入【英文标题】:Get input for unknown class fields during runtime 【发布时间】:2021-08-05 20:49:46 【问题描述】:

过去几个月我一直在学习 C#,我们的任务是一个小项目,其中有这个特定要求:

我们需要以这样的方式设计项目的 UI 部分,我们可以找出一些随机的类字段,然后从用户那里获取输入来初始化这些字段。

例如,在程序的一次运行中,我们有一个包含两个整数字段的类 A。 在 UI 部分,我们需要弄清楚类 A 有两个整数,然后我们需要从用户那里接收 2 个整数并将它们传回用于初始化。

另一种情况:

我们有一个 B 类,它有一个布尔值和一个 Enum 字段,我们也需要这样做。

我一直倾向于使用反射来收集运行时所需的数据,但在弄清楚如何实际接收用户所需的输入时遇到了很多问题。 另一个障碍是我们被告知反思不会帮助我们和/或不需要完成这项任务。

我对该语言相当缺乏经验,因此我认为可能还有其他一些我不知道的实现方式,但从概念上讲 - 我真的无法理解它是如何实现的。

以下是一些基本的类层次结构,以提供更好的示例:

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;

以这个例子为例 - 假设稍后在项目完成后将“三角形”类添加到解决方案中。 我们先用大家共享的一些基本字段构造抽象类Shape,然后根据需求,UI需要接收这些字段:

angleType、edgePropery 和 Angles1-3

并将值传回项目的逻辑部分,以便正确初始化它们。

【问题讨论】:

听起来你真的需要反思,但谁给了你这个要求?那个说反思没有帮助的人?还是您误解了要求,这就是您解释它的方式?为什么需要“找出一些随机类字段,然后从用户那里获取输入以初始化这些字段”?这样一个程序的目的是什么? 该程序是某种“形状管理器”,即我们有一个基本的形状,正方形、圆形、三角形等。有很多继承实例,因此也有多态性。弄清楚字段的要求是编写程序,理论上,有人可以在其中添加另一个形状类(共享基类),并且程序将运行而无需在 UI 部分进行任何调整。要求来自课程的讲师,他说不需要反思。 为什么不显示这段代码?所以这些类没有方法体,只是为了说明需求。 当然。我添加了一些代码,希望能更好地说明我的问题。 您可以向基类添加一个抽象方法,该方法返回有关需要初始化的字段的信息。然后每个形状实现/覆盖此方法并返回其各自的字段。在 UI 知道所需字段后,需要第二种方法来实际初始化字段。 【参考方案1】:

您可以向基类添加一个方法,该方法返回有关需要初始化的字段的信息。然后每个形状都会覆盖此方法并返回其各自的字段。

在 UI 知道必填字段并让用户输入值之后,需要第二种方法来实际初始化字段。

主要问题是子类不知道其基类中的任何私有字段并且无法初始化它们。这可以通过在每次覆盖中始终调用 GetFieldInfo()InitFields()base 实现来解决。

为确保正确“使用”所提供值的集合,您可以使用堆栈。每个基类都将Pop() 集合中的尽可能多的值进行初始化,然后将其余的留给其派生类。

使用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() 也可以直接返回从字段值创建的IEnumerable&lt;object&gt;。然后 UI 可以使用 is operator 检查字段类型并显示适当的 UI 元素(文本框、数字框、下拉列表等)。


顺便说一句,我认为这是一个相当丑陋的解决方案。基类和子类之间的反复来回导致代码高度不可读。在现实世界的应用程序中,我可能宁愿接受使用反射的性能影响,而不是将类型检查和初始化逻辑放在一个地方。在上述解决方案中,此逻辑分布在您的模型类中。

【讨论】:

以上是关于在运行时获取未知类字段的输入的主要内容,如果未能解决你的问题,请参考以下文章

在 CodeIgniter 中密钥未知时获取 post 值

设置 innerhtml 时出现 IE 未知运行时错误

C#高阶-反射

运行时获取 ResNet 模型全连接层的输入

Java反射机制

如何在运行时访问派生类字段?