重庆熊猫 Loading

C#教程 - 特性(Attribute)

更新记录
转载请注明出处:
2022年9月23日 发布。
2022年9月10日 从笔记迁移到博客。

特性(Attribute)说明

An attribute is a declarative tag that is used to convey information to runtime about the behaviors of various elements like classes, methods, structures, enumerators, assemblies etc

Attributes are used for adding metadata, such as compiler instruction and other information such as comments, description, methods and classes to a program. The .Net Framework provides two types of attributes: the pre-defined attributes and custom built attributes.

特性本质和ref/out/virtual/override类似

都是将用户自定义的信息(Custom Information)添加到代码单元中

特性(Attribute)是一种允许我们通过编程向程序集添加元数据的语言结构

特性是保存程序结构信息的某种特殊的类

使用特性的程序结构叫做目标(target)

设计用来获得和使用元数据的程序叫做特性消费者(特性使用者)

.NET框架中预定义了许多特性,也可以自定义特性

所有的特性类派生自System.Attribute

程序结构可以是:assemblies, types, members, return values, parameters, generic type parameters
image

使用预定义的特性(Applying Attributes to Assemblies)

说明

使用了特性修饰的结构称为被特性修饰

注意:使用时可以省略或带上Attribute后缀

单个特性

单个特性-不带参数:

[SomeAttribute]
class SomeClass
{

}

单个特性-带参数:

[SomeAttribute(Arg1,arg2)]
class SomeClass
{

}

实例:

[XmlType ("Customer", Namespace="http://oreilly.com")]
public class CustomerEntity { ... }

实例:

[assembly: AssemblyFileVersion ("1.2.3.4")]

实例:

[field:NonSerialized]
public int MyProperty { get; set; }

实例:应用特性类到后备字段(Applying Attributes to Backing Fields)
注意:从C# 7.3开始支持

[field:NonSerialized]
public int MyProperty { get; set; }

使用多个特性修饰(Decorating with Multiple Attributes)

将多个特性放置在一个代码单元上(Specifying Multiple Attributes)

既可以单独放在一个中括号中,也可以并列使用,逗号分隔到

多个特性-堆叠:

[Serializabke]
[SomeName(arg1,arg2)]
class SomeClass
{

}

多个特性-逗号:

[Serializabke,SomeName(arg1,arg2)]
class SomeClass
{
    
}

实例:

[Serializable, Obsolete, CLSCompliant(false)]
public class Bar {...}

[Serializable] [Obsolete] [CLSCompliant(false)]
public class Bar {...}

[Serializable, Obsolete]
[CLSCompliant(false)]
public class Bar {...}

显式标注

显式标注将特性用在指定目标结构上

使用显式目标说明符即可

定义

class SomeClass
{
    [method:SomeAttrName(arg)]
    [return:SomeAttrName(arg)]
    public void Method()
    {

    }
}

显式目标说明符支持:

class       class<T>
struct      struct<T>
interface   interface<T>
delegate    delegate<T>
enum        enum<T>
event
field
method
property
param
return
module
assembly

全局特性

可以使用assembly和module目标名称来使用显式目标说明符把特性设置在程序集或模块级别

注意:

程序集级别的特性必须放在命名空间之外,一般放在AssemblyInfo.cs文件中

AssemblyInfo.cs文件通常包含有关公司、产品以及版权信息的元数据

比如:
image

预定义特性

[Obsolete]

标记为过时程序结构,在编译时,显示警告过时信息

参数:

第一个参数:String类型,表示要显示过时警告信息

第二个参数:Boolean表示,是否标记为错误而不仅仅是警告(可选)

实例:Obsolete不用参数 和 Obsolete单个参数

using System;
using System.Collections.Generic;

namespace ConsoleApp1
{
    class Program
    {
        [Obsolete]
        static void DoSomething1()
        {
            //do something
        }

        [Obsolete("该方法将在第 1.0.2 版本 后移除")]
        static void DoSomething2()
        {
            //do something
        }

        static void Main(string[] args)
        {
            DoSomething1();
            DoSomething2();

            //wait
            Console.ReadKey();
        }
    }
}

编译器将显示:
image

实例:多个参数

using System;
using System.Collections.Generic;

namespace ConsoleApp1
{
    class Program
    {
        [Obsolete("该方法已经移除,不可以使用", true)]
        static void DoSomething()
        {
            //do something
        }

        static void Main(string[] args)
        {
            DoSomething();

            //wait
            Console.ReadKey();
        }
    }
}

编译器将显示:
image

[Conditional]

条件特性(Conditional Attributes)

只有给定的预定义指令符号存在才进行编译

根据预处理指令中的符号定义指令确定程序结构调用是否可以使用

比如定义了#define Panda666预处理符号定义指令

class SomeClass
{
    [Conditional("Panda666")]
    public void SomeMethod()
    {

    }
}

这段代码中的方法只在该预处理符号定义存在时才可以使用成功

如果该预处理符号定义存在不存在,则忽略方法调用

实例:
image

实例:

#define DEBUG
using System;
using System.Diagnostics;
[Conditional("DEBUG")]
public class TestAttribute : Attribute {}

[CallerFilePath]

获得调用者文件路径(Caller Info Attributes)

注意:只能用于方法中的可选参数

实例:
image

[CallerLineNumber]

获得调用者代码行数(Caller Info Attributes)

注意:只能用于方法中的可选参数

注意:使用参数必须带有默认的参数值

实例:
image

[CallerMemberName]

调用者成员名称(Caller Info Attributes)

注意:只能用于方法中的可选参数

所在命名空间:

using System.Runtime.CompilerServices;

实例:运行后输出源文件的信息

using System;
using System.Runtime.CompilerServices;
class Program
{
    static void Main() => Foo();
    static void Foo (
        [CallerMemberName] string memberName = null,
        [CallerFilePath] string filePath = null,
        [CallerLineNumber] int lineNumber = 0)
    {
        Console.WriteLine (memberName);
        Console.WriteLine (filePath);
        Console.WriteLine (lineNumber);
    }
}

实例:
image

[DebuggerStepThrough]

告诉调试器在但不调试时不要进入该目标代码

注意:该特性位于System.Diagnostics命名空间

注意:该特性可以应用于:类、结构、构造函数、方法或访问器

实例:
image

[CLSCompliant]

检查程序结构是否符合CLS

提示:符合CLS的程序集可以被任何.NET兼容的编程语言使用

[Serializable]

声明程序结构可以被序列化

[NonSerializable]

声明程序结构不可以被序列化

[DLLImport]

声明非托管代码实现

[WebMethod]

声明方法时XML Web服务的一部分暴露

[AttributeUseage]

定义自定义特性应用范围

全局特性

通常特性用于修饰定义特性地方紧跟后面的代码

可以使用assembly 和module 目标名称来使用显式目标说明符

把特性设置在程序集或模块级别

有关程序集级别的特性的要点如下:

​ 程序级级别的特性必须放置在任何命名空间之外

并且通常放置在AssemblyInfo.cs文件中

AssemblyInfo.cs文件通常包含有关公司、产品以及版权信息的元数据

