65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 属性详解

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (17投票s)

2020年8月23日

CPOL

6分钟阅读

viewsIcon

13482

在本文中,我们将探讨 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);
            }
        }
    }
}

请看下面的输出

Built-in Attributes of the .NET Core

运行时读取属性

现在,我们已经回答了属性是什么、最常用的属性是什么,以及如何通过 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 类很容易理解。在下面的示例中,我们要检查以下几点:

  • 检查 Product 类是否具有 [Serializable] 属性。
  • 检查 Product 类是否具有两个方法。
  • 检查每个方法是否具有属性。
  • 检查 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; }
    }
}

请参阅下图以直观了解位置参数和可选参数之间的区别。

Difference between positional and optional parameters.

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

在类中应用自定义属性

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();
        }
    }
}

限制属性的使用

默认情况下,您可以将自定义属性应用于应用程序代码中的任何实体。因此,当您创建自定义属性时,它可以应用于类、方法、私有字段、属性、结构体等。但是,如果您想限制您的自定义属性仅出现在特定类型的实体上。您可以使用 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}");
    }
}

摘要

在本文中,我们讨论了以下内容:

  • 什么是属性?
    • 关于属性需要记住的事情
  • 属性类型
    • 内置属性
      • 常见的内置属性
      • 使用 C# 通过反射提取内置类型
      • 运行时读取属性
    • 自定义属性
      • 创建自定义属性
      • 在类中应用自定义属性
  • 限制属性的使用

希望您喜欢这篇文章,就像我喜欢写它一样。请继续关注更多内容。下次再见,祝您编程愉快!

最后,您可以在此处下载示例代码 此处
© . All rights reserved.