C#笔记——命名空间和程序集、反射和特性
本文最后更新于:2023年10月3日 下午
引用其他程序集
在编写自己的程序时,我们有时想要使用别人写好的库里的类和方法,比如python
中我们会先用pip
命令安装想要的库,再使用import
语句引入,那在C#
中应该如何引入呢?
首先要知道引入的是类库,而这些类库的文件名通常以.dll
结尾(很熟悉不是吗)。
其次要使用类库,就需要给编译器一个到该程序集的引用,给出程序集的名称和位置。
在Visual Studio
中,右键Solution Explorer
下的对应项目名称下的References
,并点击Add Reference
就可以为该项目添加类库引用了。
当然,引用类库难免会出现多个类库中存在相同名称的类的情况,这个时候如果不在程序中指明我们使用的是哪个类库的该类的话,编译器就会报错,这个时候,命名空间就该登场啦。
命名空间
基本概念
其实我们每个cs
文件中的最外边的namespace
语句就是创建命名空间语法:namespace NamespaceName
。
我们可以通过命名空间来区别不同命名空间下拥有相同名字的成员,如:MyNamespace.Point
和YourNamespace.Point
,从而避免类库冲突等情况。
命名空间拥有一些命名指南,就像编码规范那样,遵循指南可以让代码拥有良好的可读性:
- 以公司(或个人)名称开头
- 在公司(或个人)之后跟着技术名称
- 不要与类或类型名相同
例如:namespace Maple.Media
和namespace Maple.Games
命名空间跨文件伸展
命名空间不是封闭的,这意味着可以在该源文件的后面或另一个源文件中再次声明它(像不像partial class
),以对它增加更多的类型声明。而我们可以源文件编译成单个程序集,也可以编译成单独的程序集。
using
指令
using
命名空间指令
我们可以通过在源文件的顶端放置using
命名空间指令以避免使用长名称,比如常见的:using System
,这样在写代码的时候我们就可以直接Console.WriteLine(" ")
而不是System.Console.WriteLine(" ")
了,因为using
命名空间指令会通知编译器你将要使用来自某个指定命名空间的类型。
using
别名指令
对于using
还有另外一个作用就是通过它来为命名空间或类取别名,类似于python
的import xxx as alias
。例如:using SC = System.Console
,然后可以直接SC.WriteLine(" ")
。
using static
指令
可以使用using static
指令引用给定命名空间中的特定类、结构体或枚举,这样就可以不带任何前缀地访问该类、结构体或枚举的静态成员。比如using static System.Math
,然后我们就可以直接调用一系列数学方法如var squareRoot = Sqrt(16)
了。
需要注意的是,using static
指令指定的类本身可以不是静态的,而它也仅仅引入指定类、结构体或枚举的静态成员
反射
基本概念
反射,即:运行中的程序查看本身的元数据或其他程序的元数据的行为。而元数据,则是有关程序及其类型的数据,它们保存在程序的程序集中。要反射数据,我们可以使用System.Reflection
命名空间中的类和System.Type
。
Type
类
BCL(Base Class Library) 声明了一个叫作Type
的类,它被设计用来包含类型的特征。使用这个类的对象就获取程序使用的类型的信息(通过类中定义的属性和方法)。
System.Type
类的部分成员
成员 | 成员类型 | 描述 |
---|---|---|
Name | 属性 | 返回类型的名字 |
Namespace | 属性 | 返回包含类型声明的命名空间 |
Assembly | 属性 | 返回声明类型的程序集 |
GetFields | 方法 | 返回类型的字段列表 |
GetProperties | 方法 | 返回类型的属性列表 |
GetMethods | 方法 | 返回类型的方法列表 |
获取Type
对象
要获取Type
对象,可以使用实例对象的GetType
方法和typeof
运算符和类名来获取Type
对象。
GetType
方法
object
类型包含了一个叫做GetType
的方法,它返回对实例的Type
对象的引用。
1 |
|
typeof
运算符
typeof
运算符接受一个操作数,这个操作数应是类型名,返回的结果是Type
对象的引用。
1 |
|
特性
基本概念
特性(attribute)是一种允许我们向程序的程序集添加元数据的语言结构。特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集。
将应用了特性的程序结构(program construct)叫作目标(target)。
设计用来获取和使用元数据的程序叫作特性的消费者(consumer)。
命名规范
特性名使用 Pascal 命名法并且以 Attribute 为后缀结尾。当为目标应用特性时,可以不使用后缀。比如命名时是:MyAttributeAttribute
,使用时是[MyAttribute]
。
应用特性
通过在结构前放置特性片段来应用特性,特性片段由方括号包围特性名,有时候也包括参数列表。
可以为单个结构应用多个特性,有两种方式:
- 独立的特性片段一个接一个。通常,它们彼此叠加,位于不同的行中。
- 单个特性片段,特性之间使用逗号分隔。
可以显式地标注特性,让特性应用到特殊的目标结构,例如:[method: MyAttribute(parameter)]
。可用的特性目标有10个,分别是:event
、method
、property
、type
、assembly
、field
、param
、return
、typevar
、module
。
应用了特性的结构称为被特性装饰(decorated 或 adorned)。
建议了解的预定义的保留特性
Obsolete
特性,Conditional
特性,CallerXxx
特性(调用者信息特性),DebuggerStepThrough
特性(帮助调试),Serializable
特性。
自定义特性
特性就是特殊的类。要自定义一个特性,请遵循命名规范,然后再让其派生自System.Attribute
类即可。
为安全起见,通常建议声明一个sealed
的特性类。
由于特性持有目标的信息,所有特性类的共有成员只能是:字段、属性和构造函数。之所以应用特性时可以带参也可以不带参,是因为我们其实是在指定应该使用哪个构造函数来创建特性的实例。
既然我们可以为类应用特性,自然也能够为特性类应用特性。其中有一个很重要的预定义特性AttributeUsage
,我们可以用它来限制将某个特性用在某个目标类型上(其实这里像前面提到的“显式地标注特性”),例如:[ AttributeUsage( AttributeTarget.Method ) ]
。
检查某个特性是否应用到了某个类上
我们可以使用Type
对象的IsDefined
方法来检测某个特性是否应用到了某个类上。此方法接受两个参数,第一个参数接受需要检查的特性的Type
对象;第二个参数是bool
类型的,它指示是否搜索类的继承树来查找这个特性。例如:bool isDefined = new MyClass().GetType().IsDefined(typeof(MyAttributeAttribute), false);
。
希望本文章能够帮到您~