C#語法——反射,架構師的入門基礎。
前言
程式設計其實就是寫程式碼,而寫程式碼目的就是實現業務,所以,語法和框架也是為了實現業務而存在的。因此,不管多麼高大上的目標,實質上都是業務。
所以,我認為不要把寫程式碼上升到科學的高度。上升到藝術就可以了,因為藝術本身也沒有高度。。。。
軟體設計存在過度設計,語法和框架的理解,也存在過度理解。比如,反編譯下,看看反射是怎麼實現的。。。
有興趣是好事,但就算知道了反射的本質,瞭解了反射是如何設計的,你技術也沒什麼質的改變。因為,技術水平最終還是要落實到應用上。
在比如,過度的追求程式碼效能,也不見得是一件好事,因為,[大多數]情況下,硬體比程式設計師便宜多了。。。(注意這裡指的是程式碼不是演算法和資料庫效能)
所以,不論什麼事,過度了,總不是好事。
本篇文章主要介紹C#反射【用法】。
反射是架構師必會的基礎,因為任何一個被設計出來的框架,都要使用反射。
反射也是最隱蔽的語法,因為反射寫出來後,通常它會被直接封裝,然後呼叫者就只負責使用,不再關注他的具體實現。
這與它的特性有關,因為反射就是為了減少程式碼冗餘而存在的,所以,看不見很正常。
反射的定義
官方定義:反射提供了封裝程式集、模組和型別的物件(Type 型別)。可以使用反射動態建立型別的例項,將型別繫結到現有物件,或從現有物件獲取型別並呼叫其方法或訪問其欄位和屬性。如果程式碼中使用了屬性,可以利用反射對它們進行訪問。
看不懂?沒關係,我們把它翻譯成人類可理解的語言。
C#程式語言中,最常使用的是類和類中的函式和屬性。正向呼叫的方法是,建立類,然後用類建立一個物件。接下來就可以用這個物件呼叫類中的方法和屬性了。
而反射,就是相對於這種正向呼叫的存在。即,它是反向呼叫。
反射可以透過類名的字串來建立類,可以透過函式名的字串和屬性名的字串,來呼叫類下的函式和屬性。
有同學會問了, 既然正向可以呼叫,那麼反向呼叫幹什麼呢?
會有這種問題的同學,先彆著急,繼續往下看,反射既然存在,就必然有存在的道理。
反射的基礎應用
1,類反射
先看下面程式碼;程式碼為透過類名稱的字元,反射出類的物件。
public class ReflectionSyntax
{
public static void Excute()
{
Type type = GetType("Syntax.Kiba");
Kiba kiba = (Kiba)Activator.CreateInstance(type);
Type type2 = GetType2("Syntax.Kiba");
Kiba kiba2 = (Kiba)Activator.CreateInstance(type2);
}
public static Type GetType(string fullName)
{
Assembly assembly = Assembly.Load("Syntax");
Type type = assembly.GetType(fullName, true, false);
return type;
}
public static Type GetType2(string fullName)
{
Type t = Type.GetType(fullName);
return t;
}
}
public class Kiba
{
public void PrintName()
{
Console.WriteLine("Kiba518");
}
}
在程式碼中我們看到,反射時傳遞了字串"Syntax.Kiba",然後透過解析字串,獲取到了該字串對應的類的型別,最後再借助Activator來輔助建立類的例項。
其中字串"Syntax.Kiba"是一個完全限定名。什麼是完全限定名?完全限定名就是名稱空間+類名。在反射的時候,需要我們傳遞完全限定名來確定到底要去哪個名稱空間,找哪個類。
在程式碼中我們還可以看到,獲取型別的方式有兩種,一種是較複雜的,一種是簡單的。
GetType2方法是簡單的獲取類別,透過Type直接就解析了字串。而GetType則先進行了載入Assembly(元件),然後再由元件獲取型別。
兩者有什麼區別呢?
區別是,用Type直接解析,只能解析當前名稱空間下的類。如果該類存在於引用的DLL中,就解析不了。
而GetType方法中的[Assembly.Load指定了程式集名],所以,在反射時,就會去指定的名稱空間裡找對應的類。這樣就能找到非本程式集下的類了。
[Assembly.Load指定了程式集名]這句話不好理解?
沒關係,換個表達,Assembly.Load指定了名稱空間的名稱,所以反射時,會去這個名稱空間裡找類,這樣是不是就好理解了。
Assembly
Assembly的存在讓反射變得特別靈活,其中Assembly.Load不止可以匯入我們引入的程式集(或名稱空間)。
也可以匯入我們未引入程式集的dll。呼叫模式如下:
System.Reflection.Assembly o = System.Reflection.Assembly.Load("mscorlib.dll");
Assembly匯入了程式集後,還可以不借助Activator來輔助,自己就可以建立類。如下:
Assembly assembly = Assembly.Load("Syntax");
Kiba kiba = (Kiba)assembly.CreateInstance("Syntax.Kiba");
有的同學可能會擔心效能,會覺得這樣反射,會使程式變慢。
有這種想法的同學,其實你已經是在過度理解語法了。這種地方的程式碼效能其實是可以不用關心的。
那麼,到底會不會變慢呢?
答案是這樣的,如果你是使用完全限定名來反射,速度就是一樣的。如果是反射時,只寫了一個類名,那麼速度就會變慢。因為它要遍歷所有的名稱空間,去找這個類。
即,只要反射時把類的名稱空間寫全,那麼速度就不會慢。
2,函式反射
函式的反射應用主要是使用類MethodInfo類反射,下面先看下基礎應用。
public static void ExcuteMethod()
{
Assembly assembly = Assembly.Load("Syntax");
Type type = assembly.GetType("Syntax.Kiba", true, false);
MethodInfo method = type.GetMethod("PrintName");
object kiba = assembly.CreateInstance("Syntax.Kiba");
object[] pmts = new object[] { "Kiba518" };
method.Invoke(kiba, pmts);//執行方法
}
public class Kiba
{
public string Name { get; set; }
public void PrintName(string name)
{
Console.WriteLine(name);
}
}
一些同學第一眼看上去可能會有點不適應,因為好像很多類都是大家不經常用的。這也沒辦法,因為這是一個進階的過程,必須經歷從陌生到熟悉。當你熟悉了這樣的程式碼後,就代表你的技術水平又進步了一個臺階。
下面講解一些這些程式碼。
首先我們匯入了名稱空間,接著我們獲取了該名稱空間下Kiba這個類的型別;接下來我們透過這個型別來獲取指定名稱的函式。
然後我們透過Assembly建立了一個Kiba的例項,接著定義了一個引數的Object陣列,因為Kiba類下的函式PrintName只有一個引數,所以,我們只為這個Object陣列新增一個物件[Kiba518]。
最後,我們透過method.Invoke來呼叫這個函式,由於是反射,所以呼叫時,需要指定Kiba類的例項物件和入參。
這樣,函式的反射就實現了。
3,屬性反射
屬性反射是用PropertyInfo類來實現,下面看基礎的屬性反射。
public static void ExcuteProperty()
{
Kiba kiba = new Kiba();
kiba.Name = "Kiba518";
object name = ReflectionSyntax.GetPropertyValue(kiba, "Name");
Console.WriteLine(name);
}
public static object GetPropertyValue(object obj, string name)
{
PropertyInfo property = obj.GetType().GetProperty(name);
if (property != null)
{
object drv1 = property.GetValue(obj, null);
return drv1;
}
else
{
return null;
}
}
如程式碼所示,首先我們定義了一個Kiba的物件,併為Name賦值,然後我們透過GetPropertyValue方法,傳遞了Kiba物件和要獲取值的屬性名稱。
GetPropertyValue函式里透過使用PropertyInfo完成了反射。
有的同學可能會覺得,這個很雞肋,既然已經得到物件,還反射做什麼,直接獲取就可以了呀。
彆著急,我們接下來一起看反射的架構應用。
反射的架構應用
框架編寫的核心目的之一,是統一系統秩序。那麼什麼是系統秩序呢?
首先我們看下系統的構成,系統個通常是由子系統,程式集,類,函式這四部分構成。如下圖所示。
既然系統由子系統,程式集,類,函式這四個基礎元素構成,那麼系統秩序,自然指的就是這四個元素的秩序。而這四個元素最難形成秩序的就是函式了。
很顯然,任何的專案都存在重複的函式,或者功能相近的函式。而徹底杜絕這種情況,顯然是不可能的。那麼我們只好儘量是設計會避免重複元素的框架了。而反射,正是為此而存在的。
反射的架構應用
現實中的框架因為這樣那樣的原因,會有千奇百怪的設計,所以拘泥於一種設計模式是愚蠢的,實戰中要多種設計模式一起應用,區域性設計有時候只取設計模式中一部分也可以。這樣才能實現專案的量身定製。
所以,這裡只介紹一種實戰的架構應用,一種使用反射的框架基礎結構。下面請框架基礎程式碼。
public class Client
{
public void ExcuteGetNameCommand()
{
Proxy proxy = new Proxy();
GetNameCommand cmd = new GetNameCommand();
ResultBase rb = proxy.ExcuteCommand(cmd);
}
}
public class Proxy
{
public ResultBase ExcuteCommand(CommandBase command)
{
var result = HandlerSwitcher.Excute(command);
return result as ResultBase;
}
}
public class HandlerSwitcher
{
private const string methodName = "Excute";//約定的方法名
private const string classNamePostfix = "Handler";//約定的處理Command的類的名稱的字尾
//獲取名稱空間的名稱
public static string GetNameSpace(CommandBase command)
{
Type commandType = command.GetType();//獲取完全限定名
string[] CommandTypeNames = commandType.ToString().Split('.');
string nameSpace = "";
for (int i = 0; i
程式碼中框架很簡單,主要目的是實現一個代理,用於處理繼承了CommandBase的類的代理。
即,客戶端,不論傳來什麼樣的Command,只要它是繼承自CommandBase的,這個代理都會找到對應的處理類,並執行處理,且返回結果。
為了更清晰的理解這段程式碼,我們可以參考下面這個流程圖。結合了圖片在來看程式碼,框架結構就會更清晰。
這個簡單的框架中,使用了一個概念,叫做約定優先原則,也叫做約定優於配置;喜歡概念的小夥伴可以自行百度。
框架中使用的兩個約定如下:
第一個是,處理Command的類必須字尾名是Command的類名+Handler結尾。
第二個是,處理Command的類中的處理函式名必須為Excute。
其實概念就是供大家使用的,會用即可;學習的過程中,概念之類的術語,有個印象即可。
PS:為了閱讀方便,這裡面的類都集中寫在了一個名稱空間之下了,如果有想使用這種設計模式的同學,請按照自己專案所需進行擴充套件。
這樣,我們就透過反射實現了一個非常簡約的框架,透過使用這個框架,會讓程式碼變的更加簡潔。
而為了實現每個模組的簡潔,反射也將會被封裝在各個模組的底層,所以,反射毫無疑問,就是框架設計的基礎。
反射與特性
反射在系統中另一個重要應用就是與特性的結合使用。
在一些相對複雜的系統中,難免會遇到一些場景,要講物件中的一部分屬性清空,或者要獲取物件中的某些屬性賦值。通常我們的實現方式就是手寫,一個一個的賦值。
而利用反射並結合特性,完全可以簡化這種複雜操作的程式碼量。
public partial class ReflectionSyntax
{
public void ExcuteKibaAttribute()
{
Kiba kiba = new Kiba();
kiba.ClearName = "Kiba518";
kiba.NoClearName = "Kiba518";
kiba.NormalName = "Kiba518";
ClearKibaAttribute(kiba);
Console.WriteLine(kiba.ClearName);
Console.WriteLine(kiba.NoClearName);
Console.WriteLine(kiba.NormalName);
}
public void ClearKibaAttribute(Kiba kiba)
{
List plist = typeof(Kiba).GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public).ToList();//只獲取Public的屬性
foreach (PropertyInfo pinfo in plist)
{
var attrs = pinfo.GetCustomAttributes(typeof(KibaAttribute), false);
if (null != attrs && attrs.Length > 0)
{
var des = ((KibaAttribute)attrs[0]).Description;
if (des == "Clear")
{
pinfo.SetValue(kiba, null);
}
}
}
}
}
public class Kiba
{
[KibaAttribute("Clear")]
public string ClearName { get; set; }
[KibaAttribute("NoClear")]
public string NoClearName { get; set; }
public string NormalName { get; set; }
}
[System.AttributeUsage(System.AttributeTargets.All)]
public class KibaAttribute : System.Attribute
{
public string Description { get; set; }
public KibaAttribute(string description)
{
this.Description = description;
}
}
如上述程式碼所示, 我們透過反射,將擁有KibaAttribute特性的,且描述為Clear的屬性,清空了。
當然為了一個屬性這麼做不值得,但如果一個物件有70個屬性的時候,這麼做就值得了。
既然能清除屬性的資料,那麼自然就可以為屬性賦值。至於如何實現反射賦值,相信大家可以舉一反三。
反射+特性最常見的場景
反射+特性一起應用,最常見的場景就是用ADO.NET從資料庫查詢出DataTable的資料,然後將DataTable的資料轉換成Model實體型別。
我們在開發中,為了讓實體更加充血,往往會對資料實體增加一些屬性和方法。(什麼是充血?充血就是充血模型,有興趣的同學可以自行百度瞭解下,簡單說就是為實體加屬性和方法。)
那麼,在用反射,將DataTable轉存到Model實體的時候,遍歷屬性並賦值的時候,就會多遍歷那麼幾次。
如果只是一個實體,那麼,多遍歷幾次也沒影響。但,如果是數十萬的資料,那這多幾次的遍歷影響就大了。
而用反射+特性,就可以減少這些額外遍歷次數。
講了這麼多為什麼不給程式碼呢?
因為我覺得,將上面的內容全理解的同學,應該可以說,已經框架啟蒙了。那麼,這個反射+特性的DataTable轉資料實體,如果能自己寫出來,就算是框架入門了。所以,這裡給大家留下了一個練習的空間。
注意,我這裡說的是框架,而不是架構。
框架與架構的區別是這樣的,框架是個名詞,而架構是個動詞。框架即便很熟練了,也不見得可以架構的很好。這個大家還是要注意區別。
結語
看完了整篇文章,有的同學可能會有疑問,這麼生疏的PropertyInfo和MethodInfo真的有人會用嗎?都是Copy程式碼,然後使用吧。
答案是,當然有人可以熟練應用。反射是架構師的入門基礎,任何一個[可以實戰]的架構師,都需要隨時隨地的可以手寫出反射,因為最佳化框架是他們的責任。
所以,對此有所懷疑的小夥伴,可以努力練習了,將委託融入血液,是高階軟體工程師的基礎,而將反射融入血液,就是架構師的基礎了。
注:此文章為原創,歡迎轉載,請在文章頁面明顯位置給出此文連結!
若您覺得這篇文章還不錯,請點選下右下角的【推薦】,非常感謝!
如果您覺得這篇文章對您有所幫助,那就不妨支付寶小小打賞一下吧。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/810/viewspace-2812644/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- PL/SQL基礎語法入門SQL
- C#基礎語法C#
- oracle架構的基礎知識(入門級)Oracle架構
- MongoDB for C#基礎入門MongoDBC#
- Gradle入門系列(一)——groovy基礎語法Gradle
- javascript快速入門7--ECMAScript語法基礎JavaScript
- C#基礎語法補充C#
- 系統架構基礎知識入門指南-下架構
- 系統架構基礎知識入門指南-上架構
- Python基礎入門_2基礎語法和變數型別Python變數型別
- 零基礎入門Python教程4節與基礎語法Python
- lua學習之入門(二)----基礎語法1
- java基礎語法(三十九)—反射機制(二)Java反射
- 《SpringBoot 基礎架構師》Spring Boot架構
- C語言入門基礎C語言
- Python程式設計入門基礎語法詳解Python程式設計
- IPC Kit基礎入門:理解HarmonyOS的程序間通訊架構架構
- C#基礎程式設計——簡介及基礎語法C#程式設計
- web_前端開發JS框架篇-Vue基礎入門版-基礎語法Web前端JS框架Vue
- Dart 語言基礎入門篇Dart
- 入門MySQL——基礎語句篇MySql
- Scala學習 1.1 Scala基礎與語法入門實戰
- 五道Python基礎語法面試題!Python入門Python面試題
- Python程式設計入門——基礎語法詳解(經典)Python程式設計
- 輸入和輸出基礎語法
- Java基礎-語法基礎Java
- 基礎語法
- C#程式設計基礎入門教程pdfC#程式設計
- C#快速入門教程(12)—— if語句結構C#
- Dart的基礎語法Dart
- Scala 的基礎語法
- JavaSE的基礎語法Java
- 英語語法基礎
- 大資料架構師從入門到精通大資料架構
- scala基礎語法-----Spark基礎Spark
- Java架構師 - 基礎篇(持續更新中)Java架構
- MySQL基礎架構MySql架構
- Oracle基礎構架Oracle