淺談.net插件式編程

一、動態加載控件
動態加載,最基本用到的就是反射機制。在System.Reflection的namespace下有一系列的關於獲取Assembly信息、類(型)信息的類、接口、結構等。可能上面的話對急切想實現動態加載控件的朋友來說可能一點用也沒有,那麼就看下面的代碼吧,也許可以使你馬上實現你想要的:

//加載控件
Assembly assembly = Assembly.LoadFrom(@"C:/Controls.dll");
//獲得類(型)
Type type = assembly.GetType("Controls.UserControl",false,true);
//設置篩選標誌
BindingFlags bflags = BindingFlags.DeclaredOnly | BindingFlags.Public
| BindingFlags.NonPublic | BindingFlags.Instance;
//調用構造函數並獲得對象
Object obj = type.InvokeMember("UserControl", bflags |
BindingFlags.CreateInstance, null, null, null);
//將對象轉換類型
System.Windows.Forms.Control c = (Control)obj;
//將控件添加到窗體
this.Controls.Add(c);


下面對上面程序段用到的一些變量、方法做一點說明
1、BindingFlags,枚舉類型
BindingFlags.Instance : 對象實例
BindingFlags.Static : 靜態成員
BindingFlags.Public : 指可在搜索中包含公共成員
BindingFlags.NonPublic : 指可在搜索中包含非公共成員(即私有成員和受保護的成員)
BindingFlags.FlattenHierarchy : 指可包含層次結構上的靜態成員
BindingFlags.IgnoreCase : 表示忽略 name 的大小寫
BindingFlags.DeclaredOnly : 僅搜索 Type 上聲明的成員,而不搜索被簡單繼承的成員
BindingFlags.CreateInstance : 表示調用構造函數。忽略 name。對其他調用標誌無效

2、Type.InvokeMember
public object InvokeMember(
string name,
BindingFlags invokeAttr,
Binder binder,
object target,
object[] args
);
參數
name
String,它包含要調用的構造函數、方法、屬性或字段成員的名稱。
- 或 -
空字符串 (""),表示調用默認成員。
invokeAttr
一個位屏蔽,由一個或多個指定搜索執行方式的 BindingFlags 組成。 訪問可以是 BindingFlags 之一,如Public、 NonPublic、Private、 InvokeMethod 和 GetField 等。不需要指定查找類型。如果省略查找類型, 則將應用 BindingFlags.Public | BindingFlags.Instance。
binder
一個 Binder 對象,該對象定義一組屬性並啓用綁定,而綁定可能涉及選擇重載方法、 強制參數類型和通過反射調用成 員。 - 或 - 若爲空引用(Visual Basic 中爲 Nothing),則使用 DefaultBinder。
target
要在其上調用指定成員的 Object。
args
包含傳遞給要調用的成員的參數的數組。
返回值
表示被調用成員的返回值的 Object。

二、插件編程
通過上面代碼段,我們基本實現動態加載控件。由此我想到了現在網上提到很多的插件式的開發方法。通過動態加載控件,我們不是能很方便的爲軟件擴充功能嗎?我不知道Eclipse這種插件是怎麼實現的,但至少這種動態加載控件的方法實現插件編程的一個變通的方法。不是嗎?我把一個功能模塊做成一個控件,然後在程序啓動是掃描目錄,即可獲得所有的控件,當點擊菜單是,將控件加載到窗體就行了。我在母體程序裏,我們所要做的只不過要一個容器窗口類來加載控件。當然,事先要有些約定,比如說,控件有哪些可供調用的方法等等。

 

 

反射(Reflection): 就是能夠在運行時刻查詢到類型信息的進程。他有以下的各個部分,可以根據你應用的需要選擇其中的一個或者某些來使用:
1. Assembly:使用它來定義和加載一些Assembly, 加載存在於Assembly 中的modules,並且可以得到這個Assembly的類型,同時創建他的實例MSN原文:Use Assembly to define and load assemblies, load modules that are listed in the assembly manifest, and locate a type from this assembly and create an instance of it.
 
2. Module: 可以使用它來查找到包含該moduleAssembly信息,並且可以得到該module中的各種類;同時你不單單可以得到所有的全局函數和其他變量,也可以得到定義在該module中的非全局方法MSN原文:Use Module to discover information such as the assembly that contains the module and the classes in the module. You can also get all global methods or other specific, nonglobal methods defined on the module.
 
3. ConstructorInfo: 使用它你可以查找到一個構造函數的名字,參數,訪問權限和實現的細節(比如說它是抽象的,還是虛擬的);使用某個類型的GetConstructors GetContructor方法來運行他的構造函數MSN原文:Use ConstructorInfo to discover information such as the name, parameters, access modifiers (such as public or private), and implementation details (such as abstract or virtual) of a constructor. Use the GetConstructors or GetConstructor method of a Type to invoke a specific constructor.
 
4. MethodInfo: 通過他您可以查找到某個方法的名字,返回類型,參數,訪問權限和實現的細節(比如說它是抽象的,還是虛擬的?);它也提供兩個方法來運行制定的方法,他們分別是:GetMethodsGetMethod.Use MSN原文:MethodInfo to discover information such as the name, return type, parameters, access modifiers (such as public or private), and implementation details (such as abstract or virtual) of a method. Use the GetMethods or GetMethod method of a Type to invoke a specific method.
 
5. FieldInfo: 使用它你可以查找到某個字段的名字,返回類型,參數,訪問權限和實現的細節(比如說是否是靜態的?);它提供了get set方法來訪問字段。MSN原文:Use FieldInfo to discover information such as the name, access modifiers (such as public or private) and implementation details (such as static) of a field, and to get or set field values.
 
6. EventInfo:使用它你將可以查找到某一個事件的名字,事件句柄的數據類型,自定義的屬性,聲明的類型和相關的類型等等的信息,當然你可以增加或者去除一個事件。MSN原文:Use EventInfo to discover information such as the name, event-handler data type, custom attributes, declaring type, and reflected type of an event, and to add or remove event handlers.
 
7. PropertyInfo: 使用它你可以查找到某個屬性的名字, 數據類型, 聲明的類型,關聯的類型 和只讀或者可寫狀態等一些信息; 當然你可以得到和設置他的值。MSN原文:Use PropertyInfo to discover information such as the name, data type, declaring type, reflected type, and read-only or writable status of a property, and to get or set property values.
 
8. MSN原文:Use ParameterInfo to discover information such as a parameter's name, data type, whether a parameter is an input or output parameter, and the position of the parameter in a method signature.
 
9. MSN原文:Use CustomAttributeData to discover information about custom attributes when you are working in the reflection-only context of an application domain. CustomAttributeData allows you to examine attributes without creating instances of them.
看了上面的那麼多字,你可以能還不知道怎麼應用,不過沒關係,下面將會提供一些例子,在看例子之前,我們先看看Type類。
 
