本文分享一些代码使用Attribute的高级写法部分,日常使用中应该很少用到,但是是能给代码书写带来很好的优化效果的。
这些东西其实和xml解析时定义的class中做的标记、自定义配置文件的class标记、枚举中文标记等等是类似的。都是在上面“[]”+标记。
日常编码中,应该Enum的中文标记是使用最多的,接下来就是xml解析,自定义配置文件应该用的人比较少,毕竟实用性不大,直接定义字段,或者使用系统自带的要容易用一些。接下来分享的几个,应该较少人接触过。
一.Obsolete
类似tooltip提示,一般写接口的人会用到,用于标记函数是否过时,以及强制让函数无法通过编译。
1 | public static class TestObsolete |
实例中的三个方法是一样的操作:调用输出信息。照理说,是能直接调用然后输出的,毕竟没有语法错误,编译器也没提示函数有问题。
但实际使用时是:一个警告,一个直接错误。
直接给你一个error,不让编译通过。
查看定义,该属性是继承Attribute,这样用法就是直接标在函数或者成员上面,并用“[]”包起来。该特性有三种构造函数,第二种常用,就是让函数提示过时,同时里面有message提示信息。用法就是上面第一张图那样,让函数本身“过时”,给了一个警告提,同时鼠标移上去时,会提示message的信息。第三种构造函数,就是加了error,标记是显示成“警告”(可以编译通过),还是“错误”(无法编译通过)。
这个特性,一般写接口的人会使用到,在接口的版本更新后,如果替换了新接口,但是又想老程序能使用时,一般保留老接口的代码。不过这里就有问题了,接下来新使用的程序,应该让他们用新接口而不是用老接口:如果说写在文档里面说明这个情况,但是好多人是连接口文档都不看的,直接dll引用就开始写代码的;如果写在注释里面?那更加不行,一般没报错,是很少有人去看注释的。所以这时候就得用特性了,使用这个特性,写代码的时候就直接编译器提示了,使用者全部都会看到这个提示。(unity经常用这个特性提示每次更新版本后丢弃的老属性,不过现在国内绝大部分公司,都是直接删掉老函数,然后拉分支来处理的,这样导致后期一大堆分支,维护很麻烦)
二.Conditional
一个好玩的特性,类似于 #if XXXX #elif XXXX #else XXX #endif 这种使用:
使用方式如下:
1 | using System; |
调用时:发现明明代码写在那里,但是却不执行:
因为它的使用前提是你要提示它“要执行”,它才会执行。不然没提示,代码在,但是不执行。
怎么提示:
方法一:在开头写#define +构造函数中传递的字符串
方法二:使用“条件编译符号”
这里插播一下怎么运行core程序,因为vs编译生成的是dll:
cd到vs工程文件所在地方,然后“dotnet build”或者“dotnet run”都行,run是编译后同时运行,然后cd到dll所在地方,“dotnet”+项目名,就能运行。当然目录下要有“项目名.runtimeconfig.json”这个文件(标记目标环境)
这里分享几个参考的博文:
.NET Core - .NET 使用 .NET Core 跨平台运行
三.CallXXXX特性
这个东西实用性一般般,用于调试排错的时候,就是你找到在哪里出错了,但是看代码又不知道上一层是哪个函数(就是哪里调用这个函数导致出问题),就加这个特性,一层一层排上去,就能找到是哪里出问题了。
之前不知道有这个特性的时候,我都是直接用反射,找到哪个函数调用,然后一层一层反射上去。反射的过程超级麻烦,实用性也不好。当然,如果可以,建议直接用vs远程附加调试,打断点就能知道到底怎么出问题了。
四.DebuggerStepThrough
好吧,这个算凑数的。
这个特性是调试的时候,F10和F11的区别,就是如果函数加了这个特性,执行到这个函数,就算你使用F11(单步,逐语句),它也给你当F10(整个函数直接过,逐过程)。
“[System.Diagnostics.DebuggerStepThrough]”
1 | using System; |
五.枚举中文Description
Description,这个最常用。
1 | using System; |
这里就不多解释了。做界面的人建议多用,在一些选项框中,中文对应枚举,比对应源的第几个要好用,也不容易出问题。
这里插播一下enum的另一个用法:权限校验
就是把枚举和二进制关联起来,0表示没这个权限,1表示有,然后进行与或非操作就能判断。
定义枚举:1
2
3
4
5
6
7
8
9
10
11
12
13
14[Flags]
public enum EnumPower
{
[Description("游客")]
AllNull = 0, //0x00 表示全部没有 0000
[Description("创建")]
Create = 1 << 0, //0x01或者1 2的0次方 0001
[Description("读取")]
Read = 1 << 1, //0x02或者2 2的1次方 0010
[Description("更新")]
Update = 1 << 2, //0x04或者4 2的2次方 0100
[Description("删除")]
Delete = 1 << 3 //0x08或者8 2的3次方 1000
}
接下来,把权限“与”/“或”操作起来,然后检查是否某位上为1就是有该权限。使用该用法,数据库保存用户权限就容易了,例如有read和delete权限的,直接0010|1000=1010。
判断是否有read权限时:1010&0010 = 0010->1表示有;当没有时:1000&0010=0000->0
判断是否有read或者delete其中之一:1010&(0010|1000)=1010->都有。或者其中一个有0010&(0010|1000)=0010。
可以发现,判断权限时,我们只要&|操作后,检查==0?就可以了。省了一堆if else if语句。
可以合并成一个函数:
1 | public static class CheckPower |
使用:
1 | EnumPower owner = EnumPower.AllNull; |
这里比较好玩的是“1<<n”,表示2的n次方。注意“|”和“&”的区别就可以使用了,特别注意&操作,前后顺序,有没有加(),结果是不同的。以及拥有全部权限的另类算法,应该是1111(4个1),即10000(5位)-1.
当然普遍写法是直接|或者&然后判断==0?,就不会写一个函数来增加多余的部分的。所以上面这个函数显得突兀,只是为了直观理解而写的。
六.自定义特性
特性类的后缀要以Attribute结尾,需要继承自System.Attribute,一般情况下声明为sealed。
示例如下:注意使用的时候自动去除Attribute后缀
获取内容的方式:
1 | public static class TestMyMethod |
注意当特性放在不同的地方时获取方式不同,类中的字段是class.GetProperties()下的内容,然后才可以 Attribute.GetCustomAttributes
注意当命名不规范的时候,是不会自动裁剪后缀的:
当然还有C++和其他dll导入时的特性,xml编辑的特性,以及ORM特性等等,由于那些的主题应该是对应的内容,特性只是一个小小的标记,所以那部分的内容到时放在具体项目中分享。
本文测试程序工程可以从git直接获取:
git代码库: Codes