.Net中通過反射技術的應用----插件程序的開發入門


再開始之前,先學習基本基本的概念.

程序集:所有.Net類都是定義在某個Assembly(程序集)中的,.Net基本類是定義在mscorlib.dll中。exe也可以看做是類庫,也可以引用。.net的exe也是Assembly,

.net中的exe和dll的區別就是exe中包含入口函數,其他沒有區別,exe也可以當成dll那樣引用、也可以反編譯。

GAC:全局程序集緩存。公用的Assembly放到GAC中,我們新建一個項目,會發現引用的程序集,如system,找不到這個dll放在哪里,實質上系統已注冊到全局GAC中

程序集包含描述它們自己的內部版本號和它們包含的所有數據和對象類型的詳細信息的元數據

程序集具有以下特點: 程序集作為 .exe 或 .dll 文件實現。 通過將程序集放在全局程序集緩存中,可在多個應用程序之間共享程序集。 要將程序集包含在全局程序集緩存中,必須對程序集進行強命名

有關更多信息,請參見具有強名稱的程序集。

程序集僅在需要時才加載到內存中。 可以使用反射以編程方式獲取關於程序集的信息。

如果加載程序集的目的只是對其進行檢查,應使用諸如 ReflectionOnlyLoadFrom 的方法。 可以在單個應用程序中使用相同程序集的兩個版本

OK,編寫以下代碼

獲取當前程序已加載的所有程序集名稱
        static void Main(string[] args)
{
//獲取當前程序已經加載的所有程序集
System.Reflection.Assembly [] arry= AppDomain.CurrentDomain.GetAssemblies();
foreach (var item in arry)
{
string str = item.FullName;
Console.WriteLine(str.Substring(0,str.IndexOf(',')));
}
Console.Read();
}

運行結果:

上述是獲取程序已經加載的程序集,如何動態從文件加載Assembly,不需要在編譯的時候引用該文件。

首先新建一個類庫項目 CLib

CLib中Person類
namespace CLib
{
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void Say()
{
Console.WriteLine("{0}{1}歲",Name,Age);
}
}
}
CLi中的IFace接口
namespace CLib
{
interface IFace
{
void ShowPic();
}
}

 編譯CLib項目,將CLib.dll拷貝到D盤根目錄下.

編寫以下代碼,獲取CLib.dll中的信息

View Code
    System.Reflection.Assembly asm=System.Reflection.Assembly.LoadFile(@"D:\CLib.dll");
//獲取程序集中所有的數據類型
Type[] typeArray = asm.GetTypes();
foreach (var item in typeArray)
{
Console.WriteLine(item.Name);
}

程序運行結果:

到這里已經能夠從文件加載Assembly,並且能獲取到Assembly中的數據類型,那么下一步可以如何調用CLib中Person類中的Say()方法呢?

編寫以下代碼:

標注出來的4個方法,是因為Person中的Name屬性和Age屬性的get;set;編譯而成。這個可以通過反編譯工具進行查看.

從文件加載Assembly,並且調用方法
 System.Reflection.Assembly asm=System.Reflection.Assembly.LoadFile(@"D:\CLib.dll");
//獲取程序集中所有的數據類型
Type[] typeArray = asm.GetTypes();
foreach (var item in typeArray)
{
Console.WriteLine(item.FullName);
}
//這里的參數是完全限定名稱.
Type typePerson = asm.GetType("CLib.Person");
if (typePerson != null)
{
//獲取類型中所有的成員(屬性+方法)
System.Reflection.MemberInfo [] memberArray= typePerson.GetMembers();
foreach (var item in memberArray)
{
Console.WriteLine(item.Name);
}
//獲取say方法
System.Reflection.MethodInfo methodSay = typePerson.GetMethod("Say");
//程式中並沒有添加CLib的引用,所以這里不能通過直接new的方式來創建Person的實例
object objPerson = Activator.CreateInstance(typePerson);
//調用Say方法,第一個參數表述say方法是哪個對象的,第二個參數是這個方法的參數
methodSay.Invoke(objPerson, null);
}
Console.Read();

到這里,已經能夠從文件加載Assembly,並且調用其方法,再介紹幾個方法

Type類可以叫做“類的類”,一個類型對應一個Type類的對象,通過Type對象可以獲得類的所有的定義信息,比如類有哪些屬性、哪些方法等。Type就是對類的描述。

 獲得Type對象的方法:

1、通過類獲得Type:Type t = typeof(Person)

2、通過對象獲得類的Type:Type t = p.GetType()

獲得Assembly中定義的所有的public類型
typeArray= asm.GetExportedTypes();

在CLib項目中,新增一個類Chinese,繼承與Person類

View Code
    public class Chinese:Person
{
public void HouseholdRegister()
{
Console.WriteLine("景德鎮的鎮戶口超牛13。");
}
}

在AssemblyDemo中編寫

View Code
            Assembly asm = Assembly.LoadFile(@"D:\CLib.dll");
Type typePerson = asm.GetType("CLib.Person");
Type typeChinese = asm.GetType("CLib.Chinese");
#region IsAssignableFrom 判斷是否是"父子關系",這里可以是類,也可以是接口
//判斷能否把typeChinese賦值給typePerson
typePerson.IsAssignableFrom(typeChinese);
typeChinese.IsAssignableFrom(typePerson);
#endregion

#region IsInstanceOfType 判斷對象是否是某類型,當前類可以是o的類、父類、接口
//創建Person類型
object obj = Activator.CreateInstance(typePerson);
//判斷obj是typePerson類型
typePerson.IsInstanceOfType(obj);
#endregion

#region IsSubclassOf(c) 判斷調用者是否是c的子類
typePerson.IsSubclassOf(typeChinese);
#endregion

