Ich habe gerade meinen Kopf um diese, und so werde ich ein Beispiel zu teilen, wie Sie bereits Beschreibungen, aber im Moment ein Vorteil sehe ich ist, um die Circular Reference Stil Warnungen, wo man nicht haben kann 2 Projekte verweisen einander zu umgehen.
Angenommen, eine Anwendung lädt eine XML-Datei herunter und speichert sie in einer Datenbank.
Ich habe hier 2 Projekte, die meine Lösung aufbauen: FTP und eine SaveDatabase.
Unsere Anwendung beginnt also mit der Suche nach Downloads und dem Herunterladen der Datei(en) und ruft dann das SaveDatabase-Projekt auf.
Nun muss unsere Anwendung die FTP-Site benachrichtigen, wenn eine Datei in der Datenbank gespeichert wird, indem sie eine Datei mit Metadaten hochlädt (ignorieren Sie, warum, es ist eine Anfrage vom Eigentümer der FTP-Site). Die Frage ist, zu welchem Zeitpunkt und wie? Wir brauchen eine neue Methode namens NotifyFtpComplete(), aber in welchem unserer Projekte soll sie gespeichert werden - FTP oder SaveDatabase? Logischerweise sollte der Code in unserem FTP-Projekt gespeichert werden. Das würde aber bedeuten, dass NotifyFtpComplete ausgelöst werden muss, oder dass es warten muss, bis die Speicherung abgeschlossen ist, und dann die Datenbank abfragen muss, um sicherzustellen, dass die Daten dort vorhanden sind. Was wir tun müssen, ist unserem SaveDatabase-Projekt zu sagen, dass es die Methode NotifyFtpComplete() direkt aufrufen soll, aber das können wir nicht; wir würden eine zirkuläre Referenz erhalten und NotifyFtpComplete() ist eine private Methode. Wie schade, das hätte funktioniert. Nun, es kann.
Im Code unserer Anwendung hätten wir Parameter zwischen den Methoden übergeben, aber was wäre, wenn einer dieser Parameter die Methode NotifyFtpComplete wäre? Ja, wir übergeben die Methode, und zwar mit dem gesamten Code darin. Das würde bedeuten, dass wir die Methode an jedem beliebigen Punkt und von jedem beliebigen Projekt aus ausführen könnten. Nun, genau das ist der Delegat. Das heißt, wir können die Methode NotifyFtpComplete() als Parameter an unsere Klasse SaveDatabase() übergeben. Zum Zeitpunkt des Speicherns wird dann einfach der Delegat ausgeführt.
Vielleicht hilft dieses grobe Beispiel (Pseudocode). Wir gehen auch davon aus, dass die Anwendung mit der Methode Begin() der FTP-Klasse beginnt.
class FTP
{
public void Begin()
{
string filePath = DownloadFileFromFtpAndReturnPathName();
SaveDatabase sd = new SaveDatabase();
sd.Begin(filePath, NotifyFtpComplete());
}
private void NotifyFtpComplete()
{
//Code to send file to FTP site
}
}
class SaveDatabase
{
private void Begin(string filePath, delegateType NotifyJobComplete())
{
SaveToTheDatabase(filePath);
/* InvokeTheDelegate -
* here we can execute the NotifyJobComplete
* method at our preferred moment in the application,
* despite the method being private and belonging
* to a different class.
*/
NotifyJobComplete.Invoke();
}
}
So, nachdem das erklärt ist, können wir es jetzt mit dieser Konsolenanwendung in C# in die Praxis umsetzen
using System;
namespace ConsoleApplication1
{
/* I've made this class private to demonstrate that
* the SaveToDatabase cannot have any knowledge of this Program class.
*/
class Program
{
static void Main(string[] args)
{
//Note, this NotifyDelegate type is defined in the SaveToDatabase project
NotifyDelegate nofityDelegate = new NotifyDelegate(NotifyIfComplete);
SaveToDatabase sd = new SaveToDatabase();
sd.Start(nofityDelegate);
Console.ReadKey();
}
/* this is the method which will be delegated -
* the only thing it has in common with the NofityDelegate
* is that it takes 0 parameters and that it returns void.
* However, it is these 2 which are essential.
* It is really important to notice that it writes
* a variable which, due to no constructor,
* has not yet been called (so _notice is not initialized yet).
*/
private static void NotifyIfComplete()
{
Console.WriteLine(_notice);
}
private static string _notice = "Notified";
}
public class SaveToDatabase
{
public void Start(NotifyDelegate nd)
{
/* I shouldn't write to the console from here,
* just for demonstration purposes
*/
Console.WriteLine("SaveToDatabase Complete");
Console.WriteLine(" ");
nd.Invoke();
}
}
public delegate void NotifyDelegate();
}
Ich schlage vor, dass Sie den Code Schritt für Schritt durchgehen und sehen, wann _notice aufgerufen wird und wann die Methode (Delegat) aufgerufen wird, da dies, so hoffe ich, die Dinge sehr klar machen wird.
Schließlich können wir es jedoch nützlicher machen, indem wir den Delegatentyp so ändern, dass er einen Parameter enthält.
using System.Text;
namespace ConsoleApplication1
{
/* I've made this class private to demonstrate that the SaveToDatabase
* cannot have any knowledge of this Program class.
*/
class Program
{
static void Main(string[] args)
{
SaveToDatabase sd = new SaveToDatabase();
/* Please note, that although NotifyIfComplete()
* takes a string parameter, we do not declare it,
* all we want to do is tell C# where the method is
* so it can be referenced later,
* we will pass the parameter later.
*/
var notifyDelegateWithMessage = new NotifyDelegateWithMessage(NotifyIfComplete);
sd.Start(notifyDelegateWithMessage );
Console.ReadKey();
}
private static void NotifyIfComplete(string message)
{
Console.WriteLine(message);
}
}
public class SaveToDatabase
{
public void Start(NotifyDelegateWithMessage nd)
{
/* To simulate a saving fail or success, I'm just going
* to check the current time (well, the seconds) and
* store the value as variable.
*/
string message = string.Empty;
if (DateTime.Now.Second > 30)
message = "Saved";
else
message = "Failed";
//It is at this point we pass the parameter to our method.
nd.Invoke(message);
}
}
public delegate void NotifyDelegateWithMessage(string message);
}