天天看点

演练C#特性介绍背景什么是特性?特性类型限制特性的使用

目录

介绍

背景

什么是特性?

关于特性要记住的事情

特性类型

内在特性

常用的内置特性

使用C#通过反射提取内置类型

在运行时读取特性

自定义特性

创建自定义特性

在类中应用自定义特性

限制特性的使用

介绍

如果您已经使用C#语言已有一段时间了,那么您正在使用内置特性(例如[Serializable], [Obsolete]),但是您还没有对此进行深入的考虑?在本文中,我们将探讨特性的基础知识,常见的特性,如何创建和读取特性。令人兴奋的是,您将看到如何使用System.Reflection来获取内置特性。好,那就开始吧。

背景

这有点题外话,但可以分享。你知道吗?当我们放松和阅读书籍时,我们的头脑往往会开始怀疑。而且,当我读一本C#书籍时,这件事发生在我身上,我开始想知道如何通过System.Reflection来获取那些内置特性。因此,这篇文章变得生动起来。

什么是特性?

特性很重要,它提供了其他信息,为开发人员提供了线索。尤其是在期望的情况下,具有类的行为以及应用程序中类的特性和/或方法。

简而言之,特性就像形容词,描述类型、程序集、模块、方法等。

关于特性要记住的事情

  • 特性是派生自System.Attribute的类 
  • 特性可以具有参数
  • 在代码中使用特性时,特性可以省略特性名称的Attribute部分。框架将以任何一种方式正确处理特性。

特性类型

内在特性

这些特性也称为预定义或内置特性。.NET Framework / .NET Core提供了数百甚至数千个内置特性。它们中的大多数是专门的,但是我们将尝试以编程方式提取它们,并讨论一些最常见的方法。

常用的内置特性

特性 描述
[Obsolete]

System.ObsoleteAttribute

帮助您识别代码应用程序中的过时位。

[Conditional]

System.Diagnostics.ConditionalAttribute

使您能够执行条件编译。

[Serializable]

System.SerializableAttribute

表明一个类可以序列化。

[NonSerialized]

System.NonSerializedAttribute

表明不应将可序列化类的字段序列化。

[DLLImport]

System.DllImportAttribute

显示方法由非托管动态链接库(DLL)公开为静态入口点。

使用C#通过反射提取内置类型

如所承诺的,我们将看到如何使用C#提取内置特性。请参见下面的示例代码:

using System;
using System.Linq;
using System.Reflection;
using Xunit;
using Xunit.Abstractions;

namespace CSharp_Attributes_Walkthrough {
    public class UnitTest_Csharp_Attributes {
        private readonly ITestOutputHelper _output;

        private readonly string assemblyFullName = "System.Private.CoreLib, 
                Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e";

        public UnitTest_Csharp_Attributes (ITestOutputHelper output) {
            this._output = output;
        }

        [Fact]
        public void Test_GetAll_BuiltIn_Attributes () {
            var assembly = Assembly.Load (assemblyFullName);

            var attributes = assembly
                .DefinedTypes
                .Where (type =>
                    type
                    .IsSubclassOf (typeof (Attribute)));

            foreach (var attribute in attributes) {
                
                string attr = attribute
                    .Name
                    .Replace ("Attribute", "");

                this._output
                    .WriteLine ("Attribute: {0} and Usage: [{1}]", attribute.Name, attr);
            }
        }
    }
}
           

请参见下面的输出:

演练C#特性介绍背景什么是特性?特性类型限制特性的使用

在运行时读取特性

现在,我们已经回答了什么是特性,什么是常用特性,以及如何通过System.Reflection提取内置特性的方法,让我们看看如何在运行时读取这些特性,当然使用System.Reflection。

在运行时检索特性值时,有两种方法可以检索值。

  • 使用GetCustomAttributes()方法,这将返回一个包含指定类型的所有特性的数组。当您不确定哪些特性适用于特定类型时,可以使用此方法,可以遍历此数组。
  • 使用GetCustomAttribute()方法,这将返回所需的特定特性的详细信息。

好的,接下来让我们来看一个例子。

让我们首先尝试创建一个类,并使用一些随机特性对其进行标记。

using System;
using System.Diagnostics;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    [Serializable]
    public class Product
    {
        public string Name { get; set; }
        public string Code { get; set; }

        [Obsolete("This method is already obselete. Use the ProductFullName instead.")]
        public string GetProductFullName()
        {
            return $"{this.Name} {this.Code}";
        }

        [Conditional("DEBUG")]
        public void RunOnlyOnDebugMode()
        {

        }
    }
}
           

Product-class是很容易理解。在下面的示例中,我们要检查以下内容:

  • 检查Product-class是否确实具有[Serializable]特性。
  • 检查Product-class是否具有两个方法。
  • 检查每个方法是否确实具有特性。
  • 检查方法GetProductFullName是否正在使用[Obsolete]特性。
  • 检查方法RunOnlyDebugMode是否正在使用[Conditional]特性。
/*
*This test will read the Product-class at runtime to check for attributes. 
*1. Check if [Serializable] has been read. 
*2. Check if the product-class has two methods 
*3. Check if each methods does have attributes. 
*4. Check if the method GetProudctFullName is using the Obsolete attribute. 
*5. Check if the method RunOnlyOnDebugMode is using the Conditional attribute.
*/
[Fact]
public void Test_Read_Attributes()
{
    //get the Product-class
    var type = typeof(Product);

    //Get the attributes of the Product-class and we are expecting the [Serializable]
    var attribute = (SerializableAttribute)type.
                    GetCustomAttributes(typeof(SerializableAttribute), false).FirstOrDefault();

    Assert.NotNull(attribute);

    //Check if [Serializable] has been read.
    //Let's check if the type of the attribute is as expected
    Assert.IsType<SerializableAttribute>(attribute);

    //Let's get only those 2 methods that we have declared 
    //and ignore the special names (these are the auto-generated setter/getter)
    var methods = type.GetMethods(BindingFlags.Instance | 
                                    BindingFlags.Public | 
                                    BindingFlags.DeclaredOnly)
                        .Where(method => !method.IsSpecialName).ToArray();

    //Check if the product-class has two methods 
    //Let's check if the Product-class has two methods.
    Assert.True(methods.Length == 2);

    Assert.True(methods[0].Name == "GetProductFullName");
    Assert.True(methods[1].Name == "RunOnlyOnDebugMode");

    //Check if each methods does have attributes. 
    Assert.True(methods.All( method =>method.GetCustomAttributes(false).Length ==1));

    //Let's get the first method and its attribute. 
    var obsoleteAttribute = methods[0].GetCustomAttribute<ObsoleteAttribute>();

    // Check if the method GetProudctFullName is using the Obsolete attributes. 
    Assert.IsType<ObsoleteAttribute>(obsoleteAttribute);

    //Let's get the second method and its attribute. 
    var conditionalAttribute = methods[1].GetCustomAttribute<ConditionalAttribute>();

    //Check if the method RunOnlyOnDebugMode is using the Conditional attributes.
    Assert.IsType<ConditionalAttribute>(conditionalAttribute);
}
           

希望您喜欢上面的示例。然后让我们进入自定义特性。

自定义特性

内置特性是有用且重要的,但是在大多数情况下,它们具有特定用途。此外,如果您认为出于某种原因需要特性而不考虑内置特性,则可以创建自己的特性。

创建自定义特性

在本部分中,您将看到我们如何创建自定义特性,以及在创建自定义特性时需要记住的内容。

  • 要创建自定义特性,请定义一个派生自System.Attribute的类。
using System;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    public class AliasAttribute : Attribute
    {
        //This is how to define a custom attributes.
    }
}
           
  • 位置参数——如果您的自定义特性的构造函数中有任何参数,它将成为必需的位置参数。
using System;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    public class AliasAttribute : Attribute
    {
        /// <summary>
        /// These parameters will become mandatory once have you decided to use this attribute.
        /// </summary>
        /// <param name="alias"></param>
        /// <param name="color"></param>
        public AliasAttribute(string alias, ConsoleColor color)
        {
            this.Alias = alias;
            this.Color = color;
        }

        public string  Alias { get; private set; }
        public ConsoleColor Color { get; private set; }
    }
}
           
  • 可选参数 ——这些是从System.Attribute派生的类的public字段和public可写特性。
using CSharp_Attributes_Walkthrough.My_Custom_Attributes;
using System;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    public class AliasAttribute : Attribute
    {
        //....

        //Added an optional-parameter
        public string AlternativeName { get; set; }
    }
}
           

请参见下面的完整示例代码:

using System;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    public class AliasAttribute : Attribute
    {
        /// <summary>
        /// These parameters will become mandatory once have you decided to use this attribute.
        /// </summary>
        /// <param name="alias"></param>
        /// <param name="color"></param>
        public AliasAttribute(string alias, ConsoleColor color)
        {
            this.Alias = alias;
            this.Color = color;
        }

        #region Positional-Parameters
        public string Alias { get; private set; }
        public ConsoleColor Color { get; private set; }
        #endregion 

        //Added an optional-parameter
        public string AlternativeName { get; set; }
    }
}
           

请参见下图,以可视化位置参数和可选参数之间的差异。

演练C#特性介绍背景什么是特性?特性类型限制特性的使用

现在我们已经创建了一个自定义特性,让我们尝试在一个类中使用它。

在类中应用自定义特性

using System;
using System.Linq;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    [Alias("Filipino_Customers", ConsoleColor.Yellow)]
    public class Customer
    {
        [Alias("Fname", ConsoleColor.White, AlternativeName = "Customer_FirstName")]
        public string Firstname { get; set; }

        [Alias("Lname", ConsoleColor.White, AlternativeName = "Customer_LastName")]
        public string LastName { get; set; }

        public override string ToString()
        {
            //get the current running instance.
            Type instanceType = this.GetType(); 

            //get the namespace of the running instance.
            string current_namespace = (instanceType.Namespace) ?? "";

            //get the alias.
            string alias = (this.GetType().GetCustomAttributes(false).FirstOrDefault() 
                            as AliasAttribute)?.Alias;

            return $"{current_namespace}.{alias}";
        }
    }
}
           

该示例的主要内容是ToString()方法,默​​认情况下将返回该类型的全限定名称。然而; 我们在这里所做的是,我们已经覆盖了ToString()返回特性的全限定名称和别名的方法。

现在让我们尝试调用该ToString()方法,看看它返回什么。请参见下面的示例,输出:

using CSharp_Attributes_Walkthrough.My_Custom_Attributes;
using System;

namespace Implementing_Csharp_Attributes_101
{
    class Program
    {
        static void Main(string[] args)
        {
            var customer = new Customer { Firstname = "Jin Vincent" , LastName = "Necesario" };
          
            var aliasAttributeType = customer.GetType();

            var attribute = 
                aliasAttributeType.GetCustomAttributes(typeof(AliasAttribute), false);

            Console.ForegroundColor = ((AliasAttribute)attribute[0]).Color;

            Console.WriteLine(customer.ToString());

            Console.ReadLine();
        }
    }
}
           
演练C#特性介绍背景什么是特性?特性类型限制特性的使用

限制特性的使用

默认情况下,您可以将自定义特性应用于应用程序代码中的任何实体。因此,当您创建自定义特性时,它可以应用于类、方法、私有字段、特性、结构等。但是,如果您希望将自定义特性限制为仅出现在某些类型的实体上。您可以使用AttributeUsage特性来控制可以将其应用于哪些实体。

目标
AttributeTargets.All 可以应用于应用程序中的任何实体
AttributeTargets.Assembly 可以应用于程序集
AttributeTargets.Class 可以应用于类
AttributeTargets.Construtor 可以应用于构造函数
AttributeTargets.Delegate 可以应用于代理
AttributeTargets.Enum 可以应用于枚举
AttributeTargets.Event 可以应用于事件
AttributeTargets.Field 可以应用于字段
AttributeTargets.Interface 可以应用于接口
AttributeTargets.Method 可以应用于方法
AttributeTargets.Module 可以应用于模块
AttributeTargets.Parameter 可以应用于参数
AttributeTargets.Property 可以应用于属性
AttributeTargets.ReturnValue 可以应用于返回值
AttributeTargets.Struct 可以应用于结构

如果您想知道,我们如何在运行时获得这些AttributeTargets?请参阅以下示例:

[Fact]
public void Test_GetAll_AttributeTargets()
{
    var targets = Enum.GetNames(typeof(AttributeTargets));

    foreach (var target in targets)
    {
        this._output.WriteLine($"AttributeTargets.{target}");
    }
}