03-C#

C#上位机 ModbusRTU 03功能码 【代码】C#上位机 ModbusRTU 03功能码。 阅读详情

C#.Net-特性-学习笔记


一、特性的本质

1.1 什么是特性

  • 特性是一个类:直接或间接继承自 Attribute 抽象类
  • 命名规范:类名默认以 Attribute 结尾(使用时可省略)
  • 应用方式:用方括号 [] 包裹,标记在类或类成员上
  • 编译后存在:与注释不同,特性在编译后依然存在于程序集中

1.2 特性 vs 注释

// 注释:编译后不存在,仅用于描述
/// <summary>这是一个Student类</summary>

// 特性:编译后存在,可通过反射获取
[Serializable]  // 标记类可序列化
[Custom(123)]   // 自定义特性
public class Student { }

二、自定义特性

2.1 定义特性类

特性类就是普通的类,继承 Attribute。构造函数参数是标记时的位置参数,公开的属性/字段可以在标记时用命名参数赋值:

// 使用 AttributeUsage 约束特性的使用方式
[AttributeUsage(
    AttributeTargets.All,      // 可标记的目标(类、方法、属性等)
    AllowMultiple = true,      // 是否允许重复标记
    Inherited = true           // 是否可被继承
)]
public class CustomAttribute : Attribute
{
    public string _Name { get; set; }  // 命名参数:标记时可赋值
    public int _Age;                   // 命名参数:标记时可赋值

    public CustomAttribute(int id) { }       // 位置参数

    public CustomAttribute(string name)
    {
        _Name = name;
    }

    public void Do()
    {
        Console.WriteLine("CustomAttribute.Do()");
    }
}

2.2 标记特性

特性可以标记在几乎所有代码元素上:

[Custom(123)]
[CustomAttributeChild]              // 子类特性同样可以用
public class Student
{
    [Custom(123)]
    [Custom(123)]                   // AllowMultiple=true 时允许重复标记
    public int Id { get; set; }

    [Custom("陈大宝")]
    public string Name { get; set; }

    [Custom(123456, _Age = 17)]     // 位置参数 + 命名参数
    public string Description;

    [Custom("山水", _Name = "456789")]
    public void Study() { }

    [return: Custom(123)]           // 标记在返回值上
    public string Answer([Custom(456)] string name)  // 标记在参数上
    {
        return $"This is {name}";
    }
}

2.3 AttributeUsage 详解

AttributeUsage 本身也是一个特性,专门用来约束自定义特性的使用方式,三个参数:

  • AttributeTargets — 指定特性能标记在哪里。常用值:Class、Method、Property、Field、Parameter、All,可用 | 组合多个目标。建议自定义特性时明确指定,避免误用:
[AttributeUsage(AttributeTargets.Property)]                          // 只能标记在属性上
[AttributeUsage(AttributeTargets.Field)]                             // 只能标记在字段上
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]  // 类或方法
  • AllowMultiple — 同一位置是否允许重复标记,默认 false。验证场景里一个属性需要同时标 [Required] 和 [Length],就需要设为 true。

  • Inherited — 子类是否继承父类上的特性,默认 true。StudentVip 继承 Student,Inherited=true 时通过反射也能在 StudentVip 上获取到 Student 的特性。注意:接口上的特性不会被实现类继承,只有类的继承链有效。


三、通过反射调用特性

特性标记后无法直接调用,必须通过反射获取实例才能使用。

关键点:

  • 特性标记 ≠ 执行,反射是让特性生效的唯一途径
  • 先用 IsDefined() 判断是否存在,再用 GetCustomAttributes() 获取实例,避免无谓的对象创建
  • GetCustomAttributes(true) 的 true 参数表示同时搜索继承链;false 只获取当前类型自身的特性
  • 类、属性、字段、方法、参数上的特性都可以通过对应的反射对象获取
