特性
添加特性
特性是一种用于给代码添加额外信息的声明性标签。
在修饰的元素之前,用方括号声明特性。
[Obsolete("过时的类")]
public class MyClass1
{
}
需要声明多个特性时,可以使用多个方括号,也可以在一个方括号里使用逗号隔开。
使用无参构造器的特性可以省略圆括号。
[Serializable, Obsolete("过时的类")]
public class MyClass2
{
}
[Serializable][Obsolete("过时的类")]
public class MyClass3
{
}
特性成员赋值
特性只有一个声明,没有后续的执行语句。
要为特性的成员赋值,使用命名参数在构造器里直接指定
[Obsolete("过时的类",DiagnosticId ="GPT")]
public class MyClass4
{
}
预定义特性
特性可以通过反射获取。
而你的编译器一直在观察你的代码,可以直接和一些特性交互。
以下是一些预定义的特性,编译器会对他们做出响应。
已过时
Obsolete
特性用于标记已经过时的代码元素,不建议使用。
它可以接受一个字符串参数,用于说明过时的原因和替代方案。
它还可以接受一个布尔值参数,用于指示是否将使用过时元素视为编译错误
class MyClass5
{
[Obsolete("此方法已经过时。请使用NewMethod", false)]
public void OldMethod()
{
}
public void NewMethod()
{
}
}
获取调用者信息
Caller Info特性是一组预定义的特性,用于获取调用者的信息,如文件路径、行号、成员名等 。
它们可以应用于可选的方法参数,当方法被调用时,编译器会自动为这些参数赋值。
这些特性有助于提供更详细的诊断和跟踪信息,而不需要显式传递参数。
CallerFilePathAttribute
:获取源文件的完整路径,包含文件名和扩展名。CallerLineNumberAttribute
:获取源文件中调用方法的语句所在的行号。CallerMemberNameAttribute
:获取调用方法的成员名,如方法名、属性名、事件名等。
例如,下面的代码定义了一个TraceMessage方法,它有三个可选的参数,分别使用了上述三个特性。
当这个方法被其他地方调用时,编译器会自动为这些参数赋值为调用者的信息。
using System;
using System.Runtime.CompilerServices;
public class Trace
{
public static void TraceMessage(string message,
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0,
[CallerMemberName] string memberName = "")
{
Console.WriteLine($"Message: {message}");
Console.WriteLine($"File path: {filePath}");
Console.WriteLine($"Line number: {lineNumber}");
Console.WriteLine($"Member name: {memberName}");
}
}
下面的代码演示了如何在不同的地方调用TraceMessage方法,并输出不同的调用者信息。
public class Program
{
public static void Main()
{
Trace.TraceMessage("Hello from Main");
Foo();
var bar = new Bar();
bar.Baz();
}
public static void Foo()
{
Trace.TraceMessage("Hello from Foo");
}
}
public class Bar
{
public void Baz()
{
Trace.TraceMessage("Hello from Baz");
}
}
输出结果如下:
Message: Hello from Main
File path: C:\Users\user\source\repos\TraceDemo\Program.cs
Line number: 11
Member name: Main
Message: Hello from Foo
File path: C:\Users\user\source\repos\TraceDemo\Program.cs
Line number: 16
Member name: Foo
Message: Hello from Baz
File path: C:\Users\user\source\repos\TraceDemo\Program.cs
Line number: 26
Member name: Baz
语言格式
某些特殊的语言格式,比如json,xml,正则表达式,在VS中有特殊的提示和语法着色。
使用语言格式特性,作用于函数参数,可以在传入实参时激活这种提示。
void Json([StringSyntax(StringSyntaxAttribute.Json)] string json ) { }
void Regex([StringSyntax(StringSyntaxAttribute.Regex)] string regex ) { }
外部方法
外部方法必须使用DllImport特性,且必须添加static extern修饰符
外部方法可以允许你使用已经编译好的dll文件里面的方法。
class Extern
{
[DllImport("user32.dll", EntryPoint = "MessageBoxA")]
public static extern int MessageBox(int hwnd, string msg, string caption, int type);
}
Extern.MessageBox(0, "你调用了windos自带的dll,并弹出了一个窗口", "一个窗口", 0);
自定义特性
特性是一种继承自Attribute类的类,它可以具有类的所有功能,
但是通常用于给其他代码元素(如类、方法、属性等)添加一些额外的信息或行为。
class TipsAttribute : Attribute
{
public string Tips;
}
可赋值成员
特性类中可以定义一些公共的字段或属性,用于存储特性的数据或配置。
这些成员可以在应用特性时,使用命名参数的语法进行赋值。
但是,只能使用常量表达式作为赋值的源,不能使用需要运行时计算或构造的值。
[Tips(Tips = "你好")]
class MyClass6
{
}
特性的构造过程
特性类和普通类一样,都需要通过构造函数来初始化。
不同的是,特性类的构造函数每次获取特性时都会被调用,而不是只在第一次获取时调用。
这意味着,如果特性类的构造函数中包含一些随机或不确定的逻辑,
那么多次获取同一个特性可能会得到不同的结果。
另外,一个特性类也是一个普通的类。
可以直接通过new
来创建它的实例,而不一定要应用到某个代码元素上。
TipsAttribute tp = new TipsAttribute();
tp.Tips = "你好";
尽管没有这方面的限制,但是为了保持特性类的简洁和清晰,
建议只在特性类中定义一些常量或只读的成员。
特性的简易名字
一个特性类通常以Attribute
结尾。但在应用特性时,可以省略结尾的Attribute
。
(只有应用特性时可以省略。其他情况下,如继承、声明、获取时都必须使用完整的名字)。
[Tips]
class MyClass7
{
}
如果要强调(或是在省略后有重名类)特性的名字就是原本这样,不是省略后的结果。
那么需要在前面加@
[@Tips2]
class Test { }
class Tips2Attribute : Attribute
{
public string Tips;
}
class Tips2 : Attribute
{
public string Tips;
}
限制特性
你可以通过应用系统提供的AttributeUsageAttribute
特性,
来指定你的特性类只能应用到某些代码元素上,
编译器会根据这个特性来检查你是否正确地使用了自定义特性。
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
class Tips3Attribute : Attribute
{
public string Tips;
}
AttributeUsageAttribute
构造函数接受一个枚举参数,表示可以应用自定义特性的目标类型。
可以使用位运算符来组合多个目标类型。
AllowMultiple
属性表示是否允许同一个代码元素上有多个相同的自定义特性,默认是false
。
Inherited
属性表示当有这个自定义特性的类被继承时,是否会将这个自定义特性传递给子类,默认是true
。
[Flags]
public enum AttributeTargets
{
Assembly = 1, // 可以应用到程序集上
Module = 2, // 可以应用到模块上,模块指的是可执行文件(.dll或.exe),而不是标准模块
Class = 4, // 可以应用到类上
Struct = 8, // 可以应用到结构体上,即值类型
Enum = 16, // 可以应用到枚举上
Constructor = 32, // 可以应用到构造函数上
Method = 64, // 可以应用到方法上
Property = 128, // 可以应用到属性上
Field = 256, // 可以应用到字段上
Event = 512, // 可以应用到事件上
Interface = 1024, // 可以应用到接口上
Parameter = 2048, // 可以应用到参数上
Delegate = 4096, // 可以应用到委托上
ReturnValue = 8192, // 可以应用到返回值上
GenericParameter = 16384, // 可以应用到泛型参数上,目前只有C#、中间语言(MSIL)和发出的代码支持这个特性
All = 32767 // 可以应用到任何代码元素上
}
// 应用到程序集上
[assembly: Tips3]
// 应用到模块上
[module: Tips3]
// 应用到类上,应用到泛型参数上
[Tips3]
public class Foo<[Tips3] T>
{
// 应用到方法上,应用到返回值上,应用到参数上
[Tips3] [return: Tips3]
public int Plugh([Tips3] int x)
{
return 0;
}
// 应用到属性上
[Tips3]
public int Baz { get; set; }
// 应用到字段上
[Tips3]
public string Qux;
// 应用到事件上
[Tips3]
public event EventHandler Quux;
}