特性
大约 11 分钟约 3263 字
特性
什么是特性,特性的本质
Serializable:只要是标记Serializable,就可以序列化
Serializable:还可以影响我们这个编译器
特性和注释的区别---看看特性的本质
如何自定义特性,特性的多种标记
如何调用到特性内部的成员--自定义的这个特性,如果才能使用它呢
{
Student student = new Student()
{
Id = 123,
Name = "我与春风皆过客"
};
//student.custom//调用不到
InvokeAttributeManager.Show(student);
}{
public class InvokeAttributeManager
{
/// <summary>
/// 通过反射来调用特性---确实是可以调用到特性的实例的
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
public static void Show(Student student)
{
//Type type = typeof(Student);
Type type = student.GetType();
//1.先判断是否有特性
if (type.IsDefined(typeof(CustomAttribute), true))
{
//2.获取--先判断再获取--为了提高性能
foreach (CustomAttribute attribute in type.GetCustomAttributes(true))
{
Console.WriteLine($"attribute._Name:{attribute._Name}");
Console.WriteLine($"attribute._Age:{attribute._Age}");
attribute.Do();
}
}
//获取当前Type下所有的属性上标记的特性
foreach (PropertyInfo prop in type.GetProperties())
{
if (prop.IsDefined(typeof(CustomAttribute), true))
{
//2.获取--先判断再获取--为了提高性能
foreach (CustomAttribute attribute in prop.GetCustomAttributes(true))
{
Console.WriteLine($"attribute._Name:{attribute._Name}");
Console.WriteLine($"attribute._Age:{attribute._Age}");
attribute.Do();
}
}
}
//获取当前Type下所有的字段上标记的特性
foreach (FieldInfo field in type.GetFields())
{
if (field.IsDefined(typeof(CustomAttribute), true))
{
//2.获取--先判断再获取--为了提高性能
foreach (CustomAttribute attribute in field.GetCustomAttributes(true))
{
Console.WriteLine($"attribute._Name:{attribute._Name}");
Console.WriteLine($"attribute._Age:{attribute._Age}");
attribute.Do();
}
}
}
//获取当前Type下所有的方法上标记的特性
foreach (MethodInfo method in type.GetMethods())
{
foreach (ParameterInfo para in method.GetParameters())
{
if (para.IsDefined(typeof(CustomAttribute), true))
{
//2.获取--先判断再获取--为了提高性能
foreach (CustomAttribute attribute in para.GetCustomAttributes(true))
{
Console.WriteLine($"attribute._Name:{attribute._Name}");
Console.WriteLine($"attribute._Age:{attribute._Age}");
attribute.Do();
}
}
}
if (method.IsDefined(typeof(CustomAttribute), true))
{
//2.获取--先判断再获取--为了提高性能
foreach (CustomAttribute attribute in method.GetCustomAttributes(true))
{
Console.WriteLine($"attribute._Name:{attribute._Name}");
Console.WriteLine($"attribute._Age:{attribute._Age}");
attribute.Do();
}
}
}
}
}
}特性获取额外信息
{
//一、传统方式获取枚举
{
//UserStateEnum userState = UserStateEnum.Normal;
////1.传统方式如果要获取到描述--只能一层一层的判断
////2.你们觉得这样好吗?
////问题:
//// a.分支判断太多了--如果增加一个枚举字段---就需要增加一个判断
//// b.如果说描述信息改了呢?只要是是使用到这个枚举的地方--都需要修改这个描述信息----工作量剧增
//if (userState == UserStateEnum.Normal)
//{
// Console.WriteLine("正常状态");
//}
//else if (userState == UserStateEnum.Frozen)
//{
// Console.WriteLine("已冻结");
//}
}
//....
//二.通过特性来获取描述信息---额外信息--特性获取额外信息
{
//1.特性获取描述信息---获取额外信息
//好处:
// a.如果增加字段,就可以直接获取不用其他的改动
// b.描述修改后,获取描述信息的方法不用修改
//2.通过反射+特性+扩展方法,可以封装一个获取额外新的的公共方法
{
UserStateEnum normal = UserStateEnum.Normal;
UserStateEnum frozen = UserStateEnum.Frozen;
UserStateEnum deleted = UserStateEnum.Deleted;
UserStateEnum other = UserStateEnum.Other;
UserStateEnum other1 = UserStateEnum.Other1;
string strnormalRemark = RemarkAttributeExtension.GetRemark(normal);
string strfrozenRemark = RemarkAttributeExtension.GetRemark(frozen);
string strdeletedRemark = RemarkAttributeExtension.GetRemark(deleted);
string strotherRemark = RemarkAttributeExtension.GetRemark(other);
string strother1Remark = RemarkAttributeExtension.GetRemark(other1);
}
{
UserStateEnum normal = UserStateEnum.Normal;
UserStateEnum frozen = UserStateEnum.Frozen;
UserStateEnum deleted = UserStateEnum.Deleted;
UserStateEnum other = UserStateEnum.Other;
UserStateEnum other1 = UserStateEnum.Other1;
string strnormalRemark = normal.GetRemark();
string strfrozenRemark = frozen.GetRemark();
string strdeletedRemark = deleted.GetRemark();
string strotherRemark = other.GetRemark();
string strother1Remark = other1.GetRemark();
}
{
// //从数据库中查询出来一条数据;
UserInfo user = new UserInfo()
{
Id = 1234,
Name = "暖风昔人",
Age = 25,
State = UserStateEnum.Normal
};
Console.WriteLine($"当前用户的状态为:{user.UserStateDescription}");
UserInfo user1 = new UserInfo()
{
Id = 1234,
Name = "暖风昔人",
Age = 25,
State = UserStateEnum.Frozen
};
Console.WriteLine($"当前用户的状态为:{user1.UserStateDescription}");
UserInfo user2 = new UserInfo()
{
Id = 1234,
Name = "暖风昔人",
Age = 25,
State = UserStateEnum.Deleted
};
Console.WriteLine($"当前用户的状态为:{user2.UserStateDescription}");
//编程这事儿,代码写的精妙了以后,其实一门艺术;
}
}
}特性获取额外功能--在之前的基础上,新增一个功能
{
//1.如果要保存一条数据到数据库中去
//2.从前端提交过来的数据格式为:
// {
// "id": 0,
// "name": "string",
// "age": 0,
// "state": 1
// }
//3.包含了很多字;
//4.如果数据库总Name的值要求存储的长度为40个字符--如果保存的数据超过40个字符---肯定会报错
//5.肯定要在保存之前就需要验证这行数据
//前端提交过来的数据
UserInfo adduse = new UserInfo()
{
Id = 123,
Name = "456464",
Age = 25,
Mobile = "sdfsdf"
};
//六-一
//1.传统方式:
//问题:
/// a. 太多的if判断
/// b. 代码量太多
{
if (adduse.Name == null)
{
Console.WriteLine("不能为空");
}
if (adduse.Name.Length > 40)
{
Console.WriteLine("Name超长了");
}
if (adduse.Mobile.Length != 11)
{
Console.WriteLine("手机号有问题");
}
}
//六--二//特性了获取额外功能---特性来完成验证
{
//增加了一个特性:可以对一个实体中的字段做验证-和验证不能为空
//1.通过特性加反射额外的获取了一个功能
//2.实体验证==特性获取额外信息+特性获取额外的来完成的
//好处:
//1.只要是把验证的规则特性定义好,就可以重新使用
//2.如果需要验证哪个属性,就把特性标记在哪个属性上就可以了;
//3.只是标记了一个特性,就可以获取了一个验证的逻辑
ApiResult bResult = ValidateInvokeManager.ValiDate<UserInfo>(adduse);
//验证一下QQ
//5位数---12位数
}
}{
public static class ValidateInvokeManager
{
public static ApiResult ValiDate<T>(this T t) where T : class
{
//typeof(T);
Type type = t.GetType();
{
//#region 判断是否为空
//foreach (PropertyInfo prop in type.GetProperties())
//{
// if (prop.IsDefined(typeof(RequiredAttribute), true))
// {
// RequiredAttribute attribute = prop.GetCustomAttribute<RequiredAttribute>();
// object oValue = prop.GetValue(t);
// ApiResult result = attribute.Validate(oValue);
// if (result.Success == false)
// {
// return result;
// }
// }
//}
//#endregion
//#region 如果要判断长度
//foreach (PropertyInfo prop in type.GetProperties())
//{
// if (prop.IsDefined(typeof(LengthAttribute), true))
// {
// LengthAttribute attribute = prop.GetCustomAttribute<LengthAttribute>();
// object oValue = prop.GetValue(t);
// ApiResult result = attribute.Validate(oValue);
// if (result.Success == false)
// {
// return result;
// }
// }
//}
//#endregion
}
{
foreach (PropertyInfo prop in type.GetProperties())
{
if (prop.IsDefined(typeof(AbstractAttribute), true))
{
object oValue = prop.GetValue(t);
foreach (AbstractAttribute attribute in prop.GetCustomAttributes())
{
ApiResult apiResult = attribute.Validate(oValue);
if (apiResult.Success==false)
{
return apiResult;
}
}
}
}
}
//....
//如果还有更多的验证呢?难道每一个验证都这样写一下?
//抽象父类 特性 继承;
return new ApiResult() { Success = true };
}
}
}.NET 内置特性深入
条件编译特性
/// <summary>
/// .NET 框架中常用的内置特性
/// </summary>
// 1. Obsolete — 标记过时的 API
[Obsolete("请使用 NewMethod 替代", error: false)] // error: true 会编译报错
public void OldMethod() { }
// 2. Conditional — 条件编译方法
[Conditional("DEBUG")]
public void DebugLog(string message)
{
Console.WriteLine($"[DEBUG] {message}");
}
// 只有在 DEBUG 模式下才会编译此方法
// Release 模式下调用 DebugLog 会被编译器移除
// 3. CLSCompliant — CLS 兼容性
[assembly: CLSCompliant(true)]
// 标记程序集是否遵循公共语言规范
// 4. Serializable — 标记可序列化
[Serializable]
public class MyData
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
// 5. Flags — 枚举位域
[Flags]
public enum Permissions
{
None = 0,
Read = 1,
Write = 2,
Execute = 4,
All = Read | Write | Execute
}
// 使用位运算组合
Permissions perm = Permissions.Read | Permissions.Write;
Console.WriteLine(perm); // "Read, Write"
Console.WriteLine(perm.HasFlag(Permissions.Read)); // True
// 6. ThreadStatic — 线程静态字段
[ThreadStatic]
private static int _threadLocalCounter;
// 每个线程有独立的 _threadLocalCounter 副本特性与 AOP(面向切面编程)
/// <summary>
/// 使用特性实现简单的 AOP 模式
/// </summary>
// 定义日志特性
[AttributeUsage(AttributeTargets.Method)]
public class LogAttribute : Attribute
{
public string? Message { get; set; }
}
// 定义缓存特性
[AttributeUsage(AttributeTargets.Method)]
public class CacheAttribute : Attribute
{
public int DurationSeconds { get; set; } = 60;
}
// 通过特性 + 装饰器实现 AOP
public interface IUserService
{
[Log(Message = "获取用户信息")]
[Cache(DurationSeconds = 30)]
User? GetUser(int id);
}
// 使用动态代理实现 AOP
public class LoggingDecorator<T> : DispatchProxy
{
private T _decorated;
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
Console.WriteLine($"调用方法: {targetMethod?.Name}");
var result = targetMethod?.Invoke(_decorated, args);
Console.WriteLine($"方法返回: {result}");
return result;
}
public static T Create(T decorated)
{
var proxy = Create<T, LoggingDecorator<T>>() as LoggingDecorator<T>;
proxy!._decorated = decorated;
return (T)(object)proxy!;
}
}
// 使用
IUserService service = new UserService();
IUserService loggingService = LoggingDecorator<IUserService>.Create(service);
var user = loggingService.GetUser(1);
// 输出: 调用方法: GetUser
// 输出: 方法返回: User { ... }特性在 ASP.NET Core 中的应用
/// <summary>
/// 特性在 Web 框架中的实际应用
/// </summary>
// 1. 授权特性
[Authorize(Roles = "Admin")]
[HttpGet("admin/users")]
public IActionResult GetAdminUsers() { /* ... */ }
// 2. 自定义授权特性
public class ApiKeyAuthorizeAttribute : Attribute, IAuthorizationFilter
{
private const string ApiKeyHeaderName = "X-Api-Key";
public void OnAuthorization(AuthorizationFilterContext context)
{
if (!context.HttpContext.Request.Headers.TryGetValue(
ApiKeyHeaderName, out var extractedApiKey))
{
context.Result = new UnauthorizedResult();
return;
}
var appSettings = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>();
var apiKey = appSettings.GetValue<string>("ApiKey");
if (extractedApiKey != apiKey)
{
context.Result = new UnauthorizedResult();
}
}
}
// 使用
[ApiKeyAuthorize]
[HttpPost("data")]
public IActionResult PostData(DataModel data) { /* ... */ }
// 3. 模型验证特性
public class RegisterRequest
{
[Required(ErrorMessage = "用户名不能为空")]
[StringLength(50, MinimumLength = 2)]
public string UserName { get; set; } = "";
[Required]
[EmailAddress(ErrorMessage = "邮箱格式不正确")]
public string Email { get; set; } = "";
[Range(0, 150, ErrorMessage = "年龄必须在 0-150 之间")]
public int Age { get; set; }
[RegularExpression(@"^1[3-9]\d{9}$", ErrorMessage = "手机号格式不正确")]
public string? Phone { get; set; }
}
// 4. 限流特性
public class RateLimitAttribute : Attribute, IActionFilter
{
public int MaxRequests { get; set; } = 100;
public int WindowSeconds { get; set; } = 60;
public void OnActionExecuting(ActionExecutingContext context)
{
var key = $"{context.HttpContext.Connection.RemoteIpAddress}";
// 实际应使用分布式缓存(如 Redis)
// 这里简化为内存缓存
Console.WriteLine($"限流检查: {key}");
}
public void OnActionExecuted(ActionExecutedContext context) { }
}
## 关键知识点
- 先明确这个主题影响的是语法层、运行时层,还是性能与可维护性层。
- 学习时要同时关注语言表面写法和编译器、JIT、GC 等底层行为。
- 真正有价值的是知道“为什么这样写”和“在什么边界下不能这样写”。
## 项目落地视角
- 把示例改成最小可运行样例,并观察编译输出、运行结果和异常行为。
- 如果它会进入团队代码规范,最好同步补充命名约定、禁用场景和替代方案。
- 涉及性能结论时,优先用 Benchmark 或实际热点链路验证,而不是凭感觉判断。
## 常见误区
- 只记语法糖,不知道底层成本。
- 把适用于小样例的写法直接搬到高并发或大对象场景里。
- 忽略框架版本、语言版本和运行时差异,导致结论失真。
## 进阶路线
- 继续向源码、IL、JIT 行为和 BCL 实现层深入。
- 把知识点和代码评审、性能诊断、面试复盘结合起来。
- 把同类主题做横向对比,例如值类型与引用类型、迭代器与 async 状态机、反射与 Source Generator。
## 适用场景
- 当你准备把《特性》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在需要理解语言特性、运行时行为或 API 边界时阅读。
- 当代码开始出现性能瓶颈、可维护性问题或语义歧义时,这类主题会直接影响实现质量。
## 落地建议
- 先写最小可运行样例,再把结论迁移到真实业务代码。
- 同时记录这个特性的收益、限制和替代方案,避免为了“高级”而使用。
- 涉及内存、并发或序列化时,最好配合调试器或基准测试验证。
## 排错清单
- 先确认问题属于编译期、运行期还是语义误用。
- 检查是否存在隐式转换、装箱拆箱、闭包捕获或上下文切换等隐藏成本。
- 查看异常栈、日志和最小复现代码,优先排除使用姿势问题。
## 复盘问题
- 如果把《特性》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《特性》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《特性》最大的收益和代价分别是什么?
## 延伸阅读
- [异步状态机原理](/dir/syntax/async_state_machine.md)
- [async/await 异步编程深入](/dir/syntax/async_await.md)
- [Channel 与异步生产者消费者](/dir/syntax/async_channel.md)
- [闭包与变量捕获机制](/dir/syntax/closure_capture.md)