第1章:特性


本章目标

  1. 理解特性的概念

  2. 熟悉常用预定义特性

  3. 掌握自定义特性的运用

本章内容

特性是什么

特性的定义

​ 特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。

​ 特性(Attribute)用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息。.Net 框架提供了两种类型的特性:预定义特性和自定义特性。

​ 特性其实就是一个类,直接或间接继承自Attribute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

#region 程序集 mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\mscorlib.dll
// Decompiled with ICSharpCode.Decompiler 6.1.0.5902
#endregion

using System.Reflection;
using System.Runtime.InteropServices;

namespace System
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Delegate, Inherited = false)]
[ComVisible(true)]
public sealed class SerializableAttribute : Attribute
{
internal static Attribute GetCustomAttribute(RuntimeType type)
{
if ((type.Attributes & TypeAttributes.Serializable) != TypeAttributes.Serializable)
{
return null;
}

return new SerializableAttribute();
}

internal static bool IsDefined(RuntimeType type)
{
return type.IsSerializable;
}
}
}
#if false // 反编译日志
缓存中的 9
#endif

特性的语法

​ 特性(Attribute)的名称和值是在方括号内规定的,放置在它所应用的元素之前。positional_parameters 规定必需的信息,name_parameter 规定可选的信息。

1
2
[attribute(positional_parameters, name_parameter = value, ...)]
element

特性和注释的区别

  • 注释不影响程序运行
  • 特性会影响程序运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

using System;

namespace CH01Demo
{
/// <summary>
/// 这里是注释,除了让人看懂这里写的是什么,对运行没有任何影响
/// </summary>
///[Obsolete("请不要使用这个了,请使用什么来代替")]//对编译都产生了影响,编译出现警告
///[Obsolete("请不要使用这个了,请使用什么来代替", true)]//对编译都产生了影响,编译报错不通过
[Serializable]//可以序列化和反序列化
public class Student
{
public int Id { get; set; }

public string Name { get; set; }

public void Study()
{
Console.WriteLine($"这里是{this.Name}在学习");
}

public string Answer([Custom]string name)
{
return $"This is {name}";
}
}
}


特性无处不在:EF–MVC–WCF–WebService–UnitTest–IOC–AOP–SuperSocket

特性声明和使用

自定义一个特性

1
2
3
4
5
6
7
8
9
10
11

using System;

namespace CH01Demo
{
public class CustomAttribute : Attribute
{

}
}

​ 约定俗成用Attribute结尾,标记时就可以省略掉;可以用中括号包裹,然后标记到元素,其实就是调用构造函数;

使用特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

using System;

namespace CH01Demo
{
[Custom]
public class Student
{
[Custom]
public int Id { get; set; }
public string Name { get; set; }
[Custom]
public void Study()
{
Console.WriteLine($"这里是{this.Name}跟着Gerry老师学习");
}
}
}


AttributeUsage特性

​ 直接在一个元素上添加多个相同的特性,会报错特性重复,需要在特性上面添加特性标记 :[AttributeUsage(AttributeTargets.All, AllowMultiple = true)] 样就可以给同一个元素添加多个相同的特性了 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH01Demo
{
/// <summary>
/// 自定义特性
/// AttributeTargets:修饰的目标
/// AllowMultiple:是否允许多重修饰
/// Inherited:是否允许子类继承
/// </summary>
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
public class CustomAttribute : Attribute
{
public CustomAttribute()
{
Console.WriteLine($"{this.GetType().Name} 无参数构造函数执行");
}
public CustomAttribute(int id)
{
Console.WriteLine($"{this.GetType().Name} int参数构造函数执行");
this._Id = id;
}
public CustomAttribute(string name)
{
Console.WriteLine($"{this.GetType().Name} string参数构造函数执行");
this._Name = name;
}

//字段
private int _Id = 0;
private string _Name = null;

public string Remark;

//属性
public string Description { get; set; }
}
}

多个相同的特性情况展示 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

using System;

namespace CH01Demo
{
[Custom]
[Custom()]
[Custom(10)]
public class Student
{
[Custom]
public int Id { get; set; }
public string Name { get; set; }
[Custom]
public void Study()
{
Console.WriteLine($"这里是{this.Name}跟着陈老师学习");
}

[Custom(0)]
public string Answer([Custom]string name)
{
return $"This is {name}";
}
}
}

AttributeUsage特性,影响编译器运行,指定修饰的对象、能否重复修饰、修饰的特性子类是否生效,建议是明确约束用在哪些对象的 。特性应用案例

1
[AttributeUsage(AttributeTargets.Method|AttributeTargets.Class|AttributeTargets.Property, AllowMultiple = true)]

特性可以指定属性和字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

using System;