public static void Show(Student student)
{
    Type type = student.GetType();

    // 获取类上的特性
    if (type.IsDefined(typeof(CustomAttribute), true))
    {
        foreach (CustomAttribute attr in type.GetCustomAttributes(true))
        {
            Console.WriteLine(attr._Name);
            attr.Do();  // 通过反射拿到实例后才能调用方法
        }
    }

    // 获取属性上的特性
    foreach (PropertyInfo prop in type.GetProperties())
    {
        if (prop.IsDefined(typeof(CustomAttribute), true))
        {
            foreach (CustomAttribute attr in prop.GetCustomAttributes(true))
                attr.Do();
        }
    }

    // 获取字段上的特性
    foreach (FieldInfo field in type.GetFields())
    {
        if (field.IsDefined(typeof(CustomAttribute), true))
        {
            foreach (CustomAttribute attr in field.GetCustomAttributes(true))
                attr.Do();
        }
    }

    // 获取方法参数上的特性
    foreach (MethodInfo method in type.GetMethods())
    {
        foreach (ParameterInfo para in method.GetParameters())
        {
            if (para.IsDefined(typeof(CustomAttribute), true))
            {
                foreach (CustomAttribute attr in para.GetCustomAttributes(true))
                    attr.Do();
            }
        }
    }
}

四、特性的两大核心用途

用途一:获取额外信息(枚举描述)

问题: 数据库存数字(1、2、3),界面要显示文字描述。传统 if-else 的问题:

// 传统方式 ❌
if (userState == UserStateEnum.Normal)
    Console.WriteLine("正常状态");
else if (userState == UserStateEnum.Frozen)
    Console.WriteLine("已冻结");
// 问题:分支多、修改描述需要改多处

解决: 把描述直接标记在枚举字段上,反射+扩展方法统一读取。

// 1. 定义描述特性,只允许标记在字段上
[AttributeUsage(AttributeTargets.Field)]
public class RemarkAttribute : Attribute
{
    private string _Description;
    public RemarkAttribute(string description) => _Description = description;
    public string GetRemark() => _Description;
}

// 2. 标记枚举
public enum UserStateEnum
{
    [Remark("正常状态")] Normal  = 1,
    [Remark("已冻结")]   Frozen  = 2,
    [Remark("已删除")]   Deleted = 3,
    [Remark("其他")]     Other   = 4
}

// 3. 扩展方法封装反射逻辑,调用方无需关心反射细节
public static class RemarkAttributeExtension
{
    public static string GetRemark(this Enum @enum)
    {
        Type type = @enum.GetType();
        FieldInfo field = type.GetField(@enum.ToString());

        if (field.IsDefined(typeof(RemarkAttribute), true))
        {
            RemarkAttribute attr = field.GetCustomAttribute<RemarkAttribute>();
            return attr.GetRemark();
        }
        return @enum.ToString(); // 没有标记就返回枚举名称本身
    }
}

// 4. 使用
UserStateEnum state = UserStateEnum.Frozen;
Console.WriteLine(state.GetRemark()); // 输出:已冻结

// 在实体中直接暴露描述属性
public class UserInfo
{
    public UserStateEnum State { get; set; }
    public string UserStateDescription => State.GetRemark();
}

新增枚举值只加一行 [Remark("xxx")],描述改了只改标记处,获取逻辑完全不用动。


用途二:获取额外功能(数据验证)

问题: 保存前需要验证字段,传统 if 判断的问题:

  • 字段多了代码量爆炸
  • 验证逻辑散落各处,无法复用

解决: 把验证规则标记在属性上,反射统一执行。

// 1. 抽象基类定义统一接口(开闭原则:扩展新规则只需继承,不改已有代码)
public abstract class AbstractAttribute : Attribute
{
    public abstract ApiResult Validate(object oValue);
}

public class ApiResult
{
    public bool Success { get; set; }
    public string ErrorMessage { get; set; }
}

