C# 预处理器指令,特性,反射

日之朝矣

预处理器指令

预处理器指令(Preprocessor Directives)是编程语言中的一类指令,用于在编译之前对代码进行预处理。在C#中,预处理器指令主要用于控制编译过程,例如条件编译、宏定义等。

C# 预处理器指令列表

下表列出了 C# 中可用的预处理器指令:

指令描述
#define定义一个符号,可以用于条件编译。
#undef取消定义一个符号。
#if开始一个条件编译块,如果符号被定义则包含代码块。
#elif如果前面的 #if#elif 条件不满足,且当前条件满足,则包含代码块。
#else如果前面的 #if#elif 条件不满足,则包含代码块。
#endif结束一个条件编译块。
#warning生成编译器警告信息。
#error生成编译器错误信息。
#region标记一段代码区域,可以在IDE中折叠和展开这段代码,便于代码的组织和阅读。
#endregion结束一个代码区域。
#line更改编译器输出中的行号和文件名,可以用于调试或生成工具的代码。
#pragma用于给编译器发送特殊指令,例如禁用或恢复特定的警告。
#nullable控制可空性上下文和注释,允许启用或禁用对可空引用类型的编译器检查。

预处理指令使用

#define#undef

  • #define:用于定义符号。
  • #undef:用于取消定义符号。

#if#elif#else#endif

  • #if:根据条件编译代码块。
  • #elif#else if 的缩写,与 #if 联合使用。
  • #else:定义 #if#elif 条件不满足时的代码块。
  • #endif:结束条件编译块。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 注意,在使用Visual Studio进行调试时,Debug是默认被定义的
// 如果不需要,可以在菜单栏切换为Release模式,并选择不调试执行
// 或者使用 #undef DEBUG
#define DEBUG
#define VC_V10
using System;
public class TestClass
{
public static void Main()
{

#if (DEBUG && !VC_V10)
Console.WriteLine("DEBUG is defined");
#elif (!DEBUG && VC_V10)
Console.WriteLine("VC_V10 is defined");
#elif (DEBUG && VC_V10)
Console.WriteLine("DEBUG and VC_V10 are defined");
#else
Console.WriteLine("DEBUG and VC_V10 are not defined");
#endif
Console.ReadKey();
}
}

#warning#error

  • **#warning**:生成编译警告。
  • **#error**:生成编译错误,阻止编译过程。
1
2
#warning This is a warning message. // CS1030:#警告:“This is a warning message”
#error This is an error message. // CS1029:#错误:"This is an error message"

#region#endregion

  • #region#endregion 用于代码折叠和组织,可以在IDE中折叠和展开代码块,提升代码的可读性。
1
2
3
4
5
6
#region MyRegion
public void MyMethod()
{
//...
}
#endregion

#pragma

  • #pragma 指令用于修改编译器的行为,常见的有 **#pragma warning**,用于禁用或启用特定的警告。
1
2
3
#pragma warning disable 0168 // 禁用 "已声明但从未使用的变量" 警告
int unusedVariable;
#pragma warning restore 0168 // 恢复警告

#line

  • #line 指令可以更改编译器报告的文件名和行号,常用于代码生成工具,以确保错误信息对应于生成的源文件。
1
2
3
#line 100 "Programs.cs"
Console.WriteLine("该行在 CustomFileName.cs 中被报告为第100行");
#line default // 重置为默认文件名和行号

特性

特性(Attribute)是一种用于在元数据中添加信息的声明性标签。特性可以附加到代码的各个部分,如类、方法、属性、字段、程序集等,以提供关于代码行为的额外信息。特性不会直接影响代码的运行,但可以被工具、编译器或运行时用来修改行为或执行特定任务。

特性是一种继承自System.Attribute类的类,表示在程序集中添加的声明性信息。

使用特性

特性通常放在声明前一行,用方括号包围[]

1
2
3
4
5
6
7
8
9
[Serializable] // 标记为可被序列化
public class MyClass
{
[Obsolete("This method is obsolete. Use NewMethod instead.")] // 标记为废弃
public void OldMethod() { }

[MyCustomAttribute("Custom Value")] // 自定义特性
public int MyProperty { get; set; }
}

常用内置特性

C# 中提供了许多内置特性,常见的包括:

  • [Serializable]: 指定类可以被序列化。
  • [Obsolete]: 标记已过时的程序元素,并可选择显示警告或错误信息。
  • [DllImport]: 用于导入非托管的 DLL 函数,通常用于调用 Win32 API。
  • [Conditional]: 指示编译器仅在指定符号定义时才调用某个方法。
  • [AttributeUsage]: 定义特性可以应用到哪些程序元素,以及它们是否可以多次应用。

这些都是缩写,省略了名称后的Attribute,如[ObsoleteAttribute]

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#undef DEBUG // 取消定义 DEBUG
using System;
using System.Diagnostics;
namespace _7.博客用
{
[Serializable] // 标记该类可被序列化
internal class Program
{
[Obsolete("此方法已过时,请使用其他方法代替")] // 标记该方法已过时
static void Method1() { Console.WriteLine("过时的Method1"); }

[Conditional("DEBUG")] // 标记当 DEBUG 被定义时才调用
static void Method2() { Console.WriteLine("Method2 被调用"); }
static void Main(string[] args)
{
Method1(); // 警告:CS0618:"Program.Mehtod1()"已过时:"此方法已过时,请使用其他方法代替"
Method2();
Console.WriteLine("hello");
Console.ReadLine();
}
}
}

执行结果

1
2
过时的Method1
hello

WinForm常用内置特性

这些内置特性一般配合用户自定义控件使用

  • [Description]:提供属性或事件的简短描述,该描述会在属性窗口中显示为提示。

  • [Category]:指定属性在属性窗口中所属的分类。

  • [Browsable]:控制属性是否在属性窗口(Property Grid)中可见。

  • [DefaultValue]:指定属性的默认值。设计器使用此值来决定是否需要生成代码来设置属性的值。

  • [Editor]:指定属性在属性窗口中使用的编辑器。通常用于需要自定义编辑器的复杂属性。

    1
    2
    [Editor(typeof(System.Drawing.Design.FontEditor), typeof(System.Drawing.Design.UITypeEditor))]
    public Font CustomFont { get; set;}

自定义特性

可以创建自定义特性,通过继承 System.Attribute 类来实现。自定义特性可以包含构造函数、属性和方法。自定义特性的名称一般以Attribute作为结尾

1
2
3
4
5
6
7
8
9
10
11
// 通过AttributeUsage,限定该特性只能用在类和方法上,Inherited表示该特性能否被派生类继承,默认false
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false)]
public class MyCustomAttribute : Attribute
{
public string Description { get; }

public MyCustomAttribute(string description)
{
Description = description;
}
}

读取特性

特性的读取需要用到反射(Reflection),下面的示例是一个从自定义特性到读取特性的完整示例

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
// 使用反射获取特性添加的元数据

// 获取类型
var t = typeof(Rectangle);

// 通过类型获取类型上的特性
var classAttributes = t.GetCustomAttributes(false);
foreach (var attr in classAttributes)
{
var dbi = attr as DebugInfoAttribute;
if (dbi is null) continue;
Console.WriteLine($"class:{t.Name}:\n\tBugNo:{dbi.BugNo} " +
$"\n\tDeveloperName:{dbi.DeveloperName}" +
$"\n\tMessage:{dbi.Message}" +
$"\n\tLastReview:{dbi.LastReview}");
}

// 通过类型获取其构造函数进而获取构造函数上的特性
var ctors = t.GetConstructors();
foreach (var ctor in ctors)
{
var ctroAttributes = ctor.GetCustomAttributes(false);
foreach (var attr in ctroAttributes)
{
var dbi = attr as DebugInfoAttribute;
if (dbi is null) continue;
Console.WriteLine($"{ctor.Name}:\n\tBugNo:{dbi.BugNo} " +
$"\n\tDeveloperName:{dbi.DeveloperName}" +
$"\n\tMessage:{dbi.Message}" +
$"\n\tLastReview:{dbi.LastReview}");
}
}

// 通过类型获取公共字段,并获取其上的特性信息
var feilds = t.GetFields();
foreach (var feild in feilds)
{
var feildAttr = feild.GetCustomAttributes(false);
foreach (var attr in feildAttr)
{
var dbi = attr as DebugInfoAttribute;
if (dbi is null) continue;

Console.WriteLine($"feild:{feild.Name}:\n\tBugNo:{dbi.BugNo} " +
$"\n\tDeveloperName:{dbi.DeveloperName}" +
$"\n\tMessage:{dbi.Message}" +
$"\n\tLastReview:{dbi.LastReview}");
}
}

// 通过当前类型获取其所有公共方法,进而获取每个方法上的特性信息
var methods = t.GetMethods();
foreach (var method in methods)
{
var methodAttr = method.GetCustomAttributes(false);
foreach (var attr in methodAttr)
{
var dbi = attr as DebugInfoAttribute;
if (dbi is null) continue;
Console.WriteLine($"method:{method.Name}:\n\tBugNo:{dbi.BugNo} " +
$"\n\tDeveloperName:{dbi.DeveloperName}" +
$"\n\tMessage:{dbi.Message}" +
$"\n\tLastReview:{dbi.LastReview}");
}
}
Console.ReadLine();
}
}


// 自定义特性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
class DebugInfoAttribute : Attribute
{
public DebugInfoAttribute(int bugNo, string developerName, string message, string lastReview)
{
BugNo = bugNo;
DeveloperName = developerName;
Message = message;
LastReview = lastReview;
}
public int BugNo { get; }
public string DeveloperName { get; }
public string Message { get; }
public string LastReview { get; }
}

// 使用自定义特性
[DebugInfo(55, "RZZY", "class test Message", "2024/08/17")]
[DebugInfo(55, "日之朝矣", "class test Message2", "2024/08/15")]
class Rectangle
{
[DebugInfo(23, "dn", "field length test Message", "2024/08/11")]
public double length;
[DebugInfo(23, "few", "field width test Message", "2024/08/11")]
public double width;

[DebugInfo(23, "fs", "ctor test Message", "2024/08/11")]
public Rectangle(double l, double w)
{
length = l;
width = w;
}

[DebugInfo(55, "za", "Return type mismatch", "2024/08/16")]
public double GetArea()
{
return length * width;
}

[DebugInfo(56, "jji", "method test Message", "2024/08/15")]
public void Display()
{
Console.WriteLine("Length: {0}", length);
Console.WriteLine("Width: {0}", width);
Console.WriteLine("Area: {0}", GetArea());
}
}
}

反射

反射(Reflection)是C#中的一种强大功能,它允许程序在运行时动态地检查和操作对象的类型、属性、方法和其他元数据。通过反射,程序可以获取关于类和对象的信息,并执行与类型相关的操作,而无需在编译时了解这些类型的确切名称或结构。

优缺点

优点:

  • 1、反射提高了程序的灵活性和扩展性。
  • 2、降低耦合性,提高自适应能力。
  • 3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。

缺点:

  • 1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
  • 2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。

为了提高性能,可以:

  • 缓存反射结果: 将 TypeMethodInfoPropertyInfo 等对象缓存起来,避免重复获取。
  • 尽量减少反射调用次数: 在设计时尽量减少使用反射的需求,或将反射用于初始化阶段,然后使用直接调用。

反射的核心类

反射功能主要由 System.Reflection 命名空间中的类提供。以下是一些常用的反射类:

  • Assembly: 表示一个程序集,提供加载程序集和检索程序集信息的方法。
  • Type: 表示类型(类、接口、结构体等),提供获取类型信息的方法,如获取属性、方法、字段等。
  • MethodInfo: 提供有关方法的信息,并允许调用方法。
  • PropertyInfo: 提供有关属性的信息,并允许获取或设置属性的值。
  • FieldInfo: 提供有关字段的信息,并允许获取或设置字段的值。
  • ConstructorInfo: 提供有关构造函数的信息,并允许调用构造函数创建实例。

反射的基本操作

  1. 加载程序集

    1
    Assembly assembly = Assembly.Load("AssemblyName");
  2. 获取类型信息

    1
    2
    3
    4
    5
    6
    7
    // 通过程序集获取
    Type type1 = assembly.GetType();
    // 使用typeof关键字获取
    Type type2 = typeof(Rectangle);
    // 通过Object.GetType()获取
    Rectangle r = new Rectangle(2, 3);
    Type type3 = r.GetType();
  3. 获取属性,方法,字段等信息

    1
    2
    3
    4
    5
    6
    7
    PropertyInfo? propertyInfo = type1.GetProperty("propertyName");
    FieldInfo? fieldInfo = type1.GetField("fieldName");
    MethodInfo? methodInfo = type1.GetMethod("methodName");

    PropertyInfo[]? propertyInfos = type1.GetProperties();
    FieldInfo[]? fieldInfos = type1.GetFields();
    MethodInfo[]? methodInfos = type1.GetMethods();
  4. 创建实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 使用无参构造函数
    object? instance1 = Activator.CreateInstance(type1);

    // 使用有参构造函数
    ConstructorInfo? constructorInfo = type1.GetConstructor(new Type[] { typeof(string), typeof(int) });
    object? instance2 = constructorInfo?.Invoke(new object[] { "parameter1", 42 });

    // 通过程序集创建实例
    Assembly.Load("AssemblyName").CreateInstance("typeName");
  5. 调用方法

    1
    2
    MethodInfo? methodInfo1 = type1?.GetMethod("methodName");
    methodInfo?.Invoke(/* 要调用方法的对象*/new object(), new object[] { /* 参数列表 */ });
  6. 获取和设置属性值

    1
    2
    3
    4
    5
    PropertyInfo? propertyInfo1 = type1?.GetProperty("propertyName");
    // 获取属性值
    object? propertyValue = propertyInfo1?.GetValue(instance1);
    // 设置属性值
    propertyInfo1?.SetValue(instance1, "new value");
  7. 获取和设置字段值

    1
    2
    3
    4
    5
    FieldInfo? fieldInfo1 = type1?.GetField("fieldName");
    // 获取字段值
    object? fieldValue = fieldInfo1?.GetValue(instance1);
    // 设置字段值
    fieldInfo1?.SetValue(instance1, "new value");
  8. 获取静态成员

    1
    2
    3
    4
    5
    FieldInfo? staticFieldInfo = type1?.GetField("saticFieldName",BindingFlags.Static|BindingFlags.Public);

    PropertyInfo? staticPropertyInfo = type1?.GetProperty("saticPropName",BindingFlags.Static|BindingFlags.Public);

    MethodInfo? staticMethodInfo = type1?.GetMethod("staticMethodName",BindingFlags.Static | BindingFlags.Public);

    在使用反射获取静态成员时,通常需要指定 BindingFlags.Static 以表明要获取静态成员。此外,还可以使用其他 BindingFlags 来精确控制反射行为,例如:

    • **BindingFlags.Public**:获取公共成员。
    • **BindingFlags.NonPublic**:获取非公共(私有或保护)成员。
    • **BindingFlags.FlattenHierarchy**:在获取静态成员时,包含继承自基类的公共和受保护的静态成员。
  9. 获取元数据,见读取特性

附录

元数据

元数据(Metadata)是关于数据的数据,或描述数据的数据。在编程和计算机科学中,元数据提供了有关其他数据的信息或描述。它不直接影响数据的内容或行为,但为系统、工具或人类理解、管理和使用这些数据提供了上下文和背景。

元数据的例子

  • 文件系统中的元数据: 在文件系统中,文件的元数据包括文件名、大小、创建日期、修改日期、文件类型、权限等。这些信息帮助操作系统和用户管理和组织文件,但并不是文件的实际内容。
  • 数据库中的元数据: 数据库的元数据包括表的结构(如列的名称和数据类型)、索引、关系约束、存储过程定义等。元数据使数据库管理系统能够理解如何存储和检索数据。
  • HTML 元数据: 在网页中,HTML 的 <meta> 标签用于指定页面的描述、关键字、作者、以及字符集等信息。搜索引擎和浏览器使用这些信息来优化搜索结果和显示页面内容。
  • 编程中的元数据: 在编程中,元数据可以是代码中的注释、特性(如前面提到的 C# 特性)、文档字符串等。这些元数据用于提供有关代码结构、行为或目的的附加信息。

C# 中的元数据

在 C# 中,特性(Attributes)就是元数据的一种形式。它们为代码的某些部分(如类、方法、属性等)提供附加的信息。例如:

1
2
[Obsolete("This method is obsolete. Use NewMethod instead.")]
public void OldMethod() { }

这里的 [Obsolete] 特性是元数据,提供了关于 OldMethod 的附加信息,告知编译器和开发者这个方法已经过时。

元数据的作用

  1. 描述数据: 提供对数据的描述或解释,使用户或系统了解数据的含义和使用方法。
  2. 数据管理: 帮助管理和组织数据,如通过描述数据的结构、格式和访问权限来管理文件、数据库或其他数据存储。
  3. 数据发现和检索: 通过提供关键词或描述,元数据可以帮助用户或系统更快地查找和检索数据。
  4. 编译器优化和代码分析: 编译器可以利用元数据优化代码或生成警告和错误消息。
  5. 运行时行为: 在某些情况下,元数据会影响代码的运行时行为。例如,序列化库可以通过读取元数据来决定如何序列化对象。

参考内容

参考内容:

Microsoft Learn .NET API

菜鸟教程 - C# 教程

ChatGPT

  • 标题: C# 预处理器指令,特性,反射
  • 作者: 日之朝矣
  • 创建于 : 2024-08-18 17:08:07
  • 更新于 : 2024-08-18 09:25:27
  • 链接: https://blog.rzzy.fun/2024/08/18/csharp-preprocessor-directives-attributes-reflection/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论