文鏈接:https://www.infoworld.com/article/3006630/how-to-work-with-attributes-in-c.html?nsdr=true
Attribute 在 C# 中是一個非常強大的特性,它能夠給你的程序集添加元數據信息。
Attribute 實際上是一個對象,它可以與以下元素中的任何一個相關聯: 程序集、類、方法、委托、枚舉、事件、字段、接口、屬性和結構,它會在這些對象上做信息聲明,當程序運行之后,你可以通過反射來獲取關聯到這些對象上的 Attribute 信息,換句話說:你可以通過 Atrribute 向程序集注入一些額外信息,然后在運行時通過反射來獲取,attribute 一般由 名字 + 一些可選參數
構成, attribute 名字對應著 atrribute 類。
你可以利用 attribute 去校驗你的業務model的正確性, attribute 有兩種:內置 + 自定義
, 前者是 .net framework 框架的組成部分,后者需要通過繼承 System.Attribute
類來實現自定義。
現在來看看代碼怎么寫,Obsolete
特性用來標記一個方法是過時的,這個過時的意思是:你不應該再使用這個方法了,未來框架也會將其剔除,目前也存在其替代方案
。其實在第三方框架中有很多這樣的例子,下面的代碼片段展示了如何在方法頂部使用 Obsolete
特性。
[Obsolete("This method is obsolete...")]
public static void DoSomeWork()
{
}
如果你在程序中調用了這個方法,當你編譯代碼時,在 Visual Studio 輸出窗口中會現在一些警告信息,如下圖:
當然,如果你一定要忽視它也是可以的,現在,假如你希望你的開發同事不允許調用這個方法,那如何去限定呢?哈哈,可以使用 Obsolete 的第二個參數,這個參數是可選的,下面是 DoSomeWork()
方法的修改版本,請注意這是一個 Boolean 型參數。
[Obsolete("This method is obsolete...", true)]
public static void DoSomeWork()
{
}
當把 true 給了這個可選參數后,再次編譯代碼,你會發現代碼根本編譯不通過,是不是完美的解決了你的問題,是吧! 截圖如下:
這一小節我們來看一下如何去實現自定義的 attribute,要想自定義實現,可以創建一個類并繼承 System.Attribute
類即可,如下代碼所示:
using System;
public class CustomAttribute : Attribute
{
}
要想限定 CustomAttribute 的使用,可以用 AttributeUsage 類去標記,這個類包含了如下屬性: ValidOn
,AllowMultiple
,Inherited
等等,這些標記都可以限定 CustomAttribute 的使用。
下面的代碼片段展示了 CustomAttribute 的修改版本,這個類使用構造函數去給內部的私有 string 賦值,代碼僅僅用于演示目的。
[AttributeUsage(AttributeTargets.All)]
public class CustomAttribute : Attribute
{
private string text;
public CustomAttribute(string text)
{
this.Text=text;
}
public string Text { get=> text; set=> text=value; }
}
當然你也可以按需去指定這些 AttributeTargets,如下代碼所示:
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple=true)]
public class CustomAttribute : Attribute
{
private string text;
public CustomAttribute(string text)
{
this.Text=text;
}
public string Text { get=> text; set=> text=value; }
}
接下來你可以用反射來獲取應用到對象上的所有attributes,代碼如下:
static void Main(string[] args)
{
MemberInfo memberInfo=typeof(CustomAttribute);
object[] attributes=memberInfo.GetCustomAttributes(true);
for (int i=0, j=attributes.Length; i < j; i++)
{
Console.WriteLine(attributes[i]);
}
}
接下來我準備將 CustomAttribute 類應用到 下面的 SomeClass 類上。
[CustomAttribute("Hello World...")]
public class SomeClass
{
}
可以著重看下 CustomAttribute 是如何安插在 SomeClass 上的,而且我還傳遞了一個 Hello World...
字符串給它,下面的代碼展示了如何將 CustomAttribute 中的 Text 屬性打印出來。
class Program
{
static void Main(string[] args)
{
MemberInfo memberInfo=typeof(SomeClass);
object[] attributes=memberInfo.GetCustomAttributes(true);
foreach (object attribute in attributes)
{
CustomAttribute customAttribute=attribute as CustomAttribute;
if (customAttribute !=null)
Console.WriteLine("Text={0}", customAttribute.Text);
else
Console.WriteLine();
}
}
}
[CustomAttribute("Hello World...")]
public class SomeClass
{
}
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple=true)]
public class CustomAttribute : Attribute
{
private string text;
public CustomAttribute(string text)
{
this.Text=text;
}
public string Text { get=> text; set=> text=value; }
}
更多高質量干貨:參見我的 GitHub: dotnetfly**
NU C的一大特色(卻不被初學者所知)就是 attribute 機制。
attribute可以設置函數屬性(Function Attribute)、變量屬性(Variable Attribute) 和 類型屬性(Type Attribute)。
attribute書寫特征是: attribute前后都有兩個下劃線,并且后面會緊跟一對圓括弧,括弧里面是相應的attribute參數。
attribute語法格式為:
__attribute__ ((attribute-list))
其位置約束為: 放于聲明的尾部“;”之前。
函數屬性可以幫助開發者把一些特性添加到函數聲明中,從而可以使編譯器在錯誤檢查方面的功能更強大。
attribute機制也很容易同非GNU應用程序做到兼容之功效。
GNU CC需要使用 –Wall編譯器來激活該功能,這是控制警告信息的一個很好的方式。
下面介紹幾個常見的屬性參數。
該attribute屬性可以給被聲明的函數加上類似printf或者scanf的特征,它可以使編譯器檢查函數聲明和函數實際調用參數之間的格式化字符串是否匹配。
該功能十分有用,尤其是處理一些很難發現的bug。
format的語法格式為:
format (archetype, string-index, first-to-check)
format屬性告訴編譯器,按照printf, scanf, strftime或strfmon的參數表格式規則對該函數的參數進行檢查。
具體使用格式如下:
__attribute__((format(printf,m,n)))
__attribute__((format(scanf,m,n)))
其中參數m與n的含義為:
m:第幾個參數為格式化字符串(format string);
n:參數集合中的第一個,即參數“…”里的第一個參數在函數參數總數排在第幾,注意,有時函數參數里還有“隱身”的呢,后面會提到;
在使用上,attribute((format(printf,m,n)))是常用的,而另一種卻很少見到。
下面舉例說明,其中myprint為自己定義的一個帶有可變參數的函數,其功能類似于printf:
//m=1;n=2
extern void myprint(const char *format,...)
__attribute__((format(printf,1,2)));
//m=2;n=3
extern void myprint(int l,const char *format,...)
__attribute__((format(printf,2,3)));
需要特別注意的是,如果myprint是一個函數的成員函數,那么m和n的值可有點“懸乎”了,例如:
//m=3;n=4
extern void myprint(int l,const char *format,...)
__attribute__((format(printf,3,4)));
其原因是,類成員函數的第一個參數實際上是一個 “隱身”的“this”指針。(有點C++基礎的都知道點this指針,不知道你在這里還知道嗎?)
這里給出測試用例:attribute.c,代碼如下:
1:
2:extern void myprint(const char *format,...)
__attribute__((format(printf,1,2)));
3:
4:void test()
5:{
6: myprint("i=%d\n",6);
7: myprint("i=%s\n",6);
8: myprint("i=%s\n","abc");
9: myprint("%s,%d,%d\n",1,2);
10:}
運行 $gcc –Wall –c attribute.c attribute后,輸出結果為:
attribute.c: In function `test':
attribute.c:7: warning: format argument is not a pointer (arg 2)
attribute.c:9: warning: format argument is not a pointer (arg 2)
attribute.c:9: warning: too few arguments for format
如果在attribute.c中的函數聲明去掉attribute((format(printf,1,2))),再重新編譯,
既運行$gcc –Wall –c attribute.c attribute后,則并不會輸出任何警告信息。
注意,默認情況下,編譯器是能識別類似printf的“標準”庫函數。
該屬性通知編譯器函數從不返回值,當遇到類似函數需要返回值而卻不可能運行到返回值處就已經退出來的情況,該屬性可以避免出現錯誤信息。
C庫函數中的abort()和exit()的聲明格式就采用了這種格式,如下所示:
extern void exit(int) __attribute__((noreturn));
extern void abort(void) __attribute__((noreturn));
為了方便理解,大家可以參考如下的例子:
//name: noreturn.c ;測試__attribute__((noreturn))
extern void myexit();
int test(int n)
{
if ( n > 0 )
{
myexit();
/* 程序不可能到達這里*/
}
else
return 0;
}
編譯顯示的輸出信息為:
$gcc –Wall –c noreturn.c
noreturn.c: In function `test':
noreturn.c:12: warning: control reaches end of non-void function
警告信息也很好理解,因為你定義了一個有返回值的函數test卻有可能沒有返回值,程序當然不知道怎么辦了!
加上__attribute__((noreturn))則可以很好的處理類似這種問題。把
extern void myexit();修改為:
extern void myexit() __attribute__((noreturn));之后,編譯不會再出現警告信息。
該屬性只能用于帶有數值類型參數的函數上。
當重復調用帶有數值參數的函數時,由于返回值是相同的,所以此時編譯器可以進行優化處理,除第一次需要運算外,其它只需要返回第一次的結果就可以了,進而可以提高效率。該屬性主要適用于沒有靜態狀態(static state)和副作用的一些函數,并且返回值僅僅依賴輸入的參數。
為了說明問題,下面舉個非常“糟糕”的例子,該例子將重復調用一個帶有相同參數值的函數,具體如下:
extern int square(int n) __attribute__((const));
...
for (i=0; i < 100; i++ )
{
total +=square (5) + i;
}
通過添加attribute((const))聲明,編譯器只調用了函數一次,以后只是直接得到了相同的一個返回值。
事實上,const參數不能用在帶有指針類型參數的函數中,因為該屬性不但影響函數的參數值,同樣也影響到了參數指向的數據,它可能會對代碼本身產生嚴重甚至是不可恢復的嚴重后果。
并且,帶有該屬性的函數不能有任何副作用或者是靜態的狀態,所以,類似getchar()或time()的函數是不適合使用該屬性的。
可以在同一個函數聲明里使用多個attribute,并且實際應用中這種情況是十分常見的。
使用方式上,你可以選擇兩個單獨的attribute,或者把它們寫在一起,可以參考下面的例子:
/* 把類似printf的消息傳遞給stderr 并退出 */
extern void die(const char *format, ...) __attribute__((noreturn)) __attribute__((format(printf, 1, 2)));
或者寫成
extern void die(const char *format, ...) __attribute__((noreturn, format(printf, 1, 2)));
如果帶有該屬性的自定義函數追加到庫的頭文件里,那么所以調用該函數的程序都要做相應的檢查。
慶幸的是,attribute設計的非常巧妙,很容易作到和其它編譯器保持兼容,也就是說,如果工作在其它的非GNU編譯器上,可以很容易的忽略該屬性。即使attribute使用了多個參數,也可以很容易的使用一對圓括弧進行處理,例如:
/* 如果使用的是非GNU C, 那么就忽略__attribute__ */
#ifndef __GNUC__#
define __attribute__(x) /*NOTHING*/
#endif
需要說明的是,attribute適用于函數的聲明而不是函數的定義。
所以,當需要使用該屬性的函數時,必須在同一個文件里進行聲明,
例如:
/* 函數聲明 */
void die(const char *format, ...) __attribute__((noreturn)) __attribute__((format(printf,1,2)));
void die(const char *format, ...)
{
/* 函數定義 */
}
更多的屬性含義參考:http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Function-Attributes.html
關鍵字attribute也可以對變量(variable)或結構體成員(structure field)進行屬性設置。
這里給出幾個常用的參數的解釋,更多的參數可參考本文給出的連接。
在使用attribute參數時,你也可以在參數的前后都加上“”(兩個下劃線),例如,使用__aligned而不是aligned,
這樣,你就可以在相應的頭文件里使用它而不用關心頭文件里是否有重名的宏定義。
該屬性規定變量或結構體成員的最小的對齊格式,以字節為單位。例如:
int x __attribute__((aligned (16)))=0;
編譯器將以16字節(注意是字節byte不是位bit)對齊的方式分配一個變量。
也可以對結構體成員變量設置該屬性,例如,創建一個雙字對齊的int對,可以這么寫:
struct foo { int x[2] __attribute__((aligned (8))); };
如上所述,你可以手動指定對齊的格式,同樣,你也可以使用默認的對齊方式。如果aligned后面不緊跟一個指定的數字值,那么編譯器將依據你的目標機器情況使用最大最有益的對齊方式。例如:
short array[3] __attribute__((aligned));
選擇針對目標機器最大的對齊方式,可以提高拷貝操作的效率。
aligned屬性使被設置的對象占用更多的空間,相反的,使用packed可以減小對象占用的空間。
需要注意的是,attribute屬性的效力與你的連接器也有關,如果你的連接器最大只支持16字節對齊,那么你此時定義32字節對齊也是無濟于事的。
使用該屬性可以使得變量或者結構體成員使用最小的對齊方式,即對變量是一字節對齊,對域(field)是位對齊。
下面的例子中,x成員變量使用了該屬性,則其值將緊放置在a的后面:
struct test
{
char a;
int x[2] attribute ((packed));
};
其它可選的屬性值還可以是:cleanup,common,nocommon,deprecated,mode,section,shared,tls_model,transparent_union,unused,vector_size,weak,dllimport,dlexport等,
詳細信息可參考:http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Variable-Attributes.html#Variable-Attributes
關鍵字attribute也可以對結構體(struct)或共用體(union)進行屬性設置。
大致有六個參數值可以被設定,即:
aligned, packed, transparent_union, unused, deprecated 和 may_alias。
在使用attribute參數時,你也可以在參數的前后都加上“”(兩個下劃線),例如,使用__aligned而不是aligned,這樣,你就可以在相應的頭文件里使用它而不用關心頭文件里是否有重名的宏定義。
該屬性設定一個指定大小的對齊格式(以字節為單位),例如:
struct S { short f[3]; } __attribute__ ((aligned (8)));
typedef int more_aligned_int __attribute__ ((aligned (8)));
該聲明將強制編譯器確保(盡它所能)變量類型為struct S 或者more-aligned-int的變量在分配空間時采用8字節對齊方式。
如上所述,你可以手動指定對齊的格式,同樣,你也可以使用默認的對齊方式。
如果aligned后面不緊跟一個指定的數字值,那么編譯器將依據你的目標機器情況使用最大最有益的對齊方式。例如:
struct S { short f[3]; } __attribute__ ((aligned));
這里,如果sizeof(short)的大小為2(byte),那么,S的大小就為6。取一個2的次方值,使得該值大于等于6,則該值為8,所以編譯器將設置S類型的對齊方式為8字節。
aligned屬性使被設置的對象占用更多的空間,相反的,使用packed可以減小對象占用的空間。
需要注意的是,attribute屬性的效力與你的連接器也有關,如果你的連接器最大只支持16字節對齊,那么你此時定義32字節對齊也是無濟于事的。
使用該屬性對struct或者union類型進行定義,設定其類型的每一個變量的內存約束。
當用在enum類型定義時,暗示了應該使用最小完整的類型(it indicates that the smallest integral type should be used)。
下面的例子中,my-packed-struct類型的變量數組中的值將會緊緊的靠在一起,但內部的成員變量s不會被“pack”,如果希望內部的成員變量也被packed的話,my-unpacked-struct也需要使用packed進行相應的約束。
struct my_unpacked_struct
{
char c;
int i;
};
struct my_packed_struct
{
char c;
int i;
struct my_unpacked_struct s;
}__attribute__ ((__packed__));
其它屬性的含義見: http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Type-Attributes.html#Type-Attributes
下面的例子中使用attribute屬性定義了一些結構體及其變量,并給出了輸出結果和對結果的分析。
程序代碼為:
struct p
{
int a;
char b;
char c;
}__attribute__((aligned(4))) pp;
struct q
{
int a;
char b;
struct n qn;
char c;
}__attribute__((aligned(8))) qq;
int main()
{
printf("sizeof(int)=%d,sizeof(short)=%d.sizeof(char)=%d\n",sizeof(int),sizeof(short),sizeof(char));
printf("pp=%d,qq=%d \n", sizeof(pp),sizeof(qq));
return 0;
}
輸出結果:
sizeof(int)=4,sizeof(short)=2.sizeof(char)=1
pp=8,qq=24
分析:
sizeof(pp):
sizeof(a)+ sizeof(b)+ sizeof(c)=4+1+1=6<23=8=sizeof(pp)
sizeof(qq):
sizeof(a)+ sizeof(b)=4+1=5
sizeof(qn)=8; 即qn是采用8字節對齊的,所以要在a,b后面添3個空余字節,然后才能存儲qn,
4+1+(3)+8+1=17
因為qq采用的對齊是8字節對齊,所以qq的大小必定是8的整數倍,即qq的大小是一個比17大又是8的倍數的一個最小值,由此得到
17<24+8=24=sizeof(qq)
更詳細的介紹見:http://gcc.gnu.org/
Reference:
1.有關attribute的相對簡單的介紹:http://www.unixwiz.net/techtips/gnu-c-attributes.html
2.attribute詳細介紹:http://gcc.gnu.org/
DOM節點是一個JS對象,它符合前面描述的對象的特性——可擴展屬性,因為DOM節點本質上也是一個JS。因此,如以下代碼所示,“p”可以具有“style”屬性和“className”“nodeName”“nodeType”屬性。注意**這些是JS類別的屬性,符合JS語法標準的**。
var pList=document.querySelectorAll('p')
var p=pList[0]
console.log(p.style.width) // 獲取樣式
p.style.width='100px' // 修改樣式
console.log(p.className) // 獲取 class
p.className='p1' // 修改 class
// 獲取 nodeName 和 nodeType
console.log(p.nodeName)
console.log(p.nodeType)
property 的獲取和修改,是直接改變 JS 對象,而 attribute 是直接改變 HTML 的屬性,兩種有很大的區別。attribute 就是對 HTML 屬性的 get 和 set,和 DOM 節點的 JS 范疇的 property 沒有關系。
var pList=document.querySelectorAll('p')
var p=pList[0]
p.getAttribute('data-name')
p.setAttribute('data-name', 'juejin')
p.getAttribute('style')
p.setAttribute('style', 'font-size:30px;')
而且,get 和 set attribute 時,還會觸發 DOM 的查詢或者重繪、重排,頻繁操作會影響頁面性能。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。