目錄
介紹
背景
什麼是特性?
關于特性要記住的事情
特性類型
内在特性
常用的内置特性
使用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}");
}
}