zt 對C#下函式,委託,事件的一點理解!

zhengnx發表於2011-05-22

對C#下函式,委託,事件的一點理解!

發表日期:2006-11-30

-
今天一來是有點空,二來是在部落格上偶然看到有關於委託的文章,一時興起,就自己也寫一點心得與大家分享一下。

先看一個例子:

using System;
namespace Consoleapplication1
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
bool m_isRight = false;
object m_obj = m_isRight?MyWrite("true"):MyWrite("false");
Console.Write(m_obj);
}
static PRivate int MyWrite(object i_string)
{
Console.Write(i_string);
return i_string.ToString().Length;
}
}
}

問輸出的結果是什麼?有一個剛學習程式設計不久的學生的回答是:false false

這個結果給我的映像很深,為什麼呢?因為我覺得這個不僅僅是學生的一個錯誤,而更多的是這個學生深入的思考了問題。

因為m_obj是一個物件,所以這個學生理解為:MyWrite()這個函式物件可以直接賦值給m_obj,然後m_obj就當成MyWrite()這個函式來呼叫,所以他就認為:


Console.Write (m_obj); 等於是:Console.Write (MyWrite(“false”));
這是思維是很有創意的,不是嗎?

於是就是C#裡而很多人不好理解的委託了。其實,從使用上講,它就是一個函式變數!如上面的例子,如果真的是想把MyWrite()做為物件賦值給m_obj會是個什麼結果呢?

我覺得我們先得解決以下幾個問題,才能正確的把函式當成變數賦值給一個物件:

1、如果可以給一個物件賦函式值,如何來區別不同的函式?

2、如何區別它是一個函式賦值,還是一個普通的物件賦值?

3、如何用這個物件來呼叫原來的函式?

如果把這幾個問題解決了,委託也就明白了一半。

先看問題1,如果可以給一個物件賦函式值,如何來區別不同的函式?

首先應該明白的是:C#裡是可以對一個物件賦函式值的。解決這個問題的辦法是先對該物件申明,申明它可以被什麼樣的函式來賦值,而這個物件申明在C#裡的學名就是委託。

(在C++裡稱為函式指標申明,相應的物件也就叫做函式指標。java裡也不同的叫法,可惜我不知道。)

而它的語法就是:

delegate [function declare];

這裡的function declare就包括了:

1、函式返回型別,

2、可用來存放函式的物件名(也就是委託名)

3、函式引數

所以完整的定義可以是:

delegate int MyDelegate(object I_object);

當然,合法的委託定義可以是:

delegate void MyDelegate();

delegate void MyDelegate(object I_1,object I_2);

現在,上面的語法就定義了一個抽象的物件MyDelegate, 注意,這裡說的是抽象的物件,也就是說,你不能直接給MyDelegate賦函式,而只能在它的例項上函式,這是C#裡特殊的要求。它的語法是:

MyDelegate m_delegate = new MyDelegate(與MyDelegate申明一致的函式名);

例如,以下是一個完全的,合法的委託申明與例項化一個物件:

delegate int MyDelegate(object i_object);
//
MyDelegate m_delegate = new MyDelegate(MyWrite);
//MyWrite函式如下,它是滿足委託的申明的。
static private int MyWrite(object i_string)
{
Console.Write(i_string);
return i_string.ToString().Length;
}
現在我們就很好的解決了第一個問題,如何定義一個物件,使該物件可以把函式當變數來賦給它。而且,可以區別不同的函式型別,主要是透過函式返回值與函式引數來共區別一類函式。

OK,第二個問題:如果有了這樣的一個物件後,如何來給它賦一個函式值呢?

其實上面的例項化一個委託物件時,就已經給它賦值了。上面的程式碼中,m_delegate就已經被賦值MyWrite,因此它已經具有了MyWrite函式的功能。

還有其實它的方法來給它賦值嗎?有,在委託的一個應用中,可以看到其它的賦值方法。也就是另一個不好理解的概念:事件!後面會提到。

我們再來看一下最後一個問題:如何透過一個已經賦值好了的委託物件,還呼叫它上面賦值了的函式。

這個最簡單了,當一個委託例項賦了函式物件在上面後,就可以像呼叫原函式一樣的來呼叫它了。因此,下面是一個會法的呼叫:基於上面的申明。

m_delegate(“This is a delegate object to call the raw function.”);

它就等同於:

MyWrite(“This is a delegate object to call the raw function.”);

因此,上面的呼叫與原函式呼叫一樣,會返回一個int結果。


OK,最後看一個完整的例子:

using System;
namespace ConsoleApplication1
{
class Class1
{
//先申明一個委託物件。
delegate int MyDelegate(object i_object);
[STAThread]
static void Main(string[] args)
{
MyDelegate m_delegate = new MyDelegate(MyWrite);
m_delegate("This is a delegate object to call the raw function.");
}
//該函式是滿足上面委託物件的申明的。
static private int MyWrite(object i_string)
{
Console.Write(i_string);
return i_string.ToString().Length;
}
}
}


再來討論一下它的應用:事件!

事件是其於委託的。我們還是先來看最開始的那個例子:

object m_obj = m_isRight?MyWrite("true"):MyWrite("false");

我想把一個函式物件賦值到m_obj上!但上面的委託只能在例項化物件的時候就直接給它賦值了。而現在是,在執行時對一個委託賦函式值。可以做到嗎?

同樣是有這樣的向個問題,當然,前提是我們已經知道有一種物件叫委託,它的例項可以賦函式物件。

下面的問題是:

1、如果可以在執行時給某個“特殊委託”賦函式物件,如何實現?

2、執行時,如何知道該“特殊委託”是否已經被賦過函式值?及如何再賦值?

3、如果可以,能否在一個“特殊委託”上新增多個函式?如果可以,如何刪除函式?

下面,我們就針對這幾個問題,來討論一下C#裡的事件,也就是上面的“特殊委託”。(其它語言裡是如何實現這些功能的,我就不清楚了。)

首先,C#裡是可以實現在執行時給一個委託動態的賦函式值的,同時也是可以動態的刪除已經新增在某個委託上的函式的,它的實現有一點點麻煩,就是要用到另一個物件:事件!event

(申明,你完全可以不把它叫事件,只不過這種動態的新增和刪除函式的功能在真實的程式設計中,基本上是與事件相關,所以就叫做事件了。個人想法,呵呵。)

OK,下面是C#語法,來申明一個“特殊委託”――事件,讓它可以動態的新增函式!

下文中,事件是指那些“特殊委託”,它的特殊之外,後面會講到。而下文中的委託就是前面講到的,一個特殊的物件,該物件可以把函式當“值”賦給它。

static event MyDelegate m_myevent;

(static 可以用其它的修飾符)

說明一下,這裡其實就是申明瞭一個事件,用event來說明它是事件(特殊委託)的。其實對比例項化一個委託的語法,你可以理解到,它就像是申明瞭一個委託,只不過個委託加了個event來說明它:

Mydelegate m_delegate;//申明一個委託

event MyDelegate m_myevent;//申明一個事件

很像吧!不是嗎?

OK,我們再來看,m_myevent 與m_delegate到底有什麼不同的?也就是,事件(特殊委託)到底特殊在什麼地方?

1、事件不是一個可以直接把函式當值一樣賦給它的委託。而委託可以直接賦函式,而且是在例項化的時候,賦函式名。

2、事件只能把一個例項的委託當值賦給它。也就是說:事件是用來管理委託的,進而來管理函式!因為一個例項化的委託一定有一個函式與之對應。

3、在事件上可以動態的新增與刪除委託。而委託上不能動態的新增刪除函式。

OK,下面的一個問題,上面事件的申明中,MyDelegate是起什麼作用的呢?

還記得前面的委託申明嗎?它就是說明了m_myevent在執行時可以動態的以委託的形式賦的函式要與MyDelegate申明的一樣!

因此上面的一個例項化是完全合法的。

再理解一下:事件,是用來動態管理委託的,而委託是單一的與一個函式對應的。

現在看第二個問題,執行時,如何知道該“特殊委託”是否已經被賦過函式值?及如何再賦值?

即:如何知道一個事件上已經賦過經過委託過的函式?

前面已經說過,m_myevent沒有給它賦值,如何給它賦值呢?它的賦值方法有點怪:

一個例項:

m_myevent += m_delegate;

有點怪吧!這裡正好說明了:事件是用來動態管理委託的。把一個委託加在事件上。

當然,你還可以在新增委託的時候直接new一個新的委託:

m_myevent +=new MyDelegate(Class1_m_myevent);//後面的函式名由vs2003生動生成

這就是.net下標準的給事件新增委託的方法,也就是給一個事件新增了一個可呼叫的函式。確切的說,是一個回撥函式(C++的概念)。

OK,下面就如何判斷一個事件上是否已經被賦過經過委託的函式:

if(m_myevent==null)

就可以知道了!

那麼如何知道一個事件上面有多少個委託呢?也就是多少個委託過的函式?

m_ myevent.GetInvocationList();

可以得到所有的委託!這裡應該知道:事件本身是一個物件,當例項化一個事件後,它是有自己的一些方法也成員的。可以查閱MSDN得到更多說明,同樣的委託也是一個物件,相關的說明也可以在MSDN裡找到。

最後的問題:如何刪除一個已經新增在事件上的委託?

太容易而且太有意思了:

m_myevent -= m_delegate;

那麼這樣的幾個問題又來了:

1.如果這個事件上沒有該委託,“減掉”以後會出錯嗎?不會,放心的減吧。

2.如何呼叫這個事件上的委託呢?上面有多個委託,它是怎樣執行呢?

呼叫事件上的委託與呼叫委託上的函式是完全一樣的:你要給出與委託申明一樣的函式引數,並且呼叫會返回與申明一樣的資料型別。

最後再回來看這個問題:

object m_obj = m_isRight?MyWrite("true"):MyWrite("false");

如何解決它呢?把一個函式賦給一個物件:一個例示(僅做演示解決這個問題,個人認為這樣的做法沒有實用意義)


using System;
namespace ConsoleApplication1
{
public class Class4
{
event System.EventHandler m_obj;
public Class4()
{
System.EventHandler m_f1 = new EventHandler(SomeFunc1);
System.EventHandler m_f2 = new EventHandler(SomeFunc2);
bool m_someCondition = false;
m_obj += m_someCondition?m_f1:m_f1;
m_obj(this,null);
}

private void SomeFunc1(object sender, EventArgs args)
{
}

private void SomeFunc2(object sender, EventArgs args)
{
}
}
}


最後看一個完整的例子:


using System;
namespace ConsoleApplication1
{
class Class1
{
//先申明一個委託物件。
delegate int MyDelegate(object i_object);
static event MyDelegate m_myevent;
[STAThread]
static void Main(string[] args)
{
MyDelegate m_delegate = new MyDelegate(MyWrite);
m_delegate("This is a delegate object to call the raw function.");
m_myevent += m_delegate;
m_myevent += new MyDelegate(MyWrite);
m_myevent +=new MyDelegate(Class1_m_myevent);
if(m_myevent!=null)
{
m_myevent("This is a event to call the funcaion on the delegate.");
}
}
//該函式是滿足上面委託物件的申明的。
static private int MyWrite(object i_string)
{
Console.WriteLine(i_string);
return i_string.ToString().Length;
}

private static int Class1_m_myevent(object i_object)
{
Console.WriteLine(i_object);
return 0;
}
}
}

我們再來看一個.net下標準的事件驅動模型的例子:


using System;

namespace ConsoleApplication1
{
public delegate void MyEventHandle(object i_sender,object i_arg);

public class Class2
{
public Class2()
{
}

[STAThread]
static void Main2(string[] args)
{
Class3 m_runOject = new Class3();
m_runOject.OnError += new MyEventHandle(m_runOject_OnError);
m_runOject.OnSomeThingHappened += new MyEventHandle(m_runOject_OnSomeThingHappened);
m_runOject.Run();
}

private static void m_runOject_OnError(object i_sender, object i_arg)
{
Console.WriteLine("Error in {0}, arg:{1}",i_sender,i_arg);
Console.WriteLine("Object {0} will stop running.",i_sender);
(i_sender as Class3).Stop();
}

private static void m_runOject_OnSomeThingHappened(object i_sender, object i_arg)
{
Console.WriteLine("Something happended in {0}, arg:{1}",i_sender,i_arg);
}
}


public class Class3
{
public bool m_isStop = false;
public event MyEventHandle OnSomeThingHappened;
public event MyEventHandle OnError;

public Class3()
{
}

public void Run()
{
Random m_rand = new Random();
int m_randomNum = m_rand.Next();
while(!m_isStop)
{
if(m_isStop){break;}
m_randomNum = m_rand.Next(100);
if(m_randomNum%5==0)
{
if(this.OnError!=null)
{
this.OnError(this,m_randomNum);
}
}
else
{
if(this.OnSomeThingHappened!=null)
{
this.OnSomeThingHappened(this,m_randomNum);
}
}
}
}

public void Stop()
{
m_isStop = true;
}
}
}


