首頁 >後端開發 >C#.Net教程 >C#中關於反射和dynamic最佳組合的範例分享

C#中關於反射和dynamic最佳組合的範例分享

黄舟
黄舟原創
2017-09-07 13:32:471577瀏覽

這篇文章主要介紹了C# 反射與dynamic最佳組合範例程式碼,需要的朋友可以參考下

在C# 中反射技術應用廣泛,至於什麼是反射....... ..你如果不了解的話,請看下段說明,否則請跳過下段。廣告一下:喜歡我文章的朋友請注意我的blog,這也有助於提升本人寫作的動力。

反射:當你背對一個美女或帥哥卻不能回頭仔細觀察研究時(純屬虛構,如有巧合、純屬雷同),一面小鏡子就能滿足你的需求。在C# 程式設計過程中也常遇到類似的情況:有一個別人寫的dll 類別庫你想使用卻沒程式文件資料...此時透過C# Runtime 提供的功能,你可以把該dll 類庫載入到你的程式中,並細細研究dll 的每一部分內容,這就是C# 中的反射。

個人認為反射最突出的優點或存在的合理性:在不修改程式原始碼的情況下,實現程式功能的動態調整(Runtime動態物件建立)

#範例:


 interface IRun {
  void Run();
 }
 class Person : IRun
 {
  public void Run()
  {
   Console.WriteLine("走,去LOL啊!");
  }
 }
 class Car : IRun
 {
  public void Run()
  {
   Console.WriteLine("呜...........");
  }
 }
 class Program
 {
  static void Main(string[] args)
  {
   IRun e = new Person();
   e.Run();
   Console.ReadLine();
  }
 }

如果將上面的Run功能並不一定是由Person來執行,有時需要是Car有時需要Person。常見的解決方案是添加 if 等判斷結構,如下:


 static void Main(string[] args)
  {
   Console.WriteLine("请输入:Car或Person");
   string type = Console.ReadLine();
   IRun e = null;
   if ("Car" == type)
   {
    e = new Car();
   }else if("Person" == type)
   {
    e = new Person();
   }
   if(null != e)
    e.Run();
   Console.ReadLine();
  }

這種結構確是解決了現在的需求,但並不健壯。隨著 IRun 介面實作、相關類別的繼承的增加,上面的判斷結構也會快速成長。物件導向程式設計、設計模式都遵循的一大原則就是封裝變換,所以上面的程式無法很好的應對變化。在此我們並不涉及 「設計模式的」 的知識,因此下面的範例程式碼只為簡化上面的程式、並未刻意套用設計模式相關知識。如下:     


 static void Main(string[] args)
  {
   Console.WriteLine("请输入:Car或Person");
   string type = Console.ReadLine();
   string classPath = String.Format("namespace.{0}", type);
   IRun e = Activator.CreateInstance(null, classPath).Unwrap() as IRun;

   if(null != e)
    e.Run();
   Console.ReadLine();
  }

經過上面的修改,程式可自行根據使用者的輸入,透過Activator.CreateInstance建立IRun 的實例,程式此處不會再隨IRun 的實現者增多這種問題的影響而改變。上面的這種優點就是透過反射得到的,也是我所認為的「反射存在的合理性」。

Activator、Assembly 實作反射方式建立物件

C#中反射方式建立物件可以透過Activator.CreateInstance(靜態)和A​​ssembly.CreateInstance (非靜態)來實現,其中Assembly.CreateInstance 內部呼叫的仍是Activator.CreateInstance。

根據要動態建立的類型物件是否處於目前組件之中,可將反射建立物件分為:建立組件內的類型物件與建立組件外的類型物件。

建立組件內的類型物件


  private static void ReflectionIRun1(string className)
  {
   string classPath = String.Format("namespace.{0}", className);
   //参数 null ,指出所要创建类型对象位于当前程序集 
   var handler = Activator.CreateInstance(null, classPath);
   IRun e = (IRun)handler.Unwrap();
   Console.WriteLine(e.Run());
  }
  private static void ReflectionIRun2(string className)
  {
   string classPath = String.Format("namespace.{0}", className);
   //typeof(IRun).Assembly 获取 IRun 类型所在的程序集
   object obj = typeof(IRun).Assembly.CreateInstance(null, classPath);
   IRun e = (IRun)obj;
   Console.WriteLine(e.Run());
  }

建立組件外的類型物件

專案中增加一個類別庫(另一個組件),如下圖:

#新增一個Boss 類,如下:


#
namespace Lib
{
 public class Boss
 {
  private string name = "老大";
  
  public string Name{
   get {return name;}
  }
  public string Talk()
  {
   return "你们都被开除了......";
  }
  //老板不会算账,总是多付钱,所以很有自知之明的将Payfor设为private,防止外部人员调用
  private int Payfor(int total)
  {
   return total + 10;
  }
 }
}

取得一個Boss 物件前,先加入Lib 的引用,取得範例如下:


 private static void ReflectionBoss1()
  {
   string classPath ="Lib.Boss";
   //"Lib" 参数指明要加载的程序集(即要创建的对象类型在哪个程序集中定义)
   var handler = Activator.CreateInstance("Lib", classPath);
   Boss b = handler.Unwrap() as Boss;
   Console.WriteLine(b.Talk());
  }
  private static void ReflectionBoss2()
  {
   string classPath ="Lib.Boss";
   //Assembly.Load("Lib") 加载的程序集(即要创建的对象类型在哪个程序集中定义)
   var assembly = Assembly.Load("Lib");
   Boss b = (Boss)assembly.CreateInstance(classPath);
   Console.WriteLine(b.Talk());
  }

關於反射時CLR如何找出並定位要載入的程序集,請參考MSDN中關於反射相關的知識。

反射存取欄位、呼叫方法(屬性)

反射除可以幫我們動態建立物件外,還可幫我們動態存取物件的方法(屬性)或字段,因C# 版本不同具體方法會有變更或擴展,更深入內容請參考MSDN。以下僅為簡單範例(標準用法)。

為老闆改名,範例:      


#
 private static void ReflectionBoss1()
  {
   string classPath = "Lib.Boss";
   //"Lib" 参数指明要加载的程序集(即要创建的对象类型在哪个程序集中定义)
   var handler = Activator.CreateInstance("Lib", classPath);
   Boss b = handler.Unwrap() as Boss;
   //关键代码
   FieldInfo f = b.GetType().GetField("name", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
   f.SetValue(b, "小二");
   Console.WriteLine("{0}:{1}", b.Name, b.Talk());
  }

輸出:

讓老闆付錢:


private static void ReflectionBoss1()
  {
   string classPath = "Lib.Boss";
   //"Lib" 参数指明要加载的程序集(即要创建的对象类型在哪个程序集中定义)
   var handler = Activator.CreateInstance("Lib", classPath);
   Boss b = handler.Unwrap() as Boss;
   //关键代码
   MethodInfo method = b.GetType().GetMethod("Payfor", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance);
   object money = method.Invoke(b, new object[] { 10 });
   Console.WriteLine("DW039:老大给我报销10元钱车费......");
   Console.WriteLine("{0}:.....,算不清了,给你这些吧。",b.Name);
   Console.WriteLine("DW039:......");
   Console.WriteLine("{0}:{1}", b.Name,money);
   Console.WriteLine("DW039:老大你真棒!");
  }

輸出:

#dynamic 對反射雙劍合璧


######################################################################################################################################################################################################## #因為反射是運行時的類型操作,所以在程式設計時面臨類型不確定的問題。根據上一篇《C# 匿名物件(匿名類型)、var、動態類型 dynamic》講得 dynamic 動態類型結合我們編寫的反射程序,可以大幅優化程序邏輯(存取受保護等級限制的程式碼不在此範圍內)。 ######上面程式碼的最佳化:############
private static void ReflectionBoss1()
  {
   string classPath ="Lib.Boss";
   var handler = Activator.CreateInstance("Lib", classPath);
   dynamic b = handler.Unwrap();
   Console.WriteLine(b.Talk());
  }
  private static void ReflectionBoss2()
  {
   string classPath ="Lib.Boss";
   var assembly = Assembly.Load("Lib");
   dynamic b = assembly.CreateInstance(classPath);
   Console.WriteLine(b.Talk());
  }
###透過dynamic 動態類型物件b 來調用反射得到物件的屬性、方法可直接調用,從而省去了頻繁的類型轉換操作。 #########反射常見應用場景######

應用程式場景我印象最深刻的是 MS Petshop 範例,從SQL Server 資料庫切換到 oracle 資料庫時反射取得不同的資料存取層。然我實際專案中從未遇到過中途切換資料庫的情況,其他應用程式場景基本上類似上面的範例。如果朋友你發現更多的應用場景,請給予補充,3ks。

反射的優缺點

優點:反射使程式更靈活

缺點:反射運行速度相對較慢

#至於反射相比普通程序慢,我沒有進行過測試也不打算進行。現實情況是:Ms提倡使用 dynamic、Mvc流行、Ms對CLR不斷優化、機器性能的提升,所以你在開發中無需過多考慮反射的性能問題。如果你寫的程式運行速度出現了瓶頸(應先確保自己程式寫的合理),研究一下資料庫最佳化、資料快取、web快取、負載平衡等技術我認為更實際一些。

以上是C#中關於反射和dynamic最佳組合的範例分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn