目录
介绍
背景
什么是特性?
关于特性要记住的事情
特性类型
内在特性
常用的内置特性
使用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);
}
}
}
}
请参见下面的输出:
在运行时读取特性
现在,我们已经回答了什么是特性,什么是常用特性,以及如何通过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; }
}
}
请参见下图,以可视化位置参数和可选参数之间的差异。
现在我们已经创建了一个自定义特性,让我们尝试在一个类中使用它。
在类中应用自定义特性
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}");
}
}