Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
A .NET-események általában néhány ismert mintát követnek. Ezeknek a mintáknak a szabványosítása azt jelenti, hogy a fejlesztők alkalmazhatják ezeket a standard mintákat, amelyek bármely .NET-eseményprogramra alkalmazhatók.
Tekintsük át ezeket a standard mintákat, hogy minden tudása birtokában legyen a standard eseményforrások létrehozásához, valamint a standard események feliratkozásához és feldolgozásához a kódban.
Eseménymeghatalmazási aláírások
A .NET-eseménydelegáltak szabványos aláírása a következő:
void EventRaised(object sender, EventArgs args);
Ez a szabványos aláírás betekintést nyújt az események használatába:
- A visszatérési típus érvénytelen. Egy eseménynek nulla vagy több hallgatója lehet. Az esemény felemelése értesíti az összes figyelőt. A figyelők általában nem adnak meg értékeket az eseményekre adott válaszként.
-
Események azt jelzik, hogy a feladó: Az esemény aláírása tartalmazza az eseményt generáló objektumot. Ez minden figyelő számára biztosít egy mechanizmust a küldővel való kommunikációhoz. A fordítási idő típusa
sender
annak ellenére,System.Object
hogy valószínűleg ismer egy olyan származtatott típust, amely mindig helyes lenne. Konvenció szerint használjaobject
. -
Az események több információt csomagolnak egyetlen struktúrába: A
args
paraméter egy System.EventArgs-ből származó típus, amely minden további szükséges információt tartalmaz. (A következő szakaszban láthatja,, hogy ez a konvenció már nincs kényszerítve.) Ha az eseménytípusnak nincs szüksége további argumentumokra, mindkét argumentumot meg kell adnia. Van egy speciális érték, EventArgs.Empty, amely azt jelzi, hogy az esemény nem tartalmaz további információkat.
Hozzunk létre egy osztályt, amely egy könyvtárban lévő fájlokat vagy annak bármely alkönyvtárát listázza, amely egy mintát követ. Ez az összetevő minden talált fájlhoz létrehoz egy eseményt, amely megfelel a mintának.
Az eseménymodellek használata néhány tervezési előnyt biztosít. Több eseményfigyelőt is létrehozhat, amelyek különböző műveleteket hajtanak végre egy keresett fájl megtalálásakor. A különböző figyelők kombinálásával robusztusabb algoritmusokat hozhat létre.
A keresett fájl megkeresésének kezdeti eseményargumentum-deklarációja:
public class FileFoundArgs : EventArgs
{
public string FoundFile { get; }
public FileFoundArgs(string fileName) => FoundFile = fileName;
}
Annak ellenére, hogy ez a típus kicsi, csak adattípusnak tűnik, kövesse az egyezményt, és legyen hivatkozási (class
) típus. Ez azt jelenti, hogy az argumentumobjektumot hivatkozással továbbítja a rendszer, és az adatok frissítéseit az összes előfizető megtekinti. Az első verzió egy nem módosítható objektum. Az eseményargumentum típusában szereplő tulajdonságokat lehetőleg tegyék nem módosíthatóvá. Így az egyik előfizető nem módosíthatja az értékeket, mielőtt egy másik előfizető megtekintené őket. (A gyakorlat alól kivételek vannak, ahogy később látni fogjuk.)
Ezután létre kell hoznunk az eseménydeklarációt a FileSearcher osztályban. A System.EventHandler<TEventArgs> típus használata azt jelenti, hogy nem kell még egy típusdefiníciót létrehoznia. Csak általános specializációt használ.
Töltsük ki a FileSearcher osztályt egy mintának megfelelő fájlok kereséséhez, és hozzuk létre a megfelelő eseményt egyezés észlelésekor.
public class FileSearcher
{
public event EventHandler<FileFoundArgs>? FileFound;
public void Search(string directory, string searchPattern)
{
foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
{
FileFound?.Invoke(this, new FileFoundArgs(file));
}
}
}
Mezőszerű események definiálása és emelése
Az eseménynek az osztályhoz való hozzáadásának legegyszerűbb módja, ha az eseményt nyilvános mezőként deklarálja, ahogy az előző példában is látható:
public event EventHandler<FileFoundArgs>? FileFound;
Ez úgy tűnik, hogy egy nyilvános mezőt deklarál, ami rossz objektumorientált gyakorlatnak tűnik. Az adathozzáférést tulajdonságokon vagy metódusokon keresztül szeretné védeni. Bár ez a kód rossz gyakorlatnak tűnhet, a fordító által létrehozott kód burkolókat hoz létre, így az eseményobjektumok csak biztonságos módon érhetők el. Egy mezőszerű eseményen csak a kiegészítő és eltávolító műveletek kezelése érhető el.
var fileLister = new FileSearcher();
int filesFound = 0;
EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs) =>
{
Console.WriteLine(eventArgs.FoundFile);
filesFound++;
};
fileLister.FileFound += onFileFound;
fileLister.FileFound -= onFileFound;
A kezelőnek van egy helyi változója. Ha a lambda törzsét használná, a remove
kezelő nem működne megfelelően. Ez a meghatalmazott egy másik példánya lenne, és csendben semmit sem tenne.
Az osztályon kívüli kód nem tudja elindítani az eseményt, és más műveleteket sem tud végrehajtani.
A C# 14-től kezdődően az események részleges tagokként deklarálhatók. A részleges eseménydeklarációnak tartalmaznia kell egy meghatározó deklarációt és egy végrehajtási deklarációt. A definiáló deklarációnak mezőszerű eseményszintaxist kell használnia. A végrehajtási nyilatkozatnak deklarálnia kell a add
és remove
kezelőket.
Esemény-előfizetők értékeinek visszaadása
Az egyszerű verzió jól működik. Adjunk hozzá egy másik funkciót: a lemondást.
A Talált esemény létrehozásakor a figyelőknek le kell tudniuk állítani a további feldolgozást, ha ez a fájl az utolsó keresett.
Az eseménykezelők nem adnak vissza értéket, ezért ezt más módon kell kommunikálnia. A standard eseményminta az EventArgs
objektummal olyan mezőket tartalmaz, amelyeket az esemény-előfizetők a megszakítások kommunikációja során használhatnak.
A Visszavonás szerződés szemantikája alapján két különböző minta használható. Mindkét esetben logikai mezőt ad hozzá a talált fájlesemény EventArguments függvényéhez.
Egy minta lehetővé teszi, hogy bármely előfizető megszakítsa a műveletet. Ebben a mintában az új mező inicializálva lesz false
. Bármely előfizető módosíthatja azt true
. Az esemény minden előfizető számára történő növelése után a FileSearcher összetevő megvizsgálja a logikai értéket, és végrehajtja a műveletet.
A második minta csak akkor szakítaná meg a műveletet, ha az összes előfizető le szeretné mondani a műveletet. Ebben a mintában az új mező inicializálva van, hogy jelezze a művelet megszakítását, és bármely előfizető módosíthatja, hogy a művelet folytatódjon. Miután az összes előfizető feldolgozta az eseményt, a FileSearcher összetevő megvizsgálja a logikai változót, és megteszi a szükséges lépéseket. Ebben a mintában van egy további lépés: az összetevőnek tudnia kell, hogy az előfizetők válaszoltak-e az eseményre. Ha nincsenek előfizetők, a mező helytelen lemondást jelez.
Implementáljuk a minta első verzióját. A(z) CancelRequested
típushoz egy FileFoundArgs
nevű logikai mezőt kell hozzáadnia.
public class FileFoundArgs : EventArgs
{
public string FoundFile { get; }
public bool CancelRequested { get; set; }
public FileFoundArgs(string fileName) => FoundFile = fileName;
}
Ez az új mező automatikusan inicializálva lesz a false
értékre, hogy ne törölje véletlenül. Az összetevő másik módosítása az, hogy az esemény bekövetkezte után ellenőrizze a jelzőt, kérte-e bármelyik előfizető a lemondást.
private void SearchDirectory(string directory, string searchPattern)
{
foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
{
var args = new FileFoundArgs(file);
FileFound?.Invoke(this, args);
if (args.CancelRequested)
break;
}
}
Ennek a mintának az egyik előnye, hogy ez nem törés változás. Egyik előfizető sem kérte korábban a lemondást, és még mindig nem. Az előfizetői kódok egyike sem igényel frissítéseket, hacsak nem támogatják az új megszakítási protokollt.
Frissítsük az előfizetőt úgy, hogy az az első végrehajtható fájl megkeresése után kérje a lemondást:
EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs) =>
{
Console.WriteLine(eventArgs.FoundFile);
eventArgs.CancelRequested = true;
};
Másik eseménydeklaráció hozzáadása
Adjunk hozzá még egy funkciót, és mutassuk be az események egyéb nyelvi kifejezéseit. Adjunk hozzá egy túlterhelést a Search
metódushoz, amely minden alkönyvtárat bejár a fájlok keresése során.
Ez a módszer hosszú művelet lehet egy sok alkönyvtárat tartalmazó könyvtárban. Vegyünk fel egy eseményt, amely az egyes új címtárkeresések megkezdésekor merül fel. Ez az esemény lehetővé teszi az előfizetők számára, hogy nyomon kövessék az előrehaladást, és frissítsék a felhasználót a haladásról. Az eddig létrehozott minták nyilvánosak. Tegyük ezt az eseményt belső eseménysé. Ez azt jelenti, hogy az argumentumtípusokat belsővé is teheti.
Először hozza létre az új EventArgs származtatott osztályt az új címtár és a folyamat jelentéséhez.
internal class SearchDirectoryArgs : EventArgs
{
internal string CurrentSearchDirectory { get; }
internal int TotalDirs { get; }
internal int CompletedDirs { get; }
internal SearchDirectoryArgs(string dir, int totalDirs, int completedDirs)
{
CurrentSearchDirectory = dir;
TotalDirs = totalDirs;
CompletedDirs = completedDirs;
}
}
Ismét követheti a javaslatokat az eseményargumentumok megváltoztathatatlan hivatkozástípusának létrehozásához.
Ezután adja meg az eseményt. Ezúttal egy másik szintaxist használ. A mezőszintaxis használata mellett explicit módon is létrehozhatja az eseménytulajdonságot kezelők hozzáadásával és eltávolításával. Ebben a mintában nincs szükség további kódra ezekben a kezelőkben, de ez bemutatja, hogyan hozhatná létre őket.
internal event EventHandler<SearchDirectoryArgs> DirectoryChanged
{
add { _directoryChanged += value; }
remove { _directoryChanged -= value; }
}
private EventHandler<SearchDirectoryArgs>? _directoryChanged;
Az itt írt kód sokféleképpen tükrözi a fordító által a korábban látott mezőesemény-definíciókhoz létrehozott kódot. Az eseményt tulajdonságokhoz hasonló szintaxissal hozza létre. Figyelje meg, hogy a kezelőknek különböző neveik vannak: add
és remove
. Ezeket a hozzáféréskezelőket az eseményre való feliratkozásra vagy az eseményről való leiratkozásra hívjuk meg. Figyelje meg, hogy az eseményváltozó tárolásához egy privát háttérmezőt is deklarálnia kell. Ez a változó null értékűre van inicializálva.
Ezután adjuk hozzá az alkönyvtárakat bejáró és mindkét eseményt kiváltó metódus túlterhelését Search
. A legegyszerűbben egy alapértelmezett argumentum használatával adhatja meg, hogy az összes könyvtárban szeretne keresni:
public void Search(string directory, string searchPattern, bool searchSubDirs = false)
{
if (searchSubDirs)
{
var allDirectories = Directory.GetDirectories(directory, "*.*", SearchOption.AllDirectories);
var completedDirs = 0;
var totalDirs = allDirectories.Length + 1;
foreach (var dir in allDirectories)
{
_directoryChanged?.Invoke(this, new (dir, totalDirs, completedDirs++));
// Search 'dir' and its subdirectories for files that match the search pattern:
SearchDirectory(dir, searchPattern);
}
// Include the Current Directory:
_directoryChanged?.Invoke(this, new (directory, totalDirs, completedDirs++));
SearchDirectory(directory, searchPattern);
}
else
{
SearchDirectory(directory, searchPattern);
}
}
private void SearchDirectory(string directory, string searchPattern)
{
foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
{
var args = new FileFoundArgs(file);
FileFound?.Invoke(this, args);
if (args.CancelRequested)
break;
}
}
Ezen a ponton futtathatja az alkalmazást, amely meghívja a túlterhelést az összes alkönyvtár kereséséhez. Az új DirectoryChanged
eseménynek nincsenek előfizetői, de a ?.Invoke()
kifejezés használata biztosítja, hogy megfelelően működjön.
Adjunk hozzá egy kezelőt egy olyan sor írásához, amely a konzolablak előrehaladását mutatja.
fileLister.DirectoryChanged += (sender, eventArgs) =>
{
Console.Write($"Entering '{eventArgs.CurrentSearchDirectory}'.");
Console.WriteLine($" {eventArgs.CompletedDirs} of {eventArgs.TotalDirs} completed...");
};
A .NET-ökoszisztémában követett mintákat láthatta. Az ilyen minták és konvenciók elsajátításával gyorsan írsz idiomatikus C#- és .NET-kódot.
Lásd még
A következő lépésben a .NET legújabb kiadásában látható néhány változás ezekben a mintákban.