前言:上篇 C#基礎系列——反射筆記 總結了下反射得基礎用法,這章我們來看看C#的另一個基礎技術——特性。
1、什么是特性:就博主的理解,特性就是在類的類名稱、屬性、方法等上面加一個標記,使這些類、屬性、方法等具有某些統一的特征,從而達到某些特殊的需要。比如:方法的異常捕捉,你是否還在某些可能出現異常的地方(例如數據庫的操作、文件的操作等)經常使用try...catch。這個時候如果使用特性,就可以大大減少方法里面的try...catch的使用。你只需要定義一個專門捕捉異常的特性類ExceptionExAttribute,然后給這個特性類做些特殊處理,比如給它增加一個AOP攔截的功能(AOP攔截的方式很多,有興趣可以搜搜看,園子里面很多類似的文章)。那么在可能出現異常的方法名稱上面加上一個[ExceptionEx]特性標簽,這個方法就具有自動捕捉異常的能力。還是加上官方定義:
特性提供功能強大的方法,用以將元數據或聲明信息與代碼(程序集、類型、方法、屬性等)相關聯。 特性與程序實體關聯后,即可在運行時使用名為“反射”的技術查詢特性。
特性具有以下屬性:
-
特性可向程序中添加元數據。 元數據是有關在程序中定義的類型的信息。 所有的 .NET 程序集都包含指定的一組元數據,這些元數據描述在程序集中定義的類型和類型成員。 可以添加自定義特性,以指定所需的任何附加信息。
-
可以將一個或多個特性應用到整個程序集、模塊或較小的程序元素(如類和屬性)。
-
特性可以與方法和屬性相同的方式接受參數。
-
程序可以使用反射檢查自己的元數據或其他程序內的元數據。
(以上來自MSDN)
2、為什么需要特性:這個上面已經簡單介紹過,特性能大大減少統一需求的代碼量。其他不說,至少它能讓我們的代碼看上去更大氣點吧~~
3、特性的使用:博主這次還是打算從三個方便分別介紹下特性的常規使用方法。當然這幾種方式都是博主原來用過的,可能不是最好的舉例場景,但是也算比較典型的特性用法吧。
(1)類的屬性上面特性的用法:
之所以將這個放在最前面介紹是因為博主最近做的一個BS項目正好用到,並且使用場景也比較典型。首先介紹下使用場景:最近項目有一個需求,BS界面需要一個拖拽的功能。如下圖

當將左邊的3個div拖到右邊來時,每個div都有自己的特有屬性,比如2部門拖過來時,要顯示如下屬性:

1和3部門拖過來時可能對應的屬性不同。
設計思路:每個div對應的Model,每個Model里面有自己特有的屬性,然后屬性上面加上特性顯示屬性的名稱和默認值,以及界面應該呈現的html標簽。
實現代碼:
首先來看自定義的一個特性類:
public class DetailAttribute : Attribute { public string AttrName { set; get; } public string Html { set; get; } public string DefaultValue { set; get; } public string DataSource { set; get; } }
對應的Model:
public class Factory { [Detail(AttrName="寬度", Html="<input type='text' />", DefaultValue="50", DataSource=null)] public string Width { set; get; } [Detail(AttrName = "高度", Html = "<input type='text' />", DefaultValue = "50", DataSource = null)] public string Height { set; get; } [Detail(AttrName = "狀態", Html = "<select></select>", DefaultValue = null, DataSource = "select text,value from status")] public string Status { set; get; } [Detail(AttrName = "Tag值", Html = "<input type='text' />", DefaultValue = "", DataSource = null)] public string Tag { set; get; } } public class FactoryDetail { [Detail(AttrName = "寬度", Html = "<input type='text' />", DefaultValue = "50", DataSource = null)] public string Width { set; get; } [Detail(AttrName = "高度", Html = "<input type='text' />", DefaultValue = "50", DataSource = null)] public string Height { set; get; } [Detail(AttrName = "狀態", Html = "<select></select>", DefaultValue = null, DataSource = "select text,value from status")] public string Status { set; get; } [Detail(AttrName = "Tag值", Html = "<input type='text' />", DefaultValue = "", DataSource = null)] public string Tag { set; get; } [Detail(AttrName = "描述", Html = "<input type='text' />", DefaultValue = "", DataSource = null)] public string Desc { set; get; } }
然后在界面的拖放事件結束時通過js發送ajax請求來得到界面要呈現的html:
$(".jq-draggable-outcontainer").draggable({
helper: "clone",
scroll: true,
drag: function (event, ui) {
// debugger;
}
});
$("#content").droppable({
drop: function (event, ui) {
// debugger;
if (ui.draggable[0].className.indexOf("jq-draggable-outcontainer") > 0) {
var text = ui.draggable[0].innerText.trim();
$(this).append('<div class="window jq-draggable-incontainer" onclick="GetPropertiesByType(\'1\',this)" style="position:absolute;left:' +(event.clientX-20) + 'px;top:' + (event.clientY-20) + 'px" id="window' + iIndex + '"><strong>' + text + '</strong></div>');
$("#content2").html("");
cur_selector = $("#window"+iIndex);
$.Ewin.AjaxPost("/Home/GetModelByType", { strType: "Factory" }, function (data, status) {
var element = $.parseJSON(data.Json);
var arrProp = element.element.property;
//0.構造html
var strHtml = "<div style='float:right;padding-top:0px;width:300px;height:auto;'><table cellpadding='5' border='1'>";
//1.拼html構造屬性
strHtml += "</table></div>";
$("#content2").append(strHtml);
}, function () {
}, null);
iIndex++;
}
}
});
對應的C#方法:
public JsonResult GetModelByType(string strType) {
//strType傳過來的是Factory或者FactoryDetail var assembly = Assembly.Load("Ewin.Client.Web");//參數為程序集的名稱 var oType = assembly.GetType("Ewin.Client.Web.Controllers." + strType);
//得到類的所有屬性 var lstProperties = oType.GetProperties(); foreach (var oProperty in lstProperties) {
//得到每一個屬性的特性類集合 IList<CustomAttributeData> lstAttr = oProperty.GetCustomAttributesData(); foreach (var oAttr in lstAttr) {
//得到每一個特性類的全稱 Console.WriteLine("特性類的名稱" + oAttr.AttributeType.FullName); Console.WriteLine("特性類成員如下:");
//得到特性類的所有參數 var lstAttrArgu = oAttr.NamedArguments; foreach (var oAttrAru in lstAttrArgu) {
//取每個特性類參數的鍵值對 Console.WriteLine(oAttrAru.MemberName + "=" + oAttrAru.TypedValue.Value); } //Console.WriteLine(oAttr.AttributeType+"——"+oAttr.NamedArguments); } } return Json(new { }, JsonRequestBehavior.AllowGet); }
GetModelByType方法結果簡單構造下然后將屬性的鍵值對返回給js方法,然后再由js追加到界面上面。這樣通過特性和反射的結合能很快完成這個小功能的設計。
(2)類的方法上面特性的用法:
這個用法.Net framework里面就很多,如果MVC里面Filter過濾器的用法:
public class SuperLogStat : ActionFilterAttribute { //模塊名稱 private EnumModuleName moduleEnum = EnumModuleName.ModuleOther; //功能名稱 private string functionName = string.Empty; //用戶Id private string userId = string.Empty; public string Version { get { return ConfigurationManager.AppSettings["UploatStatVersion"]; } } public EnumModuleName ModuleEnum { get { return this.moduleEnum; } set { this.moduleEnum = value; } } public string FunctionName { get { return this.functionName; } set { this.functionName = value; } } //這兩個方法都是父類的virtual方法,一個再return View()之前執行,一個再之后執行 // // 摘要: // 在執行操作方法之前由 MVC 框架調用。 // // 參數: // filterContext: // 篩選器上下文。 public override void OnActionExecuting(ActionExecutingContext filterContext) { try { string userName = filterContext.HttpContext.User.Identity.Name; this.userId = userName.Replace("china\\", ""); } catch (Exception) { this.userId = string.Empty; } } // // 摘要: // 在執行操作結果之前由 MVC 框架調用。 // // 參數: // filterContext: // 篩選器上下文。 public override void OnResultExecuting(ResultExecutingContext filterContext) { UserLogUtils.LogUserStatic(this.userId, this.Version, EnumUtils.getEnumDescByValue((int)this.moduleEnum, typeof(EnumModuleName)), this.functionName); } }
在Controller里面方法上面加上特性:
//調用 [SuperLogStat(ModuleEnum = EnumModuleName.ModuleHome, FunctionName = "待審核")] public ActionResult MyApplyToAuditing() { return View(); }
這個ActionFilterAttribute這個特性用法里面就有異常的攔截機制,和前面說的自定義的異常攔截是相同的。
(3)類上面特性的用法:
類上面特性的用法其實.Net里面也很多。比如為了避免new一個對象而使用的MEF就是一個很有說服力的例子:
在定義個類實現一個接口時:
[Export("Impc_TB_Test", typeof(Ifc_TB_Test))] public class Impc_TB_Test : Ifc_TB_Test { ...... }
定義接口沒有任何特殊:
public interface Ifc_TB_Test { ...... }
然后在使用時只需要加一個[Import]標簽,這個變量就會在編譯時自動new一個Impc_TB_Test變量:
[Import("Impc_TB_Test")] Ifc_TB_Test service { get; set; }
在使用service變量時,就可以直接把它當做一個Impc_TB_Test對象來使用。是不是很方便。
這幾種常見用法都是博主用過的覺得比較好的場景,當然特性的用法肯定遠不止如此,歡迎大俠們指正拍磚~~