System.Type
它是整個放射的中心,上面的各個部分都是圍繞着他來進行的。他能夠查詢類型名字,類型中包含的模塊和名稱空間,以及判斷該類型是值類型還是引用類型。
1. 得到類型名字:
float f = 1.0f;
Type t = f.GetType();
Console.WriteLine(t.Name);//這樣就可以得到他的類型名字了。
2. 通過一個類型名字得到一個Type:
Type t = Type.GetType(“System.Int32”);
Console.WriteLine(t.Name);
3. 下面把查詢類型的一部分列出來:
Type t = Type.GetType(typeName);
t.IsAbstract;
t.IsAnsiClass;
t.IsArray;
t.IsEnum;
t.IsClass
……
Assembly的應用
在這裏就介紹一個稱爲“插件”技術的例子作爲對這部分的總結吧!
現在使用一個現實中的例子,我在上海的某某地段開了一個超級市場,裏面有各種各種牌子的店鋪,你可以在超級市場新開設店鋪,當然有一些店鋪因爲生意不好,不得不退出超級市場。那麼在程序裏面應該怎麼樣來實現這個過程呢?因爲店鋪的開張或倒閉不是固定的,是動態的, 這樣我們就不能夠在主程序中固化這些店鋪,這個時候就需要我們動態的加載這些店鋪了,下面請看如下實現:
1.     開設超級市場:
首先我們需要一個商鋪管理類,用它來管理開店和關閉店鋪,代碼如下:
    public class ShopManager
    {
        protected static ShopManager m_ShopManager;
        protected ShopManager()
        {
        }
        public static ShopManager GetShopManager()
        {
            if (m_ShopManager == null)
            {
                m_ShopManager = new ShopManager();
            }
            return m_ShopManager;
        }
        public List<string> GetShops()
        {
            List<string> shops = new List<string>();
            string pluginFolder = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "plugins");
            if (!System.IO.Directory.Exists(pluginFolder))
                return shops;
            foreach(string pluginFile in System.IO.Directory.GetFiles(pluginFolder, "*.dll"))
            {
                try
                {
                    Assembly assembly = Assembly.LoadFile(pluginFile);
                    foreach(Type type in assembly.GetTypes())
                    {
                        if(!type.IsClass || type.IsNotPublic) continue;
                        Type[] tempInterfaces = type.GetInterfaces();
                        if(((IList)tempInterfaces).Contains(typeof(IShop)))
                        {
                            IShop shop = (IShop)CreateInstance(type);
                            shops.Add(shop.GetShopName());
                        }
                    }
                }
                catch
                {
                }
            }
            return shops;
        }
 
        protected object CreateInstance(Type type, object[] args)
        {
            try
            {
                return Activator.CreateInstance(type, args);
            }
            catch (TargetInvocationException e)
            {
                throw e.InnerException;
            }
        }
        protected object CreateInstance(Type type)
        {
            return CreateInstance(type, null);
        }
    }
接着定義個抽象的商鋪類,我們的管理類只知道這個類,代碼如下:
    public interface IShop
    {
        string GetShopName();
}
最後定義我們的超級市場類,請看代碼:
    public class Market
    {
        protected static Market m_Market;
        protected List<string> m_Shops = new List<string>();
        protected Market()
        {
            //get all shops
            m_Shops = ShopManager.GetShopManager().GetShops();
        }
        public void ShowShops()
        {
            foreach (string shop in m_Shops)
            {
                Console.WriteLine(shop);
            }
        }
        public static Market GetMarket()
        {
            if (m_Market == null)
            {
                m_Market = new Market();
            }
            return m_Market;
        }
    }
這樣整個市場就建立起來了。接着看看我們怎麼開設店鋪。
2 開設店鋪:
首先創建一個店鋪的接口類,並提供一個函數叫着:string GetShopName();
接着你就可以創建屬於你的店鋪了,這些具體的店鋪都必須繼承製店鋪的接口類,實現他的接口函數,至於你要自己要爲你開設的店鋪創建什麼功能,是你自己的事情,超級市場是不會關心的。下面是兩個店鋪的例子:
新建一個Class Library工程,命名爲NikeShop,接下來是具體的代碼:
using System;
using System.Collections.Generic;
using System.Text;
 
using SuperMarket;
namespace Shop
{
    public class NikeShop : IShop
    {
        public string GetShopName()
        {
            return "Nike";
        }
    }
}
同樣再創建一個工程,叫做AdidasShop,代碼如下:
using System;
using System.Collections.Generic;
using System.Text;
 
using SuperMarket;
namespace Shop
{
    public class AdidasShop : IShop
    {
        public string GetShopName()
        {
            return "Adidas";
        }
    }
}
最後把你生成的店鋪的dll拷貝到plugin的文件夾裏面就可以了。當你從新進入超級市場(啓動主程序)的時候就可以看到該店鋪了
3 關閉倒閉的店鋪:
怎麼樣來關閉店鋪呢? 很簡單,我們只需要把這個店鋪對應的dll文件從plugin裏面刪除就行,等你下次運行的時候,你將看不到該店鋪了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章