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
相关产品推荐
相关产品推荐