// 2. 具体验证规则
[AttributeUsage(AttributeTargets.Property)]
public class RequiredAttribute : AbstractAttribute
{
    public string _ErrorMessage;
    public RequiredAttribute(string message) => _ErrorMessage = message;

    public override ApiResult Validate(object value)
    {
        bool ok = value != null && !string.IsNullOrWhiteSpace(value.ToString());
        return new ApiResult { Success = ok, ErrorMessage = ok ? null : _ErrorMessage };
    }
}

[AttributeUsage(AttributeTargets.Property)]
public class LengthAttribute : AbstractAttribute
{
    public string _ErrorMessage;
    private int _Min, _Max;
    public LengthAttribute(int min, int max) { _Min = min; _Max = max; }

    public override ApiResult Validate(object value)
    {
        if (value == null) return new ApiResult { Success = true };
        int len = value.ToString().Length;
        bool ok = len >= _Min && len <= _Max;
        return new ApiResult { Success = ok, ErrorMessage = ok ? null : _ErrorMessage };
    }
}

// 3. 标记实体属性
public class UserInfo
{
    public int Id { get; set; }

    [Required("Name的值不能为空")]
    public string Name { get; set; }

    [Required("Mobile的值不能为空")]
    [Length(11, 11, _ErrorMessage = "手机号必须为11位数")]
    public string Mobile { get; set; }
}

// 4. 统一验证管理器,只依赖抽象基类
public static class ValidateInvokeManager
{
    public static ApiResult ValiDate<T>(this T t) where T : class
    {
        Type type = t.GetType();

        foreach (PropertyInfo prop in type.GetProperties())
        {
            if (prop.IsDefined(typeof(AbstractAttribute), true))
            {
                object value = prop.GetValue(t);

                // 一个属性可能有多个验证特性,逐一执行
                foreach (AbstractAttribute attr in prop.GetCustomAttributes())
                {
                    ApiResult result = attr.Validate(value);
                    if (!result.Success)
                        return result; // 遇到第一个失败立即返回
                }
            }
        }

        return new ApiResult { Success = true };
    }
}

// 5. 使用
UserInfo user = new UserInfo { Id = 1, Name = "张三", Mobile = "123" };
ApiResult result = ValidateInvokeManager.ValiDate(user);
if (!result.Success)
    Console.WriteLine(result.ErrorMessage); // 手机号必须为11位数

要新增验证规则(如正则、数字范围),只需新建一个继承 AbstractAttribute 的类,ValidateInvokeManager 完全不用改。


五、特性在自定义 ORM 中的应用

问题: 数据库表名/字段名与类名/属性名不一致,ORM 无法自动映射。

解决: 用特性标记映射关系,ORM 通过反射读取特性生成正确的 SQL。

// 定义映射特性
[AttributeUsage(AttributeTargets.Class)]
public class TableNameAttribute : Attribute
{
    public string Name { get; }
    public TableNameAttribute(string name) => Name = name;
}

[AttributeUsage(AttributeTargets.Property)]
public class ColumnNameAttribute : Attribute
{
    public string Name { get; }
    public ColumnNameAttribute(string name) => Name = name;
}

// 标记实体
[TableName("LM_User")]
public class SysUser : BaseModel
{
    [ColumnName("user_name")]  public string Name  { get; set; }
    [ColumnName("user_phone")] public string Phone { get; set; }
}

// ORM 查询时通过反射读取特性生成 SQL
public T Find<T>(int id) where T : BaseModel
{
    Type type = typeof(T);

    string tableName = type.IsDefined(typeof(TableNameAttribute), false)
        ? type.GetCustomAttribute<TableNameAttribute>().Name
        : type.Name;

    var columns = type.GetProperties().Select(p =>
        p.IsDefined(typeof(ColumnNameAttribute), false)
            ? p.GetCustomAttribute<ColumnNameAttribute>().Name
            : p.Name);

    string sql = $"SELECT {string.Join(",", columns)} FROM {tableName} WHERE Id={id}";
    // ... 执行查询并反射赋值
}