AssemblyInfo.cs文件实例:

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// 有关程序集的一般信息由以下
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("ConsoleApp2")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ConsoleApp2")]
[assembly: AssemblyCopyright("Copyright ©  2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// 将 ComVisible 设置为 false 会使此程序集中的类型
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
//请将此类型的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]

// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("17064f12-5f0a-483d-8be5-7a94dbe55e62")]

// 程序集的版本信息由下列四个值组成: 
//
//      主版本
//      次版本
//      生成号
//      修订号
//
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
//通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

字段特性

字段特性,从C# 7.3起可用

[field:NonSerialized]
public int MyProperty { get; set; }

自定义特性(Attribute Classes)

说明

特性本质是一个特殊类型的类

特性类都派生自System.Attribute

自定义的特性叫做用户自定义特性

特性类使用Pascal规范,一般特性类名以Attribute结尾

一般将特性声明为sealed

公开成员只能是字段、属性、构造函数

使用AttributeUsage特性限制自定义特性

把特性类设置为sealed

应用特性时,构造函数参数必须时在编译期就能确定的常量表达式:
image

应用特性时,构造函数没有参数,可以省略圆括号:
image

特性构造函数中的位置参数和命名参数:
image

限定特性的使用

我们可以限定特性只在指定的程序结构上可以使用,使用AttributeUsage特性即可
image

AttributeUsage的公共属性
image

AttributeTarget的枚举成员
image

我们可以组合枚举值来限定特性可应用的范围
image

实例:
image

实例:使用特性类

[ObsoleteAttribute]
public class Foo {...}

实例:省略Attribute后缀

[Obsolete]
public class Foo {...}

规范

特性类应该表示目标结构的一些状态

如果特性需要某些数据,可以通过带位置参数的构造函数来获得数据

除了属性之外不要实现公共方法或其他函数成员

为了更安全,把特性类声明为sealed

使用AttributeUsage特性来限制特性目标

特性类对外只提供只读的属性,写入操作只在构造器中写入

实例:
image

自定义特性

在visual studio中输入attribute按两下Tab可以自动生成一个特性

public sealed class MyAttribute:Attribute
{
    private string _Desp;
    public MyAttribute()
    {
        this._Desp = "None";
    }
    public MyAttribute(string desp)
    {
        this._Desp = desp;
    }
}

限定特性的使用范围

说明

使用AttributeUsage特性修饰我们自定义的特性,可以限定特性的使用范围

注意:AttributeUsage特性只能用于修饰自定义的特性,不可以修饰其他编程结构

AttributeUsage的几种使用方法

//限定自定义特性的使用范围
AttributeUsage(AttributeTargets值)
// AllowMultiple是否允许多次应用到同一个程序集
AttributeUsage(AttributeTargets值, AllowMultiple=bool值)
// Inherited是否允许特性被派生类或接口实现类继承
AttributeUsage(AttributeTargets值, AllowMultiple=bool值, Inherited=bool值)

AttributeTargets枚举可取值

AttributeTargets.All                //可以用在任何编程结构
AttributeTargets.Assembly           //可以用在程序集
AttributeTargets.Module             //可以用在模块上
AttributeTargets.Class              //可以用在类上
AttributeTargets.Constructor        //可以用在构造函数上
AttributeTargets.Interface          //可以用在接口上
AttributeTargets.Struct             //可以用在结构上
AttributeTargets.Delegate           //可以用在委托上
AttributeTargets.Enum               //可以用在枚举上
AttributeTargets.Event              //可以用在事件上
AttributeTargets.Field              //可以用在字段上
AttributeTargets.GenericParameter   //可以用在泛型参数上
AttributeTargets.Method             //可以用在方法上
AttributeTargets.Parameter          //可以用在参数上
AttributeTargets.Property           //可以用在属性上
AttributeTargets.ReturnValue        //可以用在返回值上

实例-限定特性使用范围

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
class AtrributeTestClass: Attribute
{

}

实例-允许在一个类中多次使用

[AttributeUsage(AttributeTargets.Class,AllowMultiple = true)]
class AtrributeTestClass: Attribute
{

}

实例-允许特性被继承

[AttributeUsage(AttributeTargets.Class,Inherited = true)]
class AtrributeTestClass: Attribute
{

}

使用特性修饰编程结构

[MyAttribute]
public void Method(){}
[MyAttribute()]
public void Method(){}
[MyAttribute("aha")]
public void Method(){}

使用自定义特性

说明

通过Type类型的IsDefined()方法来检测某个特性是否应用到了某个类上

第一个参数:要检测的特性的Type对象

第二个参数:bool类型,指示是否搜索继承树来查找这个特性

返回值:一个bool类型,指示是否应用了特性

通过Type类型的GetCustomAttributes()方法获得程序结构的特性数组

返回值:Object类型元素组成的数组,必须转为相应的特性类型才可以使用

参数:bool类型,指示是否搜索继承树来查找这个特性

IsDefined()

namespace ConsoleApplication2
{
    [AttributeUsage(AttributeTargets.Class)]
    public sealed class TestAttribute : Attribute
    {
        public string Detail { get; set; }
        public int Code { get; set; }
        public TestAttribute()
        {
            Detail = "None";
            Code = 0;
        }
        public TestAttribute(string detail, int code)
        {
            Detail = detail;
            Code = code;
        }
    }

    [TestAttribute("Something",101)]
    class Program
    {
        static void Main(string[] args)
        {
            Type t = typeof(Program);
            bool isDefined = t.IsDefined(typeof(TestAttribute), false);
            if(isDefined)
            {
                Console.WriteLine("Defined");
            }
            Console.ReadKey();
        }
    }
}

GetCustomAttributes()

namespace ConsoleApplication2
{
    [AttributeUsage(AttributeTargets.Class)]
    public sealed class TestAttribute : Attribute
    {
        public string Detail { get; set; }
        public int Code { get; set; }
        public TestAttribute()
        {
            Detail = "None";
            Code = 0;
        }
        public TestAttribute(string detail, int code)
        {
            Detail = detail;
            Code = code;
        }
    }

    [TestAttribute("Something",101)]
    class Program
    {
        static void Main(string[] args)
        {
            Type t = typeof(Program);
            object[] attrs = t.GetCustomAttributes(false);
            foreach (var item in attrs)
            {
                TestAttribute ta = item as TestAttribute;
                if(ta != null)
                {
                    Console.WriteLine(ta.Code);
                    Console.WriteLine(ta.Detail);
                }

            }
            
            Console.ReadKey();
        }
    }
}

程序集级别特性

.NET Framework中为程序集添加元信息

.NET Core中为程序集添加元信息

.NET Framework时程序一般使用 AssemblyInfo.cs文件定义程序集的元信息

.NET Core 可以在项目文件*.csproj文件中定义程序集信息

定义自定义特性(Defining a Custom Attribute)

<Project>
  <PropertyGroup>
      <Company>Addison Wesley</Company>
      <Copyright>Copyright  Addison Wesley 2020</Copyright>
      <Product>Essential C# 8.0</Product>
      <Version>8.0</Version>
  </PropertyGroup>
</Project>
posted @ 2022-09-23 08:46  重庆熊猫  阅读(697)  评论(0编辑  收藏  举报