namespace CH01Demo
{

[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
public class CustomAttribute : Attribute
{
public CustomAttribute()
{
Console.WriteLine($"{this.GetType().Name} 无参数构造函数执行");
}
public CustomAttribute(int id)
{
Console.WriteLine($"{this.GetType().Name} int参数构造函数执行");
this._Id = id;
}
public CustomAttribute(string name)
{
Console.WriteLine($"{this.GetType().Name} string参数构造函数执行");
this._Name = name;
}

//字段
private int _Id = 0;
private string _Name = null;
public string Remark;

//属性
public string Description { get; set; }
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

using System;

namespace CH01Demo
{
[Custom(Remark = "123")]
[Custom(Remark = "123", Description = "456")]
[Custom(0, Remark = "123")]
[Custom(0, Remark = "123", Description = "456")]
public class Student
{
public int Id { get; set; }
public string Name { get; set; }

public void Study()
{
Console.WriteLine($"这里是{this.Name}跟着陈老师学习");
}
}
}

特性可以修饰返回值和参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

using System;

namespace CH01Demo
{
public class Student
{
[return: Custom]
public string Answer([Custom]string name)
{
return $"This is {name}";
}
}
}

特性的多重修饰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

using System;

namespace CH01Demo
{
[Custom]
[Custom()]
[Custom(Remark = "123")]
[Custom(Remark = "123", Description = "456")]
[Custom(0)]
[Custom(0, Remark = "123")]
[Custom(0, Remark = "123", Description = "456")]
public class Student
{
[return: Custom, Custom,Custom(), Custom(0, Remark = "123", Description = "456")]
public string Answer(string name)
{
return $"This is {name}";
}
}
}

特性应用案例

特性实现枚举展示描述信息

创建枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace CH01Demo
{
/// <summary>
/// 用户状态
/// </summary>
public enum UserState
{
/// <summary>
/// 正常状态
/// </summary>
[Remark("正常状态")]
Normal = 0,
/// <summary>
/// 已冻结
/// </summary>
[Remark("已冻结")]
Frozen = 1,
/// <summary>
/// 已删除
/// </summary>
[Remark("已删除")]
Deleted = 2
}
}

创建特性类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH01Demo
{
/// <summary>
/// 字段描述特性
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
internal class RemarkAttribute:Attribute
{
public string Remark { get; set; }

public RemarkAttribute(string c) {
this.Remark = c;
}
}
}

枚举扩展方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/// <summary>
/// 根据枚获取字段描述
/// </summary>
/// <param name="en"></param>
/// <returns></returns>
static string GetRemark(Enum en)
{
//获取类型对象
Type type = en.GetType();

//查找字段
FieldInfo field = type.GetField(en.ToString());

//判断当前字段是否应用了指定类型
if (field.IsDefined(typeof(RemarkAttribute)))
{
//字段描述特性对象
RemarkAttribute attribute = (RemarkAttribute)field.GetCustomAttribute(typeof(RemarkAttribute));

return attribute.Remark;
}
else
{
return en.ToString();
}
}
1
2
3
4
5
6
7
8
9
10
static void Fun4()
{
//枚举实例
UserState state = UserState.Normal;

//获取描述信息
string ab = GetRemark(state);

Console.WriteLine(ab);
}

特性数显数据验证

基类抽象特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH01Demo
{
/// <summary>
/// 数据验证抽象类
/// </summary>
internal abstract class AbstractValidateAttribute:Attribute
{
/// <summary>
/// 数据验证
/// </summary>
/// <param name="oValue">需验证的值</param>
/// <returns>是否正确</returns>
public abstract bool Validate(object oValue);
}
}

子类特性实现-数字大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH01Demo
{
[AttributeUsage(AttributeTargets.Property)]
/// <summary>
/// 数字大小验证 特性
/// </summary>
internal class LongAttribute : AbstractValidateAttribute
{
private long _Min = 0;
private long _Max = 4;
public LongAttribute(long min, long max)
{
this._Min = min;
this._Max = max;
}

public override bool Validate(object oValue)
{
if (oValue == null) return false;

long value = long.Parse(oValue.ToString());

return value>=_Min && value<=_Max;
}
}
}

子类特性实现-字符串非空:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH01Demo
{
/// <summary>
/// 字符串非空验证 特性
/// </summary>
internal class RequiredAttribute : AbstractValidateAttribute
{
public override bool Validate(object oValue)
{
return oValue != null
&& !string.IsNullOrWhiteSpace(oValue.ToString());
}
}
}

子类特性实现-字符串长度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH01Demo
{
[AttributeUsage(AttributeTargets.Property)]
/// <summary>
/// 字符串长度验证 特性
/// </summary>
internal class StringLengthAttribute : AbstractValidateAttribute
{
private int _Min = 0;
private int _Max = 3;
public StringLengthAttribute(int min, int max)
{
this._Min = min;
this._Max = max;
}

public override bool Validate(object oValue)
{
return oValue != null
&& oValue.ToString().Length >= this._Min
&& oValue.ToString().Length <= this._Max;
}
}
}

特性校验方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/// <summary>
/// 特性校验方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static bool Validate<T>(T t)
{
//获取类型实例
Type type = t.GetType();

//遍历所有属性
foreach (var prop in type.GetProperties())
{
//判断是否应用了指定类型
if (prop.IsDefined(typeof(AbstractValidateAttribute)))
{
//获取属性的值
object oValue = prop.GetValue(t);

//遍历当前属性的所有特性
foreach (AbstractValidateAttribute attribute in prop.GetCustomAttributes(typeof(AbstractValidateAttribute)))
{
if (!attribute.Validate(oValue))
return false;
}
}
}
return true;
}

教师类定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH01Demo
{
internal class Teacher
{
[Required]
[StringLength(2,6)]
public string Name { get; set; }

[Long(20,40)]
public int Age { get; set; }
}
}

类调用扩展方法验证字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void Fun5()
{
Teacher t = new Teacher()
{
Name="陈凯",
Age=35
};

if (Validate(t))
{
Console.WriteLine("特性校验成功");
}
else
{
Console.WriteLine("特性校验失败");
}
}

本章总结

课后作业