泛型缓存优化反射性能: 同一类型的 SQL 模板每次查询都一样,没必要每次都反射。用泛型类的静态构造函数缓存结果,同一类型只反射一次:

public class ConstantSqlString<T>
{
    private static readonly string FindSql;

    static ConstantSqlString()
    {
        // 静态构造函数只执行一次,之后直接读缓存
        Type type = typeof(T);
        FindSql = $"SELECT {string.Join(",", type.GetProperties().Select(p => $"[{p.Name}]"))} FROM {type.Name} WHERE Id=";
    }

    public static string GetFindSql(int id) => $"{FindSql}{id}";
}

// 使用
string sql = ConstantSqlString<SysUser>.GetFindSql(1);

ConstantSqlString<SysUser> 和 ConstantSqlString<SysCompany> 是两个独立的类,各自有独立的静态字段,互不干扰,线程安全由 CLR 的类型初始化机制保证,无需加锁。


六、核心总结

特性的三大核心步骤

  1. 定义特性:继承 Attribute,用 AttributeUsage 约束使用范围
  2. 标记特性:用 [] 标记在目标上,传位置参数或命名参数
  3. 反射调用:运行时通过反射获取特性实例,执行其中的逻辑

特性的两大价值

  1. 获取额外信息:枚举描述、配置信息、映射关系
  2. 获取额外功能:数据验证、AOP 切面、权限控制

最佳实践

  • 明确指定 AttributeTargets,告诉使用者这个特性用在哪里
  • 先用 IsDefined() 判断再获取,避免无谓的对象创建
  • 用扩展方法封装反射调用逻辑,对外暴露干净的 API
  • 用抽象基类统一处理同类特性,保持可扩展性
  • 反射有性能开销,高频调用场景用泛型缓存优化

常见框架中的特性速查

场景常见特性
序列化[Serializable]
ASP.NET Core MVC[HttpGet] [Route] [Authorize] [FromBody]
依赖注入[Inject] [Service]
ORM(EF Core)[Table] [Column] [Key] [ForeignKey]
数据验证[Required] [Range] [StringLength] [EmailAddress]
C# WCF 03 简单的IIS寄宿 首先IIS寄宿是将WCF服务发布到IIS中,也就是说WCF服务寄宿于IIS上。 实现步奏。 接着上一篇,WCF 02   这次我们在02 调通之后,再来我们建一个WCF服务应用程序。 阅读详情

相关推荐

C# Modbus通信从入门到精通(24)——Modbus TCP(0x03功能码与C#代码实现)

本文详细介绍了ModbusTCP协议中0x03读保持寄存器功能码的原理、发送报文、返回报文,由高级工程师多年经验总结而成,绝对干货!

qq_34059233的博客 2663

C# 3.03值类型

C# 3.03值类型     float theMySum = 9.27f;  float theMuSums = 1.12F; double myDou = 927d;   double mudou = 112D;   如果9.27后面没有加上f,默认是double类型 如果定义了float类型的变量,没有赋值。默认是0,不是0.00    ...

Txwtech 363

C# Modbus通信从入门到精通(5)——Modbus RTU(0x03功能码与C#代码实现)

本文详细介绍了ModbusRTU协议中0x03读保持寄存器功能码的原理、发送报文、返回报文,由高级工程师多年经验总结而成,绝对干货!

qq_34059233的博客 5186

解决C#导出excel异常来自 HRESULT:0x800A03EC的方法 .

解决C#导出excel异常来自 HRESULT:0x800A03EC的方法 .

asdfgh0077的博客 372

C# Modbus通信从入门到精通(15)——Modbus ASCII(0x03功能码与C#代码实现)

本文详细介绍了ModbusASCII协议中0x03读输出线圈功能码的原理、发送报文、返回报文、C#代码实现,由高级工程师多年经验总结而成,绝对干货!