好了,全部完了!

最後再從另一個角度來理解:函式,委託和事件!

1、函式,是程式的基本單元,在.net下,有一個很重要的思想,就是:一切物件化!你可把一個int boxing後得到一個object,也可以把一個object unboxing後得到一個int. 可惜,就是沒有函式物件化這個概念!?

2、其實函式也可以物件化!就是透過delegate,委託就是把函式作為物件來處理,使它函式也具有了物件的一些特點。在例項化一個委託的時候,就是把一個函式”boxing”。因此,委託本質上就是一個類!它的初始化引數是一個函式名!不同的委託是不同的類,對應不同型別的函式。

3、事件是對委託的一個管理封裝,它可以很好的動態管理委託,從而完成很多有實用價值的事情,最主要的就是事件!

完全是個人理解,有不同意見,歡迎討論!

-

本文來自: 動態網站製作() 詳細出處參考:

[@more@]

如何利用C#建立和呼叫DLL

發表日期:2003-12-30

-
一、寫在前面
C# 語言是一種簡單但功能強大的程式語言,用於編寫企業應用程式。

C# 語言從C和 C++語言演化而來,在語句、表示式和運算子方面使用了許多 C++ 功能。

C# 語言在型別安全性、版本轉換、事件和垃圾回收等方面進行了相當大的改進和創新。

C# 語言提供對常用 API 樣式(如 .NET 框架、COM、自動化和 C 樣式 API 等)的訪問。

什麼是動態連結庫?DLL三個字母對於你來說一定很熟悉吧,它是Dynamic Link Library 的縮寫形式,動態連結庫 (DLL) 是作為共享函式庫的可執行檔案。動態連結提供了一種方法,使程式可以呼叫不屬於其可執行程式碼的函式。函式的可執行程式碼位於一個 DLL 中,該 DLL 包含一個或多個已被編譯、連結並與使用它們的程式分開儲存的函式。DLL 還有助於共享資料和資源。多個應用程式可同時訪問記憶體中單個 DLL 副本的內容。

和大多數程式設計師一樣,你一定很使用過DLL吧。也曾感受到它的帶給你程式設計和編碼上的好錯吧今天我想和大家探討一個主題:如何在C#建立和呼叫DLL(動態連結庫), 其實在很大意義上而講,DLL讓我更靈活的組織編寫我們的應用程式,作為軟體設計者,可一個根據它來達到很高的程式碼重用效果。下面我來介紹一下在C#中如何建立和呼叫DLL。

二、準備工作

我們需要對我們接下來要做的事情做個簡單的介紹,在本文我們將利用C#語言建立一個名為 MyDLL.DLL的動態連結庫,在這個動態連結庫檔案中我們將提供兩個功能一個是對兩個引數交換他們的值,另一個功能是求兩個引數的最大公約數。然後建立一個應用程式使用這個DLL。執行並輸出結果。

三、建立DLL

讓我們建立以下三個C#程式碼檔案:

1、 MySwap.cs

using System;

namespace MyMethods

{

public class SwapClass

{

public static bool Swap(ref long i,ref long j)

{

i = i+j;

j = i-j;

i = i-j;

return true;

}

}

}

2、 MyMaxCD.cs

using System;

namespace MyMethods

{

public class MaxCDClass

{

public static long MaxCD(long i, long j)

{

long a,b,temp;

if(i>j)

{

a = i;

b = j;

}

else

{

b = i;

a = j;

}

temp = a % b;

while(temp!=0)

{

a = b;

b = temp;

temp = a % b;

}

return b;

}

}

}

}需要注意的是:我們在製作這兩個檔案的時候可以用Visual Studio.NET或者其他的文字編輯器,就算是記事本也可以。這兩個檔案雖然不在同一個檔案裡面,但是他們是屬於同一個namespace(名稱空間)這對以後我們使用這兩個方法提供了方便。當然他們也可以屬於不同的名稱空間,這是完全可以的,但只是在我們應用他們的時候就需要引用兩個不同的名稱空間,所以作者建議還是寫在一個名稱空間下面比較好。
接下來的任務是把這兩個cs檔案變成我們需要的DLL檔案。方法是這樣的:
在安裝了Microsoft.NET Framework的作業系統上,我們可以在Windows所在目錄下找到Microsoft.NET目錄。在這個目錄下面提供了C#的編譯器,CSC.EXE
執行:csc /target:library /out:MyDLL.DLL MySwap.cs MyMaxCD.cs
完成後可在本目錄下面找到我們剛才生成的MyDLL.DLL檔案
/target:library 編譯器選項通知編譯器輸出 DLL 檔案而不是 EXE 檔案。後跟檔名的 /out 編譯器選項用於指定 DLL 檔名。
如果/out後面不跟檔名編譯器使用第一個檔案 (MySwap.cs) 作為 DLL 檔名。生成的檔案為MySwap.DLL檔案
OK!我們建立動態連結庫檔案的任務完成了,現在是我們享受勞動成果的時候了,下面我將介紹如何使用我們所建立的動態連結庫檔案。

四、 使用DLL

我們簡單寫一個小程式來測試一下我們剛才寫的兩個方法是否正確,好吧,跟我來:
MyClient.cs
using System;

using MyMethods;

//這裡我們引用剛才定義的名稱空間,如果剛才的兩個檔案我們寫在兩個不同的名稱空間
class MyClient

{

public static void Main(string[] args)

{

if (args.Length != 2)

{

Console.WriteLine("Usage: MyClient ");

return;

}

long num1 = long.Parse(args[0]);

long num2 = long.Parse(args[1]);

SwapClass.Swap(ref num1,ref num2);

// 請注意,檔案開頭的 using 指令使您得以在編譯時使用未限定的類名來引用 DLL 方法

Console.WriteLine("The result of swap is num1 = {0} and num2 ={1}",num1, num2);

long maxcd = MaxCDClass.MaxCD(num1,num2);

Console.WriteLine("The MaxCD of {0} and {1} is {2}",num1, num2, maxcd);

}

}

若要生成可執行檔案 MyClient.exe,請使用以下命令列:

csc /out:MyClient.exe /reference:MyLibrary.DLL MyClient.cs

/out 編譯器選項通知編譯器輸出 EXE 檔案並且指定輸出檔名 (MyClient.exe)。/reference 編譯器選項指定該程式所引用的 DLL 檔案。

五、執行

若要執行程式,請輸入 EXE 檔案的名稱,檔名的後面跟兩個數字,例如:

MyClient 123 456

六、輸出

The result of swap is num1 = 456 and num2 = 123

The MaxCD of 456 and 123 is 3

七、小結

動態連結具有下列優點:

節省記憶體和減少交換操作。很多程式可以同時使用一個 DLL,在記憶體中共享該 DLL 的一個副本。相反,對於每個用靜態連結庫生成的應用程式,Windows 必須在記憶體中載入庫程式碼的一個副本。
節省磁碟空間。許多應用程式可在磁碟上共享 DLL 的一個副本。相反,每個用靜態連結庫生成的應用程式均具有作為單獨的副本連結到其可執行影像中的庫程式碼。
升級到 DLL 更為容易。DLL 中的函式更改時,只要函式的引數和返回值沒有更改,就不需重新編譯或重新連結使用它們的應用程式。相反,靜態連結的物件程式碼要求在函式更改時重新連結應用程式。
提供售後支援。例如,可修改顯示器驅動程式 DLL 以支援當初交付應用程式時不可用的顯示器。
支援多語言程式。只要程式遵循函式的呼叫約定,用不同程式語言編寫的程式就可以呼叫相同的 DLL 函式。程式與 DLL 函式在下列方面必須是相容的:函式期望其引數被推送到堆疊上的順序,是函式還是應用程式負責清理堆疊,以及暫存器中是否傳遞了任何引數。
提供了擴充套件 MFC 庫類的機制。可以從現有 MFC 類派生類,並將它們放到 MFC 擴充套件 DLL 中供 MFC 應用程式使用。
使國際版本的建立輕鬆完成。透過將資源放到 DLL 中,建立應用程式的國際版本變得容易得多。可將用於應用程式的每個語言版本的字串放到單獨的 DLL 資原始檔中,並使不同的語言版本載入合適的資源。
使用 DLL 的一個潛在缺點是應用程式不是獨立的;它取決於是否存在單獨的 DLL 模組。

本文來自: 動態網站製作() 詳細出處參考:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7868752/viewspace-1050212/,如需轉載,請註明出處,否則將追究法律責任。

相關文章