C#中的特性(Attributes)是一种强大的元数据机制,允许开发者在代码中添加声明性信息。这些信息可以在运行时通过反射(Reflection)进行访问和使用。特性可以应用于各种程序元素,如类、方法、属性、字段、参数等。
特性的定义和使用
定义自定义特性
要定义一个自定义特性,需要继承自System.Attribute类。可以通过构造函数传递参数来初始化特性。
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
public class MyCustomAttribute : Attribute
{
public string Description { get; }
public MyCustomAttribute(string description)
{
Description = description;
}
}
在上面的示例中,MyCustomAttribute特性有一个字符串类型的Description属性。AttributeUsage特性用于指定该特性可以应用于哪些程序元素(如类和方法),是否可以继承,以及是否允许多次应用。
应用自定义特性
定义好特性后,可以将其应用于类、方法等程序元素。
[MyCustomAttribute("This is a sample class.")]
public class SampleClass
{
[MyCustomAttribute("This is a sample method.")]
public void SampleMethod()
{
Console.WriteLine("SampleMethod executed.");
}
}
访问特性
可以使用反射来访问特性的信息。
using System;
using System.Reflection;
class Program
{
static void Main()
{
// 获取类的特性
var classAttributes = typeof(SampleClass).GetCustomAttributes(typeof(MyCustomAttribute), false);
foreach (MyCustomAttribute attr in classAttributes)
{
Console.WriteLine($"Class Attribute Description: {attr.Description}");
}
// 获取方法的特性
var methodAttributes = typeof(SampleClass).GetMethod("SampleMethod").GetCustomAttributes(typeof(MyCustomAttribute), false);
foreach (MyCustomAttribute attr in methodAttributes)
{
Console.WriteLine($"Method Attribute Description: {attr.Description}");
}
}
}
常用的内置特性
C#提供了许多内置特性,以下是一些常用的内置特性及其示例:
[Obsolete]
标记某个程序元素已过时,使用时会产生编译器警告或错误。
public class Example
{
[Obsolete("This method is obsolete. Use NewMethod instead.")]
public void OldMethod()
{
Console.WriteLine("OldMethod executed.");
}
public void NewMethod()
{
Console.WriteLine("NewMethod executed.");
}
}
[Serializable]
标记某个类可以被序列化。
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
[NonSerialized]
标记某个字段在序列化时应被忽略。
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
[NonSerialized]
private string password;
}
[DllImport]
用于调用非托管代码中的函数。
using System;
using System.Runtime.InteropServices;
public class Example
{
[DllImport("user32.dll")]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
}
[Conditional]
用于有条件地编译方法调用。
using System;
using System.Diagnostics;
public class Example
{
[Conditional("DEBUG")]
public void DebugOnlyMethod()
{
Console.WriteLine("This method is only called in debug mode.");
}
}
特性的高级用法
多个特性
可以将多个特性应用于同一个程序元素。
[Serializable]
[Obsolete("This class is obsolete.")]
public class OldClass
{
// ...
}
特性参数
特性可以接受位置参数和命名参数。
[MyCustomAttribute("This is a sample class.", AdditionalInfo = "Some additional info")]
public class SampleClass
{
// ...
}
特性继承
特性可以继承自其他特性,这样可以复用基类特性中的逻辑和属性。
using System;
public class BaseAttribute : Attribute
{
public string BaseInfo { get; set; }
}
public class DerivedAttribute : BaseAttribute
{
public string DerivedInfo { get; set; }
}
[DerivedAttribute(BaseInfo = "Base information", DerivedInfo = "Derived information")]
public class SampleClass
{
// ...
}
在上面的示例中,DerivedAttribute继承自BaseAttribute,并添加了一个新的属性DerivedInfo。在应用特性时,可以同时设置基类和派生类的属性。
特性参数
特性可以接受位置参数和命名参数。位置参数在构造函数中定义,而命名参数则是通过属性或字段来定义的。
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyCustomAttribute : Attribute
{
public string Description { get; }
public string AdditionalInfo { get; set; }
public MyCustomAttribute(string description)
{
Description = description;
}
}
[MyCustomAttribute("This is a sample class.", AdditionalInfo = "Some additional info")]
public class SampleClass
{
// ...
}
在上面的示例中,Description是一个位置参数,通过构造函数传递,而AdditionalInfo是一个命名参数,通过属性设置。
特性的高级用法
多个特性
可以将多个特性应用于同一个程序元素。多个特性可以通过逗号分隔。
[Serializable]
[Obsolete("This class is obsolete.")]
public class OldClass
{
// ...
}
特性目标
可以显式指定特性应用于哪个程序元素(如方法、属性、字段等)。
using System;
public class Example
{
[method: Obsolete("This method is obsolete.")]
public void OldMethod()
{
// ...
}
[property: Obsolete("This property is obsolete.")]
public string OldProperty { get; set; }
}
特性与反射
通过反射可以动态地获取和操作特性的信息,这在框架和库的开发中非常有用。
using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyCustomAttribute : Attribute
{
public string Description { get; }
public MyCustomAttribute(string description)
{
Description = description;
}
}
[MyCustomAttribute("This is a sample class.")]
public class SampleClass
{
[MyCustomAttribute("This is a sample method.")]
public void SampleMethod()
{
Console.WriteLine("SampleMethod executed.");
}
}
class Program
{
static void Main()
{
// 获取类的特性
var classAttributes = typeof(SampleClass).GetCustomAttributes(typeof(MyCustomAttribute), false);
foreach (MyCustomAttribute attr in classAttributes)
{
Console.WriteLine($"Class Attribute Description: {attr.Description}");
}
// 获取方法的特性
var methodAttributes = typeof(SampleClass).GetMethod("SampleMethod").GetCustomAttributes(typeof(MyCustomAttribute), false);
foreach (MyCustomAttribute attr in methodAttributes)
{
Console.WriteLine($"Method Attribute Description: {attr.Description}");
}
}
}
在上面的示例中,通过反射获取了类和方法的自定义特性,并输出了特性的描述信息。
常见的内置特性
除了前面提到的内置特性,C#还提供了许多其他有用的内置特性:
[Flags]:用于将枚举类型视为位域(即每个值可以是多个标志的组合)。[DefaultValue]:指定属性或参数的默认值。[CallerMemberName]、[CallerFilePath]、[CallerLineNumber]:用于获取调用方法的成员名称、文件路径和行号,常用于日志记录和调试。
当然,以下是关于C#特性(Attributes)的进一步详细说明,包括如何使用[CallerMemberName]、[CallerFilePath]和[CallerLineNumber]特性来获取调用方法的成员名称、文件路径和行号。
使用 Caller 信息特性
[CallerMemberName]、[CallerFilePath]和[CallerLineNumber]特性非常有用,特别是在日志记录和调试中。它们允许你在方法参数中自动捕获调用者的信息,而无需显式传递这些信息。
示例
using System;
using System.Runtime.CompilerServices;
public class Logger
{
public void Log(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
Console.WriteLine($"Message: {message}");
Console.WriteLine($"Member Name: {memberName}");
Console.WriteLine($"File Path: {filePath}");
Console.WriteLine($"Line Number: {lineNumber}");
}
}
public class Example
{
private static Logger logger = new Logger();
public void DoSomething()
{
logger.Log("Doing something...");
}
public void DoSomethingElse()
{
logger.Log("Doing something else...");
}
}
class Program
{
static void Main()
{
Example example = new Example();
example.DoSomething();
example.DoSomethingElse();
}
}
在上面的示例中,Logger类的Log方法使用了[CallerMemberName]、[CallerFilePath]和[CallerLineNumber]特性。这些特性会自动捕获调用Log方法的成员名称、文件路径和行号。运行程序时,输出将显示调用Log方法的具体信息。
Flags 特性
[Flags]特性用于将枚举类型视为位域,这样每个值可以是多个标志的组合。它通常用于表示一组可以组合的选项。
示例
using System;
[Flags]
public enum FileAccess
{
Read = 1,
Write = 2,
Execute = 4
}
class Program
{
static void Main()
{
FileAccess access = FileAccess.Read | FileAccess.Write;
Console.WriteLine(access); // 输出: Read, Write
// 检查是否包含特定标志
bool canRead = (access & FileAccess.Read) == FileAccess.Read;
bool canWrite = (access & FileAccess.Write) == FileAccess.Write;
bool canExecute = (access & FileAccess.Execute) == FileAccess.Execute;
Console.WriteLine($"Can Read: {canRead}"); // 输出: Can Read: True
Console.WriteLine($"Can Write: {canWrite}"); // 输出: Can Write: True
Console.WriteLine($"Can Execute: {canExecute}");// 输出: Can Execute: False
}
}
在上面的示例中,FileAccess枚举使用了[Flags]特性,使其可以表示多个组合的选项。通过按位操作,可以检查和组合这些选项。
DefaultValue 特性
[DefaultValue]特性用于指定属性或参数的默认值。它在设计时和序列化时非常有用。
示例
using System;
using System.ComponentModel;
public class Settings
{
[DefaultValue("DefaultUser")]
public string Username { get; set; }
[DefaultValue(30)]
public int Timeout { get; set; }
public Settings()
{
// 应用默认值
foreach (var property in GetType().GetProperties())
{
var defaultValueAttribute = (DefaultValueAttribute)Attribute.GetCustomAttribute(property, typeof(DefaultValueAttribute));
if (defaultValueAttribute != null)
{
property.SetValue(this, defaultValueAttribute.Value);
}
}
}
}
class Program
{
static void Main()
{
Settings settings = new Settings();
Console.WriteLine($"Username: {settings.Username}"); // 输出: Username: DefaultUser
Console.WriteLine($"Timeout: {settings.Timeout}"); // 输出: Timeout: 30
}
}
在上面的示例中,Settings类的Username和Timeout属性使用了[DefaultValue]特性。在构造函数中,通过反射应用这些默认值。

2786

被折叠的 条评论
为什么被折叠?



