.NET学习笔记(三)----无处不在的特性

前言:

特性的无处不在体现在各种常用框架比如:MVC、IOC、ORM等都有它的存在。 特性的本质是一个类,声明时默认以Attribute(抽象类)结尾,直接或间接的继承Attribute类,甚至在编译时可以影响到编译器。
特性的应用:以[]包裹标记在类或者类内部的成员上。


一、特性与注释

注释:只是描述,编译后便不会存在
特性:可存在于编译后的IL代码中


二、自定义特性

①.如果以Attribute结尾,则可以直接省略Attribute(Attribute在中间比如BAttributeC则无法省略为BC)
②.可以标记在类内部的任何成员上
③.特性在标记的时候,其实就是去调用构造函数(特性中需要无参构造函数,如果只有有参构造函数的话,需要在标记时进行传参)
④.在标记的同时可以对公开的属性或对象进行赋值
⑤.在标记特性的时候,默认是不可以重复标记同一个特性的
*使用AttributeUsage可以让特性可以重复标记,如:[AttributeUsage(AttributeTargets.All,AllowMultiple = true,Inherited = true)]
----AttributeUsage:是用来修饰特性的特性
----AttributeTargets:指定当前特性只能标记在某个地方,建议自定义时最好明确指定(如:AttributeTargets.Class,代表只能给类进行标记,否则报错),方便别人理解自定的特性的目的(也可以不接受建议,.All代表没有限制)
----AllowMultiple:是否可以重复标记,默认false
----Inherited:是否可以继承此特性

1.定义

直接继承

在类名后继承Attribute

在这里插入图片描述

使用自定义的特性标记类

在这里插入图片描述

注:同一个特性只可以标记一次,上图的第二个标记会飘红报错,至于为什么图片上没飘红,因为我手速快

间接继承
在这里插入图片描述
在这里插入图片描述


2.标记

可以直接标记在类内部成员上
在这里插入图片描述


3.特性就是在调用有参/无参构造函数

特性中只存在有参构造函数时
在这里插入图片描述
直接标记会报错,需要在标记时给有参构造函数进行传参
在这里插入图片描述
正确的使用方法
在这里插入图片描述
如果想要继承有参构造的特性
在这里插入图片描述


4.标记的同时给共有的对象/属性赋值

在这里插入图片描述

在这里插入图片描述


5.重复标记特性

在需要重复使用的特性上增加AttributeUsage特性
在这里插入图片描述
在这里插入图片描述


三、调用特性内部成员(属性、对象、方法)

进行反编译后,标记有特性的类的内部,存在特性中的成员,但是无法直接调用,只能通过反射。

注:在写demo的时候无意中用了.net6.0的版本,结果导致报错: InvalidCastException:“Unable to cast object of type ‘System.Runtime.CompilerServices.NullableContextAttribute’ to type ‘MyAttribute6.CustomAttribute’.”,所以要在.net6中需要改变写法,下面会注明,由于NullableContextAttribute在官方文档中并没有搜到,所以在看了有限的两三篇大佬的整理之后,初步判定是因为.net6默认开启了NullableContextAttribute,所以不允许我们给引用类型赋值为null的,所以在声明string类型的时候,需要给string类型默认值,或者string后面加?(string?)。但是本处报错这只是诱因,并非给string类型默认值就可解决,具体更改处看代码。
另,附上大佬的文章链接:转自bilibili中的大魔王有木桑


使用到的类:

[Custom(123)]
public class Student
{
	[Custom(123)]
	public int Id { get; set; }

	[Custom(123)]
	public string Name;

	[Custom(123)]
	public int Age { get; set; }
	
	public void show()
	{
		Console.WriteLine("Hello Word");
	}

	[Custom(123)]
	public void show2( string name)
	{
		Console.WriteLine("Hello Word");
	}

	[return:Custom(123)]
	public string rName(string name)
	{
		return Name;
	}
}

使用到的自定义特性:

[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
public class CustomAttribute : Attribute
{
	private int _Id { get; set; }

	public string _Name { get; set; }

	public int _Age;

	public CustomAttribute(int id)
	{
		_Id = id;
	}

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

	public void Show()
	{
		Console.WriteLine("这是CustomAttribute");
	}
}

public class CustomAttributeChild : CustomAttribute
{
	public CustomAttributeChild() : base(123)
	{

	}
}

1.获取类上面的特性

.net5的读取特性方法:

Student stu = new Student();
stu.Name = "啊啊";

Type type = stu.GetType();
//判断Demo1类中是否存在标记了CustomAttribute特性
if (type.IsDefined(typeof(CustomAttribute), true))
{

	//CustomAttribute item in type.GetCustomAttributes(true)获取所有的特性,返回数组
	foreach (CustomAttribute attribute in type.GetCustomAttributes(true))
	{
		//调用属性
		attribute._Name = "张三";
		Console.WriteLine(attribute._Name);
		//调用对象
		attribute._Age = 22;
		Console.WriteLine(attribute._Age);
		//调用方法
		attribute.Show();
	}
}

.net6的获取特性方法:

Student stu = new Student();
stu.Name = "啊啊";

Type type = stu.GetType();
if (type.IsDefined(typeof(CustomAttribute), true))
{
	//foreach (CustomAttribute attribute in type.GetCustomAttributes(true))
	foreach (CustomAttribute attribute in type.GetCustomAttributes(typeof(CustomAttribute), true))
	{
		attribute._Name = "张三";
		Console.WriteLine(attribute._Name);
		attribute._Age = 22;
		Console.WriteLine(attribute._Age);
		attribute.Show();
	}
}

PS:区别在foreach循环里的type.GetCustomAttributes方法
输出结果:
在这里插入图片描述

2.获取类中属性上的特性

Student stu = new Student();
stu.Name = "啊啊";

Type type = stu.GetType();
//循环遍历所有的属性
foreach (PropertyInfo prop in type.GetProperties())
{
	//判断属性是否有MyAttribute特性
	if (prop.IsDefined(typeof(CustomAttribute), true))
	{
		//循环遍历属性的所有特性
		foreach (CustomAttribute attribute in prop.GetCustomAttributes(true))
		{
			attribute._Name = "张三";
			Console.WriteLine(attribute._Name);
			attribute._Age = 22;
			Console.WriteLine(attribute._Age);
			attribute.Show();
		}
	}
}

3.获取类中字段上的特性

Student stu = new Student();
stu.Name = "啊啊";

Type type = stu.GetType();
//循环遍历所有的字段
foreach (FieldInfo field in type.GetFields())
{	
	//判断字段是否有MyAttribute特性
	if (field.IsDefined(typeof(CustomAttribute), true))
	{
		//循环遍历字段的所有特性
		foreach (CustomAttribute attribute in field.GetCustomAttributes(true))
		{
			attribute._Name = "张三";
			Console.WriteLine(attribute._Name);
			attribute._Age = 22;
			Console.WriteLine(attribute._Age);
			attribute.Show();
		}
	}
}

4.获取类中方法上的特性

Student stu = new Student();
stu.Name = "啊啊";

Type type = stu.GetType();
//循环遍历所有的方法
foreach (MethodInfo method in type.GetMethods())
{
	//判断方法是否有MyAttribute特性
	if (method.IsDefined(typeof(CustomAttribute), true))
	{
		//循环遍历属性的所有特性
		foreach (CustomAttribute attribute in method.GetCustomAttributes(true))
		{
			attribute._Name = "张三";
			Console.WriteLine(attribute._Name);
			attribute._Age = 22;
			Console.WriteLine(attribute._Age);
			attribute.Show();
		}
	}
}

四、使用特性设置额外信息(枚举举例)

使用到的枚举:

public enum StudentLevelEnum
{
	[StudentLevel(_Name = "班长", _Number = 0)]
	monitor = 0,

	[StudentLevel(_Name = "组长", _Number = 1)]
	groupLeader = 1,

	[StudentLevel(_Name = "桌长", _Number = 2)]
	longTable = 2
}

使用到的自定义特性:

//指定只可以再对象上标记
[AttributeUsage(AttributeTargets.Field)]
public class StudentLevelAttribute : Attribute
{
	//枚举名称
	public string _Name { get; set; }

	//枚举编码
	public int _Number { get; set; }
	
	public StudentLevelAttribute()
	{
		
	}
	
	public StudentLevelAttribute(string Name)
	{
		_Name = Name;
	}

	public StudentLevelAttribute(int Number)
	{
		_Number = Number;
	}

	/// <summary>
	/// 获取枚举名称
	/// </summary>
	/// <returns></returns>
	public string GetName()
	{
		return _Name;
	}

	/// <summary>
	/// 获取枚举编码
	/// </summary>
	/// <returns></returns>
	public int GetNumber()
	{
		return _Number;
	}
}

取值方法:

此处只做了取名称作为例子,取编码举一反三即可

public class StudentLevelAttributeExtension
{
	/// <summary>
	/// 获取学生等级名称
	/// </summary>
	/// <param name="studentLevelEnum"></param>
	/// <returns></returns>
	public static string GetStudentLevelName(StudentLevelEnum studentLevelEnum)
	{
		string studentLevelName = string.Empty;
		//获取传入的枚举类型
		Type type = studentLevelEnum.GetType();
		//获取传入的枚举属性名
		string fieldName = studentLevelEnum.ToString();
		//获取这个属性名对应的类型
		FieldInfo field = type.GetField(fieldName);
		//判断这个属性上是否存在此特性
		if (field.IsDefined(typeof(StudentLevelAttribute),true))
		{
			//第一种方法,过于繁琐
			//StudentLevelAttribute attribute = (StudentLevelAttribute)field.GetCustomAttribute(typeof(StudentLevelAttribute), true);
			//第二种方法,推荐
			StudentLevelAttribute attribute= field.GetCustomAttribute<StudentLevelAttribute>();
			studentLevelName = attribute.GetName();
		}

		return studentLevelName;
	}
}

调用取值:

static void Main(string[] args)
{
	StudentLevelEnum enum1 = StudentLevelEnum.monitor;
	string Name = StudentLevelAttributeExtension.GetStudentLevelName(enum1);
	
	Console.WriteLine(Name);
}

在这里插入图片描述


改造为扩展方法:

取值方法:

改造后的方法为扩展方法,只能存在于静态类中,所以class前要加static

public static class StudentLevelAttributeExtension
{
	/// <summary>
	/// 扩展方法----(通过反射+特性+扩展方法封装成一个公共方法)
	/// </summary>
	/// <param name="enum">所有枚举的父类</param>
	/// <returns></returns>
	public static string GetStudentLevelName(this Enum @enum)
	{
		string studentLevelName = string.Empty;
		//获取传入的枚举类型
		Type type = @enum.GetType();
		//获取传入的枚举字段
		string fieldName = @enum.ToString();
		//获取这个字段对应的类型
		FieldInfo field = type.GetField(fieldName);
		//判断这个字段上是否存在此特性
		if (field.IsDefined(typeof(StudentLevelAttribute), true))
		{
			//第一种方法,过于繁琐
			//StudentLevelAttribute attribute = (StudentLevelAttribute)field.GetCustomAttribute(typeof(StudentLevelAttribute), true);
			//第二种方法,推荐
			StudentLevelAttribute attribute = field.GetCustomAttribute<StudentLevelAttribute>();
			studentLevelName = attribute.GetName();
		}

		return studentLevelName;
	}
}

调用方法:

static void Main(string[] args)
{
	StudentLevelEnum enum1 = StudentLevelEnum.monitor;
	string Name = enum1.GetStudentLevelName();
	Console.WriteLine(Name);
}

在这里插入图片描述

扩展(整活玩):

只看整活括起来的代码即可

public class Student
{
	[Custom(123)]
	public int Id { get; set; }

	[Custom(123)]
	public string Name;

	[Custom(123)]
	public int Age { get; set; }

	#region 整活时刻
	public StudentLevelEnum StudentLevel { get; set; }

	public string StudentLevelName
	{
		get
		{
			//get中调用前面写好的扩展方法,直接返回枚举的名称
			return this.StudentLevel.GetStudentLevelName();
		}
	}
	#endregion
}
static void Main(string[] args)
{
	#region 整活调用
	Student student = new Student();
	student.StudentLevel = StudentLevelEnum.monitor;
	Console.WriteLine("学生等级1:" + student.StudentLevel.GetStudentLevelName());
	student.StudentLevel = StudentLevelEnum.longTable;
	Console.WriteLine("学生等级2:"+student.StudentLevelName);
	#endregion
}

在这里插入图片描述


五、特性获取额外功能

1.通过特性加反射额外的获取了一个功能
2.实体验证==特性获取额外信息+特性获取额外的来完成的

好处:
1.只要是把验证的规则特性定义好,就可以重新使用
2.如果需要验证哪个属性,就把特性标记在哪个属性上就可以了;
3.只是标记了一个特性,就可以获取了一个验证的逻辑

使用到的类:

public class Student
{
	[StudentCheck("Id不能小于等于0")]
	public int Id { get; set; }

	public string Name;

	[StudentCheck("Age不能小于等于0")]
	public int Age { get; set; }
}

返回的格式类:

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

	public string ErrorMessage { get; set; }
}

自定义的特性:

/// <summary>
/// 学生信息检查特性
/// AttributeTargets.Property 限制只可在属性上进行标记
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class StudentCheckAttribute : Attribute
{
	/// <summary>
	/// 错误信息
	/// </summary>
	public string _ErrorMessage;

	/// <summary>
	/// 有参构造函数
	/// </summary>
	/// <param name="errorMessage"></param>
	public StudentCheckAttribute(string errorMessage)
	{
		_ErrorMessage = errorMessage;
	}
	
	/// <summary>
	/// 检查年龄是否符合条件
	/// </summary>
	/// <param name="age"></param>
	/// <returns></returns>
	public ApiResult NumberCheck(Object age)
	{
		if (Convert.ToInt32(age) <= 0)
		{
			return new ApiResult() { Success = false, ErrorMessage = _ErrorMessage };
		}
		else
		{
			return new ApiResult() { Success = true };
		}
	}
}

扩展类:

public class StudentCheckAttributeExtension
{
	/// <summary>
	/// 学生信息检查方法
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="t"></param>
	/// <param name="ar"></param>
	/// <returns></returns>
	public static ApiResult StudentCheck<T>(T t, out ApiResult ar)
	{
		ar = new ApiResult();
		//获取传入的实体类对象类型
		Type type = t.GetType();
		//循环遍历实体类对象的所有属性
		foreach (PropertyInfo pro in type.GetProperties())
		{
			//判断属性是否有StudentAgeCheck特性
			if (pro.IsDefined(typeof(StudentCheckAttribute), true))
			{
				//获取属性的特性
				StudentCheckAttribute att = pro.GetCustomAttribute<StudentCheckAttribute>();
				//获取属性的值
				var age = pro.GetValue(t);
				ar = att.NumberCheck(age);
				//调用属性的检查方法
				if (ar.Success == false)
				{
					return ar;
				}
			}
		}
		return ar;
	}
}

调用测试:

static void Main(string[] args)
{
	Student stu = new Student()
	{
		Id = 0,
		Name = "张三",
		Age = 0,
	};
	ApiResult ar = StudentCheckAttributeExtension.StudentCheck<Student>(stu, out ar);
	Console.WriteLine("Id为0时:" + ar.Success + "----" + ar.ErrorMessage);

	Student stu2 = new Student()
	{
		Id = 1,
		Name = "张三",
		Age = 0,
	};
	ApiResult ar2 = StudentCheckAttributeExtension.StudentCheck<Student>(stu2, out ar2);
	Console.WriteLine("Id非0时:" + ar2.Success + "----" + ar2.ErrorMessage);

	Student stu3 = new Student()
	{
		Id = 1,
		Name = "张三",
		Age = 18,
	};
	ApiResult ar3 = StudentCheckAttributeExtension.StudentCheck<Student>(stu3, out ar3);
	Console.WriteLine("标记的属性都不为0时:" + ar3.Success + "----" + ar3.ErrorMessage);
}

测试结果:
在这里插入图片描述


六、基于五的优化

优化原因:

【五、特性获取额外功能】中的例子为利用特性进行条件判断并以规定的形式返回结果,那么如果一个类中的属性、字段各有各的判断条件,就会出现扩展类里每个判断用的特性都要单独循环调用(如下图)。优化这个情况的解决方案就是继承抽象类。
在这里插入图片描述

升级示例

用到的类:

public class Student
{
	[StudentCheck("Id不能小于等于0")]
	public int Id { get; set; }

	[StudentCheckStr("Name长度有误", 2, 4)]
	public string Name { get; set; }

	[StudentCheck("Age不能小于等于0")]
	public int Age { get; set; }
}

返回的格式类:

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

	public string ErrorMessage { get; set; }
}

用于继承的抽象类:

/// <summary>
/// 特性类(用于继承的抽象类)
/// </summary>
public abstract class AbstractAttribute:Attribute
{
	/// <summary>
	/// 学生信息检查
	/// 为了与五区分,这边将所有的重载方法都改为了StuCheck
	/// </summary>
	/// <param name="oValue"></param>
	/// <returns></returns>
	public abstract ApiResult StuCheck(Object oValue);
}

第一个用于判断的特性

此处更改点有:
1.继承的不在是Attribute而是上面创建的抽象特性类
2.业务判断方法要加override重载
3.业务判断方法的方法名要与抽象类中创建的方法名一致

/// <summary>
/// 学生信息检查特性
/// AttributeTargets.Property 限制只可在属性上进行标记
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class StudentCheckAttribute : AbstractAttribute
{
	/// <summary>
	/// 错误信息
	/// </summary>
	public string _ErrorMessage;

	/// <summary>
	/// 有参构造函数
	/// </summary>
	/// <param name="errorMessage"></param>
	public StudentCheckAttribute(string errorMessage)
	{
		_ErrorMessage = errorMessage;
	}
	
	/// <summary>
	/// 检查是否符合条件
	/// </summary>
	/// <param name="age"></param>
	/// <returns></returns>
	public override ApiResult StuCheck(Object value)
	{
		if (Convert.ToInt32(value) <= 0)
		{
			return new ApiResult() { Success = false, ErrorMessage = _ErrorMessage };
		}
		else
		{
			return new ApiResult() { Success = true };
		}
	}
}

第二个用于判断的特性

更改点与第一个相同,不再赘述

/// <summary>
/// 第二个判断用的特性此处就不写太多注释了
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class StudentCheckStrAttribute: AbstractAttribute
{
	public string _ErrorMessage { get; set; }
	public int _MinLength { get; set; } 

	public int _MaxLength { get; set; }

	public StudentCheckStrAttribute(string errorMessage, int minLength, int maxLength)
	{
		_ErrorMessage = errorMessage;
		_MinLength = minLength;
		_MaxLength = maxLength;
	}

	public override ApiResult StuCheck(Object str)
	{
		if (str.ToString().Length < _MinLength || str.ToString().Length > _MaxLength)
		{
			return new ApiResult() { Success = false, ErrorMessage = _ErrorMessage };
		}
		else
		{
			return new ApiResult() { Success = true };
		}
	}
}

扩展类:

此处所用到的所有特性类均为抽象特性类

/// <summary>
/// 学生信息检查方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <param name="ar"></param>
/// <returns></returns>
public static ApiResult StudentCheck<T>(T t, out ApiResult ar)
{
	ar = new ApiResult();
	Type type = t.GetType();
	#region 抽象
	foreach (PropertyInfo pro in type.GetProperties())
	{
		if (pro.IsDefined(typeof(AbstractAttribute),true))
		{
			var value = pro.GetValue(t);
			foreach (AbstractAttribute abstractAttribute in pro.GetCustomAttributes())
			{
				ar = abstractAttribute.StuCheck(value);
				if (ar.Success==false)
				{
					return ar;
				}
			}
		}
	}
	return ar;
	#endregion
}

测试调用:

static void Main(string[] args)
{
	Student stu = new Student()
	{
		Id = 0,
		Name = "张三",
		Age = 0,
	};
	ApiResult ar = StudentCheckAttributeExtension.StudentCheck<Student>(stu, out ar);
	Console.WriteLine("Id为0时:" + ar.Success + "----" + ar.ErrorMessage);

	Student stu2 = new Student()
	{
		Id = 1,
		Name = "张三",
		Age = 0,
	};
	ApiResult ar2 = StudentCheckAttributeExtension.StudentCheck<Student>(stu2, out ar2);
	Console.WriteLine("Id非0时:" + ar2.Success + "----" + ar2.ErrorMessage);

	Student stu3 = new Student()
	{
		Id = 1,
		Name = "张三",
		Age = 18,
	};
	ApiResult ar3 = StudentCheckAttributeExtension.StudentCheck<Student>(stu3, out ar3);
	Console.WriteLine("标记的属性都不为0时:" + ar3.Success + "----" + ar3.ErrorMessage);

	Student stu4 = new Student()
	{
		Id = 1,
		Name = "张",
		Age = 18,
	};
	ApiResult ar4 = StudentCheckAttributeExtension.StudentCheck<Student>(stu4, out ar4);
	Console.WriteLine("名称不符合要求时:" + ar4.Success + "----" + ar4.ErrorMessage);
}

测试结果:
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

焦糖丨玛奇朵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值