qq_34059233的博客 1968

超声波测距的15度魔法:HC-SR04测量角度特性在机器人避障中的实战技巧

本文深入探讨了HC-SR04超声波测距模块在机器人避障中的15度测量角度特性及其应用技巧。通过分析15度角的工程意义、多模块阵列设计、狭小空间优化方案及温度补偿技术,帮助开发者提升机器人环境感知能力。文章特别强调了HC-SR04模块在低成本避障方案中的核心优势与实战优化策略。

weixin_29172963的博客 430

C#基础03

C#基础03 枚举 作用:可以限制用户输入,并且可以提供提示信息。防止了用户随意输入,输入时只需要选择相应的值。具有统一性。 注意:枚举类型的值不能取int型的值。枚举类型的实例化对象都可强制转换为int型(每个枚举值都存在一个默认编号从0开始)。在定义枚举类型时,可以通过 值=编号 来定义枚举的编号(使其不一定从零开始)。 重点:如何把字符串转换成枚举类型 程序代码实现: (自定义的枚举类型)(Enum.Parse(typeof(自定义的枚举类型),“待转换的字符串”)); 结构体 可以存储一组类型不同

Echo_Wish 238

使用vite+react+ts+Ant Design开发后台管理项目(四)

本文将引导开发者从零基础开始,运用、react、react-router、react-redux、Ant Design、less、tailwindcss、axios等前沿技术栈,构建一个高效、响应式的后台管理系统。通过详细的步骤和实践指导,文章旨在为开发者揭示如何利用这些技术工具,从项目构思到最终实现的全过程,提供清晰的开发思路和实用的技术应用技巧。使用vite+react+ts+Ant Design开发后台管理项目(一)使用vite+react+ts+Ant Design开发后台管理项目(二)

lbking666666的专栏 2006

C# ComExecption异常 HRESULT:0x800A03EC

在调用Excel的COM组件对已有的Excel进行写入操作的时候,报出了这个异常      C# ComExecption异常 HRESULT:0x800A03EC 查找了很多资料,造成抛出这个异常的原因可能包括 1、Excel的行列都是从1开始的,如果我们习惯性的给worksheet.Cells[0, 0] 或者worksheet.Cells[1, 0]或者worksheet.Cells

写代码的蜗牛 4430

VS2022配置Qt6

VS2022配置QT教程

Life is a struggle ! 6358

Tessent MBIST SDC

主要是两个PROC,tessent_constrain_${design_name}_mentor_ijtag_non_modal和tessent_constrain_${design_name}_mentor_memory_bist_modal。其中,Memory/MBIST controller/MEM Interface可以从func_clk_1来(用func_clk_1和func_clk_2的高频时钟),如图中红色线。其中,SIB和BAP用ijtag_tck的clock,如图绿色线。

po_poo的博客 1987

C#03 方法

无

u010898680的博客 177

西红柿成熟度检测数据集总结

成熟度类别标注数量semiripe(半成熟)10275rotten(腐烂)13470fullyripe(完全成熟)35031unripe(未成熟)26212。

crz1336768325的博客 2125

Modbus功能码全解析:用C#实现03/04/16功能码的寄存器读写技巧

本文深入解析Modbus协议中03/04/16功能码的工程化实现,通过C#演示如何构建高效的ModbusTcp客户端,涵盖寄存器读写、字节序处理及工业级优化技巧。内容包含完整的Demo代码和实战经验,帮助开发者快速解决工业自动化中的通信问题。

weixin_29253711的博客 243

knowhere | 第一课:项目导论与环境搭建

摘要 本课程是Knowhere项目的第一课,重点介绍项目整体架构和开发环境搭建。Knowhere定位为AI Agent的文档记忆基础设施,通过解析多格式文档并保留结构化信息(如章节层级、表格语义等),为Agent提供可导航的文档知识。项目采用Python monorepo结构,核心包括: apps/api:FastAPI服务,处理HTTP请求和流程调用 apps/worker:Celery服务,执行文档解析等耗时任务 packages/shared-python:共享基础设施层 课程明确了各层职责边界,并推