到這里,學習和復習了簡單的反射,有了這些技術點,就可以開始插件程序的編寫。

插件程序實現功能如下:

開發一個簡單的“記事本”程序,此程序的功能可通過插件進行擴展.

我們現在開發這一款“記事本”程序,並不知道將來以后別人會開發出怎樣的功能,所以主程序開發者需要確定幾個固定的方法,以后的插件開發者必須在插件中提供這幾個方法給主程序使用。用什么方式來實現這個呢?---接口。so.先定義好接口

新建一個類庫項目IEditorPro,在IEditorPro中新增接口

IEditor
namespace IEditorPro
{
interface IEditor
{
/// <summary>
/// 插件名稱
/// </summary>
string Name
{
get;
set;
}
/// <summary>
/// 插件執行程式,為了簡單起見,這里直接把記事本的內容傳遞過來
/// </summary>
void Execute(TextBox txt);
}
}

新建一個Winform項目Notepad,在主窗體上拖一個TextBox和一個菜單欄

在Notepad新建Addins文件夾,規定用於存放插件,程序在啟動時,檢測Addins文件夾下的插件,如果有,則進行加載

View Code
        private void FrmMain_Load(object sender, EventArgs e)
{

string dir = Assembly.GetEntryAssembly().Location;
dir = Path.GetDirectoryName(dir);
dir = Path.Combine(dir, "Addins");
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
//掃描下所有的dll文件
string[] dlls = Directory.GetFiles(dir, "*.dll");
foreach (var dllName in dlls)
{
Assembly asm = Assembly.LoadFile(dllName);
Type[] typeArray = asm.GetExportedTypes();
foreach (var item in typeArray)
{
Type typeIEditor=typeof(IEditor);
//必須是實現了IEditor接口,並且不能是抽象類
if (typeIEditor.IsAssignableFrom(item)
&& !item.IsAbstract)
{
IEditor editor=Activator.CreateInstance(item) as IEditor;
if (editor != null)
{
ToolStripItem tsmiItem= tsmiTools.DropDown.Items.Add(editor.Name);
//在這里傳遞editor。在Click事件中調用
tsmiItem.Tag = editor;
tsmiItem.Click += new EventHandler(tsmiItem_Click);
}
}
}
}
}

void tsmiItem_Click(object sender, EventArgs e)
{
ToolStripItem item = sender as ToolStripItem;
IEditor editor = item.Tag as IEditor;
editor.Execute(this.txtValue);
}

主程序就開發完成了,此時運行程序,可見工具欄下無任何功能.

下面,我們開始為這個程序開發新的插件.首先來開發一個轉換大小寫的插件.

新建一個類庫項目PluginToUpper,添加IEditorPro.dll的引用,新建一個類ChangeToUpper,實現接口IEditor

View Code
    public class ChangeToUpper:IEditor
{
public string Name
{
get
{
return "大小寫轉換";
}
set
{
}
}

public void Execute(TextBox txt)
{
txt.Text = txt.Text.ToUpper();
}
}

編譯PluginToUpper項目,將PluginToUpper.dll拷貝到Notepad的Addins文件夾下.

再次運行Notepad時,插件已經OK,並且可以正常使用

再編寫一個稍微復雜的插件,改變字體和字號的插件

新建一個類庫項目,PluginToChangeStyle,,添加IEditorPro.dll的引用,新建一個類ChangeStyle,實現接口IEditor

因為我們要彈出窗體,讓用戶選擇字體和字號,so.. 在PluginToChangeStyle新增一個窗口

用戶選擇字體和字號以后,通過委托方式來傳遞用戶選擇的字體和字號

選擇字體
namespace PluginToChangeStyle
{
public delegate void SetFontDelegate(string family,float size);
public partial class FrmSetFont : Form
{
public SetFontDelegate setFont;
public FrmSetFont()
{
InitializeComponent();
}

private void FrmSetFont_Load(object sender, EventArgs e)
{
//獲取計算機中所有的安裝字體
InstalledFontCollection fc = new InstalledFontCollection();
foreach (FontFamily font in fc.Families)
{
this.cbFontFamily.Items.Add(font.Name);
}
}

private void button1_Click(object sender, EventArgs e)
{
string family = cbFontFamily.Text;
float size = float.Parse(cbFontSize.Text);
setFont(family, size);

this.DialogResult = System.Windows.Forms.DialogResult.OK;

}
}
}

ChangeStyle類的代碼:

ChangeStyle
namespace PluginToChangeStyle
{
public class ChangeStyle:IEditor
{
public string Name
{
get
{
return "修改字體";
}
set
{
}
}
private TextBox txtBox;
public void Execute(TextBox txt)
{
txtBox = txt;
FrmSetFont frm = new FrmSetFont();
//通過委托方式在兩個窗體之間傳遞內容
frm.setFont = SetTextBoxFont;
frm.ShowDialog();
}
/// <summary>
/// 設置字體、字號
/// </summary>
/// <param name="family"></param>
/// <param name="size"></param>
public void SetTextBoxFont(string family, float size)
{
txtBox.Font = new System.Drawing.Font(family, size);
}
}
}


編譯PluginToChangeStyle項目,將PluginToChangeStyle.dll拷貝Notepad的Addins文件夾下.

再次運行Notepad時,我們編寫的改變字體字號插件已經OK,並且可以正常使用

 

 相關的代碼下載:

http://115.com/file/ani80ym1#插件Demo.zip

 代碼寫得很粗糙,主要是方便初學者的理解.主程序在掃描插件的時候,還需要考慮很多因素,也可以使用linq方式來獲取.

這個插件還可以應用於MVC,實現補丁式開發.主要原理是將注冊視圖的代碼分離,實現無縫補丁升級模式.

有空再再寫.

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM