You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WinForms访问UserControl内部类的架构合理性与代码优化咨询

问题说明

当前代码可正常运行,但不确定现有架构设计是否存在不合理之处,目标是编写整洁规范的代码,特咨询代码修正与优化方向。
相关业务说明:用户控件USC_AddDeviceControl为容器控件,内部嵌套包含DeviceInitControl、SerialPortInitControl、HttpClientInitControl三个用户控件。

现有代码

主窗体代码

public partial class MainForm1 : Form
{
    public MainForm1()
    {
        InitializeComponent();

        string errorMessage = "";

        DeviceInitControl.BO.BO_GetParams deviceParams;
        SerialPortInitControl.BO.BO_GetParams serialPortParams;
        HttpClientInitControl.BO.BO_GetParams httpClientParams;

        if (USC_AddDeviceControl.deviceInitParams.GetValues(out deviceParams, ref errorMessage))
        {
            //Device init
        }
        else
        {
            errorMessage = "Device initialisation parametrs error! " + errorMessage;
        }
        if (USC_AddDeviceControl.serialPortInitParams.GetValues(out serialPortParams, ref errorMessage))
        {
            //Serial port init
        }
        else
        {
            errorMessage = "Serial port initialisation parametrs error! " + errorMessage;
        }
        if (USC_AddDeviceControl.httpClientInitParams.GetValues(out httpClientParams, ref errorMessage))
        {
            //Http client init
        }
        else
        {
            errorMessage = "Http client initialisation parametrs error! " + errorMessage;
        }

        if (String.IsNullOrEmpty(errorMessage))
        {
            //Next step
        }
        else
        {
            //Show errorMessage to logs
        }
    }
}

SerialPortInitControl 控件实现代码

public partial class SerialPortInitControl : UserControl
{
    public class BO
    {
        public enum BO_Language
        {
            RUS,
            EN
        }

        public class BO_Init
        {
            public class CbxItems
            {
                public string[] names;
                public string[] baudRates;
                public string[] parities;
                public string[] dataBits;
                public string[] stopBits;
            }

            public CbxItems cbxItems = new CbxItems();
        }

        public class BO_GetParams
        {
            public string name;
            public string baudRate;
            public string paritie;
            public string dataBits;
            public string stopBits;
        }
        /*
        public class BO_SetParams
        {
            //Some controls in UserControlLibrary can contain it class 
        }
        */
    }


    public SerialPortInitControl()
    {
        InitializeComponent();
    }

    public void Init(in BO.BO_Language language, in BO.BO_Init initParams) // This methode is public because I can not initialisaion UserControl in constructor, because constructor can not take a parametrs
    {
        ControlsInit(in initParams);
        SetLanguage(in language);

        void ControlsInit(in BO.BO_Init controlsParams)
        {
            CbxInit(in controlsParams.cbxItems); //Some controls in UserControlLibrary can contain TextBox, DataGridView and other 

            void CbxInit(in BO.BO_Init.CbxItems items)
            {
                CBX_Name.Items.AddRange(items.names);
                CBX_BaudRate.Items.AddRange(items.baudRates);
                CBX_Parity.Items.AddRange(items.parities);
                CBX_DataBits.Items.AddRange(items.dataBits);
                CBX_StopBits.Items.AddRange(items.stopBits);
            }
        }
    }

    public void SetLanguage(in BO.BO_Language language)
    {
        switch (language)
        {
            case BO.BO_Language.RUS:
                {
                    LBL_Name.Text = "Имя порта";
                    LBL_BaudRate.Text = "Скорость";
                    LBL_Parity.Text = "Бит четности";
                    LBL_DataBits.Text = "Бит данных";
                    LBL_StopBits.Text = "Стоп бит";
                }
                break;

            case BO.BO_Language.EN:
                {
                    LBL_Name.Text = "Port name";
                    LBL_BaudRate.Text = "Baud rate";
                    LBL_Parity.Text = "Parity";
                    LBL_DataBits.Text = "Data bits";
                    LBL_StopBits.Text = "Stop bits";
                }
                break;

            default:
                {
                    throw new Exception("Control language is not define!");
                }
                break;
        }
    }

    public bool GetParams(out BO.BO_GetParams values, ref string errorMessage)
    {
        if (CheckGetParams(out values, ref errorMessage))
        {
            values = new BO.BO_GetParams()
            {
                name = CBX_Name.Text,
                baudRate = CBX_BaudRate.Text,
                paritie = CBX_Parity.Text,
                dataBits = CBX_DataBits.Text,
                stopBits = CBX_StopBits.Text,
            };

            return true;
        }
        else
        {
            values = null;
            errorMessage = "Checking failed! " + errorMessage;
            return false;
        }

        bool CheckGetParams(out BO.BO_GetParams values, ref string errorMessage)
        {
            values = new BO.BO_GetParams();
            bool valid = true;

            if (string.IsNullOrEmpty(CBX_Name.Text)) { valid = false; errorMessage = "Name field is null or empty! "; }
            if (string.IsNullOrEmpty(CBX_BaudRate.Text)) { valid = false; errorMessage = "BaudRate field is null or empty! "; }
            if (string.IsNullOrEmpty(CBX_Parity.Text)) { valid = false; errorMessage = "Parity field is null or empty! "; }
            if (string.IsNullOrEmpty(CBX_DataBits.Text)) { valid = false; errorMessage = "DataBits field is null or empty! "; }
            if (string.IsNullOrEmpty(CBX_StopBits.Text)) { valid = false; errorMessage = "StopBits field is null or empty! "; }

            return valid;
        }
    }
    /*
    public bool SetParams(in BO.BO_SetParams values, ref string errorMessage)
    {
        if (CheckSetValues(in values, ref errorMessage))
        {
            return true;
        }
        else
        {
            errorMessage = "Checking failed! " + errorMessage;
            return false;
        }

        bool CheckSetParams(in BO.BO_SetParams values, ref string errorMessage)
        {
            errorMessage = "Methode is not defined! ";
            return false;
        }
    }
    */
}
优化调整方向

架构解耦优化

  • 数据模型与UI控件分离:当前所有BO类(参数模型、语言枚举、初始化配置)都嵌套在UserControl内部,耦合度极高。建议将所有公共数据模型、枚举抽离到独立的Models类库/目录,不依赖UI控件即可访问,后续做参数持久化、跨模块传参不需要引用UI层。
  • 容器控件统一入口:当前主窗体直接访问容器控件内部子控件的参数实例,违反迪米特法则。建议给USC_AddDeviceControl容器控件统一对外暴露GetAllParams()、ValidateAllParams()方法,主窗体只和容器控件交互,不需要感知内部有多少个子控件,后续调整内部子控件数量时不需要修改主窗体代码。
  • 校验逻辑抽离:当前参数校验逻辑完全写在UI控件内部,无法复用。建议将参数合法性校验逻辑放到对应的数据模型类中实现,UI层只负责读取用户输入、调用模型校验方法、展示错误信息,做到UI与业务逻辑分离。

逻辑缺陷修复

  • 错误收集逻辑修复:当前校验逻辑如果存在多个字段非法,只会保留最后一个字段的错误信息;主窗体中前一个模块校验失败后,后续模块仍会执行校验/初始化,且错误信息可能出现覆盖、拼接混乱的问题。建议使用List<string>收集所有错误信息,校验阶段执行所有模块的检查,一次性返回全部错误给用户;初始化阶段只要存在错误就终止后续初始化流程。
  • 重复对象创建修复:GetParams方法中先在CheckGetParams里创建了一次BO_GetParams实例,校验通过后又new了一次新实例赋值,属于不必要的内存开销,直接在校验通过后读取控件值赋值即可。
  • 防重复初始化处理:当前Init方法如果被多次调用,会重复向下拉框添加选项,导致选项重复,建议初始化前先清空下拉框原有项,或者增加_isInitialized标记防止重复初始化。
  • 初始化位置调整:当前在主窗体构造函数中执行串口、设备、HTTP客户端这类重资源初始化操作,会导致窗体构造卡顿、启动白屏。建议将这类初始化逻辑移到Form_Load事件中,必要时采用异步初始化,搭配加载提示提升体验。

代码规范修正

  • 命名修正:修复现有拼写错误,比如paritie改为parity、parametrs改为parameters、methode改为method;去掉类名冗余的BO_前缀,遵循C# PascalCase命名规范;私有字段使用驼峰命名加下划线前缀,和UI控件命名做区分。
  • 移除无意义的in关键字:in关键字适合大尺寸值类型的只读引用传递,对于引用类型、小尺寸枚举/值类型加in没有任何性能收益,反而增加代码噪音,建议移除不必要的in修饰。
  • 减少嵌套函数:Init方法里嵌套两层本地函数,可读性差,建议将ControlsInit、CbxInit拆分为类的私有方法,不要嵌套在方法内部。
  • 强类型参数改造:当前BO_GetParams所有属性都是字符串类型,后续初始化串口时需要手动做类型转换,容易出现转换异常。建议将参数改为强类型,比如波特率用int类型、校验位用System.IO.Ports.Parity枚举、数据位/停止位用对应数值类型,从源头避免类型转换错误。
  • 多语言逻辑优化:当前多语言文案硬编码在SetLanguage方法中,后续新增语言、修改文案都需要修改UI代码。建议将多语言文案抽离到.NET自带的资源文件(.resx)中,通过资源管理器读取对应语言的文案,不需要硬编码。

内容的提问来源于stack exchange,提问作者isx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 05:33:17