weixin_43674738的博客 151

优化YOLOv8检测精度:Damo-YOLO的RepGFPN特征融合技术解析【YOLOv8】

YOLOv8在结构上依旧延续了自YOLOv4以来的CSPNet(Cross Stage Partial Network)和FPN(Feature Pyramid Network)的设计,结合了PANet(Path Aggregation Network)来增强特征的多尺度表达。然而,随着目标检测任务的复杂性增加,如何更有效地融合不同层级的特征成为提升检测性能的关键。Damo-YOLO中的RepGFPN引入了重参数化的思想,通过在训练和推理阶段对特征融合模块进行结构优化,显著提升了检测效果。

博客 1583

解决C#导出excel异常来自 HRESULT:0x800A03EC另一种可能解决方案

C#导出Excel时报:异常来自 HRESULT:0x800A03EC 可能的原因是:你导出是的多个sheet时, 1、sheet名称太长,超出30个字符, 2、还有一种可能是sheet重名了

LYBWWP 6781

AutoSAR系列讲解(深入篇)13.6-闪烁一颗LED灯 6

闪烁一颗LED灯:配置SWC和生成代码

雪云飞星的博客 1万+

C#基础——03

答:重载 (Overload) 是重新加载的意思,指的是同一个类 中方法名相同,但是参数列表不同的方法组 (返回值不能区别函数是否重载),重载没有关键字。通俗点说重载是对内存中同一个类中的函数或方法进行重新加载的意思。重写 (Override) 指子类修改父类中某个函数的函数体,需要重写的父类中的函数必须是由virtual、abstract或override标注的,,虚方法可以在派生类(子类) 中被重写,也可以不被派生类(子类)中被重写。使用abstract关键字修饰的方法就是抽象方法,

ldSunrise的博客 266

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

03 C#编程概述

1. 一个简单的应用程序 引入命名空间 using System;使用关键字using注释:单号注释//; 多行注释 /*…*/, 文档注释///…/// //输出 Console.WriteLine("Hello Wold");2. 标识符标识符是一种字符,用来命名变量,方法,参数等。规则:- 字母,下划线,数字组成- 数字不能子首位- 允许使用@字符,但是不推荐使用如:int id = 10001; 其中id就是标识符标识符区分大小写Id 和id是不同的变量3.

flysh05的专栏 189

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

C#中类Class— 03

一、类(class) 1、定义一个类中包含 成员变量,也称作字段 成员函数,也称作方法 【注意】:在同一个命名空间下定义的类时,相互调用时不需要引入命名空间。若是在不同的命名空间下,则在使用之前需要引入命名空间。 在一个类中调用另一个类中的变量或函数时,需要先为被调用的类创建对象,并将该对象初始化。然后再通过该对象来调用对应的变量或函数。 // 自定义的类 using System; ...

Jeffxu_lib的博客 530

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

C#学习——Day03

第三天,冲冲

lb121zc的博客 659

c# Modbus串口通信实现(功能码0x03)记录

最近在学习Modbus的通信 这个代码实现的是关于Modbus-RTU协议(功能码0x03)的内容,代码是目前学习教程的,我跟着敲下来的,但作为新手理解起来有点抽象,所以记录下目前我对于这段代码理解。

m0_72238284的博客 970

C#入门之路03-数组

13 Sort(Array) 使用数组的每个元素的 IComparable 实现来排序整个一维数组中的元素。 14 ToString 返回一个表示当前对象的字符串。从对象(Object)继承。

TurboQ珞珈 248

C#学习——03 简单类型

C#简单类型

weixin_48488858的博客 636
上一篇: 15-C#
下一篇: 05-C#
似水明俊德
博客等级 码龄8年 247粉丝 276原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值