簡體   English   中英

如何以正確的方式使用委托/了解委托

[英]How to use delegates in correct way / Understanding delegates

使用 - C#(。Net Framework 4.5,Visual Studio 2012)

我試着理解像代表這樣的主題,目前我有幾點,必須為我澄清。 我在互聯網上發現了很多不同的信息來描述如何使用它,但是對我來說理解這個主題有點復雜。

據我所知,我必須做一些使用委托的事情:

  • 創建一些實體用於它(需要創建一些委托)
  • 聲明委托類型
  • 創建一些我調用委托的方法
  • 在主類調用委托與使用實體的必需方法(從第一點)

所有描述如下所示

理解分流

問題 - 我是否正確理解了所有或者我錯了 - 請澄清一下。

另外一個關於DELEGATE的問題 - 哪里更好地將代碼放在DELEGATE中 - 在Console C#應用程序中我可以在任何使用過的Namespace的地方創建它 - 我可以在下面看到。

放置dalagete

但也許有一些建議/要求不僅為控制台應用程序而且為WinForms,WPF等放置委托。

這個主題對我來說是新的,我花了一天時間來理解它,但仍然有點(或更多)與此混淆,最后創建這篇文章以獲得更好和清晰的理解。 認為這是非常強大的東西。

編輯

namespace SimpleCSharpApp
{
   delegate void myDelagate ();
}

Ho-ho ..你有些搞砸了。 直到我看到VS的屏幕截圖,並在“委托”聲明下帶有紅色下划線,我還沒有完全掌握你想要說明的問題。

首先, public void delegate zczcxxzc忘掉public void delegate zczcxxzc行。 這有點特別。 首先,讓我們看一些代表的標准種類*)。

最基本的兩個是:

  • System.Action
  • System.Func

兩者都是通用的,並且第一次看到它們的簽名,它們看起來可能過於復雜。 但是,它們真的非常簡單。

首先,讓我們限制裸,無參數, System.Action

private static void myFunction1() { Console.WriteLine("Hello!"); }
private static void myFunction2() { Console.WriteLine("Bye!"); }
private static void myFunction0() { return; }

... // in some function, i.e. Main()
Action myDelegate = null;

myDelegate = new Action( myFunction1 );
myDelegate(); // writes "Hello!"

myDelegate = new Action( myFunction2 );
myDelegate(); // writes "Bye!"

myDelegate = new Action( myFunction3 );
myDelegate(); // does "nothing"

就像“int”包含一個數字,“string” - 文本,“委托”包含有關“可調用的東西”的信息,或者,使用某些術語,“可調用的東西”。

首先,我創建了一個記住“myFunction1”的“Action”類型的委托 然后我調用/調用該委托 - 它導致被記住的函數被調用。

然后,我創建一個類型為“Action” 的委托 ,記住“myFunction2”。 然后我調用/調用該委托 - 它導致被記住的函數被調用。

最后,我創建一個類型為“Action” 的委托 ,它記住了“myFunction3”。 然后我調用/調用該委托 - 它導致被記住的函數被調用,沒有任何反應 - 但只是因為目標函數什么也沒做。

請注意,我故意說“創建了一個代表”。 每次執行new Action ,都會創建一個新委托。 “委托”只是一個對象,如String“foo”或float [] {1.2,4.5}。

另請注意,此處使用的創建委托的完整語法是new Action(...) 就像創建任何對象一樣 - 新的+ typename +構造參數。 另一個標志,“代表”只是一個對象。

另外需要注意的是我沒有編寫new Action( myFunction1() ) 我不想調用該方法並獲取其結果並將該結果提供給Action的構造函數。 我寫了new Action( myFunction1 ) 我把函數本身給了構造函數。

那么,什么是“行動”呢? System.Action是一個類。 像String,或Socket或WebClient。 這里沒什么特別的。 所以,我們有一個“類”,它的對象可以記住應該被調用的函數。 涼。

因此,有些人將代表與“函數指針”進行比較。 但那並不完全正確。 函數指針可以記住要調用的函數 代表們可以記住要調用的方法 記得區別嗎? 在上面的例子中,我故意在每個myFunctionstatic 這些可以被稱為無對象/無目標。 你需要他們的名字,你可以從任何地方打電話給他們。 要打電話給他們,一個簡單的啞指針就足夠了。

現在,代表們可以做得更多。 他們可以研究方法 但是需要針對對象調用方法。

class GuineaPig
{
    public static void Squeak() { Console.WriteLine("Ieek!"); }

    public void Well() { Console.WriteLine("actually"); }
    public void IDontKnow() { Console.WriteLine("what they do"); }
}

GuineaPig.Squeak(); // says 'ieek'

Action myDelegate = null;
myDelegate = new Action( GuineaPig.Squeak );
myDelegate(); // writes "ieek"

// GuineaPig.Well(); // cannot do!
// myDelegate = new Action( GuineaPig.Well ); // cannot do!

好吧,在其他類中創建一個靜態函數的委托很容易 - 只需要確切地說什么是函數來自什么類。 再次就像打電話一樣,但沒有括號。

但是,如果您嘗試取消注釋對非靜態方法的引用,它將無法編譯。 看看GuineaPig.Well - 這很明顯。 它不是靜態的,需要針對OBJECT而不是CLASS進行調用。 出於同樣的原因,無法創建委托。 我們來解決這個問題:

class GuineaPig
{
    public void Well() { Console.WriteLine("actually"); }
    public void IDontKnow() { Console.WriteLine("what they do"); }
}

GuineaPig myPiggie = new GuineaPig();

myPiggie.Well(); // ok! writes "actually"

Action myDelegate = null;
myDelegate = new Action( myPiggie.Well ); // ok!

myDelegate(); // ok! writes "actually".

請注意在創建委托期間如何將classname替換為objectvariable。 語法被保留:就像調用一樣,但沒有parens。 但是,關於“方法”與“功能”的所有大驚小怪......

代理人不僅可以存儲要調用的“方法”,還可以存儲調用它們的對象

class GuineaPig
{
    public string Name;

    public void Well() { Console.WriteLine("I'm " + Name); }
}

GuineaPig myPiggie1 = new GuineaPig { Name = "Bubba" };
GuineaPig myPiggie2 = new GuineaPig { Name = "Lassie" };

Action myDelegate = null;

myDelegate = new Action( myPiggie1.Well );
myDelegate(); // -> Bubba

myDelegate = new Action( myPiggie2.Well );
myDelegate(); // -> Lassie

myPiggie1 = myPiggie2 = null;

myDelegate(); // -> Lassie

現在,這是普通函數指針無法做到的。 (盡管你可以使用非常智能的函數指針......但是,讓我們離開它)。

注意如何在“pig#2”上調用“Well”的事實存儲在委托對象中。 “myPiggie2”變量無關緊要。 我可以使它無效。 代表記住了目標和方法。

System.Action只是其中之一。 它是最簡單的,沒有參數,沒有返回..但是它們中有很多,它們可以獲取參數( Action<string, int> )它們可以返回值( Func<int> )或兩者( Func<string,int> ) 。 然而,不斷說Func<int,float,string,int,int,bool,decimal>有些......模糊不清。

好。 讓我們終於明白了這一切的嘮叨。 對不起,如果你知道所有這些,但我想要清楚。

“代表”就是要記住“目標”和“方法”。 實際上,如果你在調試器中檢查一個委托(或檢查Intellisense在“點”之后說的內容),你會看到兩個屬性,Target和Method。 他們正是他們的名字所代表的。

讓我們假設您想要創建自己的委托類型。 一種不會被稱為Func<int,int,int,bool,bool,Zonk,string> ,而是“MyStudentFilteringDelegate”。

現在,重點是,在C#中你不能輕易地獲取函數的& (地址) ,也不能重載operator() 這導致您無法編寫自己的委托類。

你不能只寫:

class MyStudentFilteringDelegate
{
     public object Target;
     public somethingstrange*  MethodPointer;

     // other code
}

因為,即使你真的設法遵循這個想法,最后在某個地方你會發現:

MyDelegate dd = new MyDelegate ( ... );
dd(); // is just impossible!!!

至少,在當前的C#版本4.5或5中。

你不能重載“call”/“invoke”操作符,因此你將無法完全實現自己的,自定義命名的委托類型。 你永遠都會被動作和Funcs困住。

現在回想起public void delegate xxx下的紅色下划線我請你暫時忘記。

public bool delegate MyStudentFilteringDelegate( Student stud );

此行不會創建任何委托 該行定義了委托類型 它與Func<Student,bool>完全相同,帶有自定義名稱。 *)

實際上,編譯器將行轉換為:

public class MyStudentFilteringDelegate : some_framework_Delegate_class
{
    // object Target {get;} - inherited from base class
    // MethodInfo Method {get;} - inherited from base class, too
}

所以它是一個 ,所以你現在可以創建一個委托對象:

var dd = new MyStudentFilteringDelegate ( ... ); // like normal class!
dd(); // ok!;

由於類是特殊的,編譯器生成的,它可以破壞規則。 它的'call'/'invoke'運算符被重載,所以你可以“調用”委托,因為它是一個方法。

請注意,盡管有一些奇怪的表示法:

public bool delegate MyStudentFilteringDelegate( Student stud );

MyStudentFilteringDelegate是一個 ,就像Action或Func或String或WebClient一樣。 delegate關鍵字只是編譯器的標記,以便知道它應該應用於該行的轉換以生成適當的“委托類型”(類)。

現在,要真正回答你的另一個問題:

在您放置委托類型聲明的地方真的無關緊要。 你可以在任何你喜歡的地方寫public void delegate XYZ(...) 就像你可以在任何地方放置一個類聲明一樣。

您可以將類聲明放在默認(無命名空間)范圍,某個命名空間或類中。 因此,由於委托類型只是一個類,您還可以在默認(無命名空間)范圍,某個命名空間或類內部處理新的委托類型:

public class Xc {}
public void delegate Xd();

namespace WTF {
    public class Xc {}
    public void delegate Xd();

    class Whatever {
        public class Xc {}
        public void delegate Xd();
    }
}

請注意,我完全故意將它們命名為相同。 那不是錯誤。 首先命名為::global.Xc::global.Xd ,第二對命名為WTF.XcWTF.Xd ,最后一對命名為WTF.Whatever.XcWTF.Whatever.Xd 就像普通的clases一樣。

要確定放置這些聲明的位置,請使用與您用於類的規則相同的規則。 IE瀏覽器。 如果您將文本處理類放在命名空間MyApp.Text.Parsing ,那么與該文本處理相關的所有委托類型也應該位於該命名空間中。 但是,即便如此,這純粹是化妝和組織的。 在適合您的任何范圍內放置/定義它們。

編輯:*)實際上,從歷史上看,它是完全相反的。 delegate關鍵字和編譯器技巧比Action和Func類 在.Net 2.0中,Action / Func不存在。 創建/使用委托的唯一方法是定義自己的新委托類型(或在系統的名稱空間深處找到/猜測一些合適的委托類型)。 請記住,每個新的委托類型都是一個新類。 不能轉換為任何其他類,甚至不能同樣看起來。 令人沮喪的是令人沮喪且難以保持,在.Net 3.5中,他們最終在框架中包含了“通用通用委托類型”。 從那時起,Action / Func越來越常被使用,因為即使它們更難閱讀,它們也是通用的。 System.Func<Student,bool>可以“隨處”傳遞,並且你沒有問題, from one library does not match 'bool委托StudentFilter() from one library does not match的bool委托StudentSelector()` from one library does not match

c#中的委托有點像C ++中函數指針的替代品。 它們有很多用途。 您可以使用它們:

  1. 為事件創建內聯實現。
  2. 將回調傳遞給函數。
  3. 創建一個可以循環調用的“函數”數組。

根據我的經驗,第一次使用是最常見的。

Button.Click += delegate(object sender, EventArgs args)  => {/*you do something here*/};

lamba表達式可以簡化:

Button.Click += (sender, args) => {/*you do something here*/};

您為按鈕單擊提供了一些行為,而無需為其創建單獨的方法。

關於問題的第二部分,我通常將代理聲明放在單獨的文件中。

委托聲明可以放在具有公共和內部可訪問性的任何源文件中。 我個人將它們放在最適用的類源的頂部。

例如,如果我有一個專門的事件委托,它將CustomEventArgs作為參數,我將把委托聲明放在該文件的頂部:

namespace MyNamespace {
   public delegate void SpecialEventDelegate(object sender, CustomEventArgs e);

   public class CustomEventArgs : EventArgs {
      // implementation details
   }
}

另一個選擇是將代理人放在一個單獨的源文件中......我沒有看到這樣做,並且會受到編碼指南的約束。

代表一般有兩個資格:

  • 方法或類成員的聲明
  • 對方法或類成員的引用

第一個資格是代表的聲明:

   public delegate void SpecialEventDelegate(object sender, CustomEventArgs e);

...或使用Func<TResult>Action

   Action<object, CustomEventArgs> // similar declaration to the SpecialEventDelegate above

由於事件處理程序(聲明的委托)通常沒有返回類型( void ),因此不會使用Func<TResult> Func委托需要返回類型:

   Func<bool> // a delegate that return true/false

有關Func<TResult>Action更多信息,請參閱鏈接的MSDN文章。 為了完整性和對新的聲明方法的見解,我只提供了對這些的引用。 Func<TResult>Actiondelegate專用包裝器。

委托的第二個資格是對方法或類成員的引用。 我可能有一個私有方法,作為特定需求的處理程序。 假設FileIO對象需要針對不同類型文件的特定文件處理程序 - 即.XML,.TXT,.CSV:

namespace MyNamespace {

   public delegate Stream OpenFile(FileInfo FileSpec);

}

現在,任何對象都可以根據文件類型實現自己的OpenFile定義,但必須返回Stream對象。

class XMLHandler : IFileHandler {

   private OpenFile xmlFileReader;

   // implementation of interface
   public OpenFile FileHandler {
      get { return xmlFileReader; }
   }

   public XMLHandler(){
      xmlFileReader = MyXmlFileReader;  // references the private method in this class
   }

   private Stream MyXmlFileReader(FileInfo XmlFileSpec) {
      // implementation specific to this class
   }

}

interface IFileHandler {

   OpenFile FileHandler { get; }

}

通過使用委托聲明和接口,我們可以將XMLHandler對象作為IFileHandler傳遞,並且只通過FileHandler屬性公開委托,而不暴露整個對象。 請注意,委托引用的方法是私有的。 這是委托(和Func,Action)的特殊好處。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM