C#語法——反射,架構師的入門基礎。

来源:https://www.cnblogs.com/kiba/archive/2018/08/27/9446905.html
-Advertisement-
Play Games

前言 編程其實就是寫代碼,而寫代碼目的就是實現業務,所以,語法和框架也是為了實現業務而存在的。因此,不管多麼高大上的目標,實質上都是業務。 所以,我認為不要把寫代碼上升到科學的高度。上升到藝術就可以了,因為藝術本身也沒有高度。。。。 軟體設計存在過度設計,語法和框架的理解,也存在過度理解。比如,反編 ...


前言

編程其實就是寫代碼,而寫代碼目的就是實現業務,所以,語法和框架也是為了實現業務而存在的。因此,不管多麼高大上的目標,實質上都是業務。

所以,我認為不要把寫代碼上升到科學的高度。上升到藝術就可以了,因為藝術本身也沒有高度。。。。

軟體設計存在過度設計,語法和框架的理解,也存在過度理解。比如,反編譯下,看看反射是怎麼實現的。。。

有興趣是好事,但就算知道了反射的本質,瞭解了反射是如何設計的,你技術也沒什麼質的改變。因為,技術水平最終還是要落實到應用上。

在比如,過度的追求代碼性能,也不見得是一件好事,因為,[大多數]情況下,硬體比程式員便宜多了。。。(註意這裡指的是代碼不是演算法和資料庫性能)

所以,不論什麼事,過度了,總不是好事。

----------------------------------------------------------------------------------------------------

本篇文章主要介紹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 < CommandTypeNames.Length - 1; i++)
        {
            nameSpace += CommandTypeNames[i];
            if (i < CommandTypeNames.Length - 2)
            {
                nameSpace += ".";
            }
        } 
        return nameSpace;
    }

    public static object Excute(CommandBase command)
    {
        string fullName = command.GetType().FullName;//完全限定名
        string nameSpace = GetNameSpace(command);//命名空間  
        Assembly assembly = Assembly.Load(nameSpace);
        Type handlerType = assembly.GetType(fullName + classNamePostfix, true, false);
        object obj = assembly.CreateInstance(fullName + classNamePostfix);
        MethodInfo handleMethod = handlerType.GetMethod(methodName);//獲取函數基本信息
        object[] pmts = new object[] { command }; //傳遞一個參數command
        try
        {
            return handleMethod.Invoke(obj, pmts);
        }
        catch (TargetInvocationException tie)
        {
            throw tie.InnerException;
        }
    }
}
public class GetNameCommandHandler
{
    public ResultBase Excute(CommandBase cmd)
    {
        GetNameCommand command = (GetNameCommand)cmd;
        ResultBase result = new ResultBase();
        result.Message = "I'm Kiba518";
        return result;
    }
}
public class GetNameCommand: CommandBase
{  
} 
public class CommandBase
{ 
    public int UserId { get; set; } 
     
    public string UserName { get; set; } 
    
    public string ArgIP { get; set; } 
}
public class ResultBase
{ 
    public string Message { get; set; } 
}

代碼中框架很簡單,主要目的是實現一個代理,用於處理繼承了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<PropertyInfo> 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代碼,然後使用吧。

答案是,當然有人可以熟練應用。反射是架構師的入門基礎,任何一個[可以實戰]的架構師,都需要隨時隨地的可以手寫出反射,因為優化框架是他們的責任。

所以,對此有所懷疑的小伙伴,可以努力練習了,將委托融入血液,是高級軟體工程師的基礎,而將反射融入血液,就是架構師的基礎了。

C#語法——元組類型

C#語法——泛型的多種應用

C#語法——await與async的正確打開方式

C#語法——委托,架構的血液

C#語法——事件,逐漸邊緣化的大哥。

C#語法——消息,MVVM的核心技術。

我對C#的認知。

----------------------------------------------------------------------------------------------------

註:此文章為原創,歡迎轉載,請在文章頁面明顯位置給出此文鏈接!
若您覺得這篇文章還不錯,請點擊下右下角的推薦】,非常感謝!
如果您覺得這篇文章對您有所幫助,那就不妨支付寶小小打賞一下吧。 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前面給大家介紹了IDEA的安裝和基本配置,睡覺前Alan再給大家分享一下使用IDEA創建Java Web並部署訪問。 打開IDEA,File>New>Project,進入Java Enterprise創建一個Web Application項目,選擇使用的JDK 點擊Next 修改一下項目的名稱點擊F ...
  • 一.構建工程 1.引入依賴 2.創建主類 3.配置application.properties 這裡存在 api-a 和 api-b 兩個微服務應用, 當請求http://localhost:port/api-a/helloWorld, 會被路由轉發至 api-a 服務的 /helloWorld 接 ...
  • 網路傳輸模型 基本模型 層次劃分 需要說明的是在網路傳輸層TCP可靠而UDP不可靠 傳輸層說明 說明一: 作為Python開發,咱們都是在應用層的HTTP協議之上進行開發的。 說明二: 網路編程,主要是瞭解我們Python能編寫的最低的層次, 即傳輸層的基本情況。 說明三: HTTP協議是基於TCP ...
  • 馬上又是一個金九銀十的招聘旺季,小編在這裡給大家整理了一套各大互聯網公司面試都喜歡問的一些問題或者一些出場率很高的面試題,給在校招或者社招路上的你一臂之力。 首先我們需要明白一個事實,招聘的一個很關鍵的因素是在給自己找未來的同事,同級別下要找比自己優秀的人,面試是一個雙向選擇的過程,也是一個將心比心 ...
  • 一、生成器的定義 在函數中使用yield關鍵字,由函數返回的結果就是生成器。 1 def gen(): 2 print('gen') #函數內部的代碼不執行 3 yield 0 4 yield 1 5 yield 2 6 7 g = gen() 8 print(g) 9 print(next(g)) ...
  • 集群健康檢查 取得健康狀態 GET /_cat/health?v 返回: 健康狀態分類 green:索引的primary shard和replica shard都是active狀態的 yellow:索引的primary shard都是active狀態的,但是部分replica shard不是acti ...
  • 參考來源:https://www.cnblogs.com/liwenzhou/p/8747872.html ...
  • 小編也不知道大家能不能用的到,我只是把我學到的知識分享出來,有需要的可以看一下。python本身就是一個不斷更新改進的語言,不存在抄襲,有需要就可以拿過來用,在用的過程中,你發現可以用另外一種方法把它實現,就可以把代碼做進一步的優化,然後分享出來,這樣python會變的越來越實用。今天心情不好,分享 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...