Share via


Standaard .NET-gebeurtenispatronen

Vorige

.NET-gebeurtenissen volgen over het algemeen enkele bekende patronen. Het standaardiseren van deze patronen betekent dat ontwikkelaars kennis van deze standaardpatronen kunnen toepassen, die kunnen worden toegepast op elk .NET-gebeurtenisprogramma.

Laten we deze standaardpatronen doorlopen, zodat u beschikt over alle benodigde kennis om standaard gebeurtenisbronnen te maken, uzelf erop te abonneren en standaardgebeurtenissen in uw code te verwerken.

Handtekeningen van gebeurtenisafgevaardigden

De standaardhandtekening voor een .NET-gebeurtenisdelegatie is:

void EventRaised(object sender, EventArgs args);

Deze standaardhandtekening biedt inzicht in wanneer gebeurtenissen worden gebruikt:

  • Het retourtype is ongeldig. Gebeurtenissen kunnen nul tot veel luisteraars hebben. Door een gebeurtenis te activeren, worden alle ontvangers op de hoogte gesteld. Over het algemeen bieden listeners geen waarden als reactie op gebeurtenissen.
  • gebeurtenissen geven de afzenderaan: de handtekening van de gebeurtenis bevat het object dat de gebeurtenis heeft gegenereerd. Dit biedt elke listener een mechanisme om met de afzender te communiceren. Het type tijdens compilatie van sender is System.Object, ook al kent u waarschijnlijk een meer afgeleid type dat altijd correct zou zijn. Gebruik volgens conventie object.
  • gebeurtenissen bevatten aanvullende informatie in één structuur: de parameter args is een type dat is afgeleid van System.EventArgs die aanvullende informatie bevat. (U ziet in de volgende sectie dat deze conventie niet meer wordt afgedwongen.) Als uw gebeurtenistype geen argumenten meer nodig heeft, moet u nog steeds beide argumenten opgeven. Er is een speciale waarde, EventArgs.Empty die u moet gebruiken om aan te geven dat uw gebeurtenis geen aanvullende informatie bevat.

We gaan een klasse bouwen waarin bestanden in een map of een van de bijbehorende submappen worden vermeld die een patroon volgen. Dit onderdeel genereert een gebeurtenis voor elk bestand dat overeenkomt met het patroon.

Het gebruik van een gebeurtenismodel biedt enkele ontwerpvoordelen. U kunt meerdere gebeurtenislisteners maken die verschillende acties uitvoeren wanneer een gezocht bestand wordt gevonden. Door de verschillende listeners te combineren, kunnen robuustere algoritmen worden gemaakt.

Hier volgt de eerste gebeurtenisargumentdeclaratie voor het vinden van een gezocht bestand:

public class FileFoundArgs : EventArgs
{
    public string FoundFile { get; }

    public FileFoundArgs(string fileName) => FoundFile = fileName;
}

Hoewel dit type eruitziet als een klein, alleen gegevenstype, moet u de conventie volgen en een verwijzingstype (class) maken. Dit betekent dat het argumentobject wordt doorgegeven via een verwijzing en dat alle updates van de gegevens door alle abonnees worden bekeken. De eerste versie is een onveranderbaar object. U moet de eigenschappen in uw gebeurtenisargumenttype onveranderbaar maken. Op die manier kan één abonnee de waarden niet wijzigen voordat een andere abonnee deze ziet. (Er zijn uitzonderingen op deze praktijk, zoals u later ziet.)

Vervolgens moeten we de gebeurtenisdeclaratie maken in de klasse FileSearcher. Als u het type System.EventHandler<TEventArgs> gebruikt, betekent dit dat u nog geen andere typedefinitie hoeft te maken. U gebruikt gewoon een algemene specialisatie.

Laten we de FileSearcher-klasse invullen om te zoeken naar bestanden die overeenkomen met een patroon en de juiste gebeurtenis te genereren wanneer een overeenkomst wordt gedetecteerd.

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));
        }
    }
}

Veldachtige gebeurtenissen definiëren en genereren

De eenvoudigste manier om een gebeurtenis aan uw klasse toe te voegen, is door die gebeurtenis als een openbaar veld te declareren, zoals in het vorige voorbeeld:

public event EventHandler<FileFoundArgs>? FileFound;

Het lijkt erop dat hiermee een openbaar veld wordt gedeclareerd, wat een slechte objectgeoriënteerde praktijk lijkt te zijn. U wilt gegevenstoegang beveiligen via eigenschappen of methoden. Hoewel deze code er als een slechte gewoonte uitziet, maakt de code die door de compiler wordt gegenereerd wrappers, zodat de gebeurtenisobjecten alleen op veilige manieren kunnen worden geopend. De enige bewerkingen die beschikbaar zijn voor een veldachtige gebeurtenis zijn handler toevoegen:

var fileLister = new FileSearcher();
int filesFound = 0;

EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs) =>
{
    Console.WriteLine(eventArgs.FoundFile);
    filesFound++;
};

fileLister.FileFound += onFileFound;

En verwijder handler van:

fileLister.FileFound -= onFileFound;

Er is een lokale variabele voor de handler. Als u de body van de lambda hebt gebruikt, werkt het verwijderen niet correct. Het zou een ander exemplaar van de delegate zijn en stilletjes niets doen.

Code buiten de klasse kan de gebeurtenis niet genereren en kan ook geen andere bewerkingen uitvoeren.

Waarden retourneren van gebeurtenisabonnees

Uw eenvoudige versie werkt prima. Laten we nog een functie toevoegen: Annulering.

Wanneer u het event Found activeert, moeten luisteraars verdere verwerking kunnen stoppen, als dit bestand het laatste is dat gezocht wordt.

De gebeurtenis-handlers retourneren geen waarde, dus u moet dat op een andere manier communiceren. Het standaard gebeurtenispatroon maakt gebruik van het EventArgs object om velden op te nemen die gebeurtenisabonnees kunnen gebruiken om te communiceren met annuleren.

Er kunnen twee verschillende patronen worden gebruikt op basis van de semantiek van het annuleren van het contract. In beide gevallen voegt u een booleaans veld toe aan de EventArguments voor de gevonden bestandsgebeurtenis.

Met één patroon kan elke abonnee de bewerking annuleren. Voor dit patroon wordt het nieuwe veld geïnitialiseerd naar false. Elke abonnee kan deze wijzigen in true. Nadat de gebeurtenis voor alle abonnees is gegenereerd, onderzoekt het filesearcher-onderdeel de booleaanse waarde en voert de actie uit.

Het tweede patroon annuleert alleen de bewerking als alle abonnees de bewerking willen annuleren. In dit patroon wordt het nieuwe veld geïnitialiseerd om aan te geven dat de bewerking moet worden geannuleerd en kan elke abonnee dit wijzigen om aan te geven dat de bewerking moet worden voortgezet. Nadat alle abonnees de gegenereerde gebeurtenis hebben verwerkt, onderzoekt het FileSearcher-onderdeel de Booleaanse waarde en onderneemt actie op basis daarvan. Er is een extra stap in dit patroon: het onderdeel moet weten of abonnees op de gebeurtenis hebben gereageerd. Als er geen abonnees zijn, geeft het veld een onjuiste annulering aan.

Laten we de eerste versie voor dit voorbeeld implementeren. U moet een booleaans veld met de naam CancelRequested toevoegen aan het FileFoundArgs type:

public class FileFoundArgs : EventArgs
{
    public string FoundFile { get; }
    public bool CancelRequested { get; set; }

    public FileFoundArgs(string fileName) => FoundFile = fileName;
}

Dit nieuwe veld wordt automatisch geïnitialiseerd voor false, zodat u niet per ongeluk annuleert. De enige andere wijziging in het onderdeel is het controleren van de vlag na het indienen van de gebeurtenis om te zien of een van de abonnees een annulering heeft aangevraagd:

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;
    }
}

Een voordeel van dit patroon is dat het geen belangrijke wijziging is. Geen van de abonnees heeft eerder annulering aangevraagd, en ze doen dat nog steeds niet. Voor geen van de abonneecode zijn updates vereist, tenzij ze het nieuwe annuleringsprotocol willen ondersteunen.

We gaan de abonnee bijwerken zodat deze een annulering aanvraagt zodra het het eerste uitvoerbare bestand heeft gevonden:

EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs) =>
{
    Console.WriteLine(eventArgs.FoundFile);
    eventArgs.CancelRequested = true;
};

Een andere gebeurtenisdeclaratie toevoegen

Laten we nog een functie toevoegen en andere taalidiomen voor gebeurtenissen demonstreren. Laten we een overbelasting toevoegen van de Search methode die alle submappen doorkruist in het zoeken naar bestanden.

Deze methode kan een langdurige bewerking in een map met veel submappen zijn. Laten we een gebeurtenis toevoegen die wordt gegenereerd wanneer elke nieuwe directoryzoekopdracht begint. Met deze gebeurtenis kunnen abonnees de voortgang bijhouden en de gebruiker bijwerken over de voortgang. Alle voorbeelden die u tot nu toe hebt gemaakt, zijn openbaar. Laten we deze gebeurtenis een interne gebeurtenis maken. Dat betekent dat u ook de argumenttypen intern kunt maken.

U begint met het maken van de nieuwe afgeleide klasse EventArgs voor het rapporteren van de nieuwe map en de voortgang.

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;
    }
}

Nogmaals, u kunt de aanbevelingen volgen om een onveranderbaar verwijzingstype te maken voor de gebeurtenisargumenten.

Definieer vervolgens de gebeurtenis. Deze keer gebruikt u een andere syntaxis. Naast het gebruik van de syntaxis van het veld, kunt u de gebeurteniseigenschap expliciet maken met handlers toevoegen en verwijderen. In dit voorbeeld hebt u geen extra code in deze handlers nodig, maar dit laat zien hoe u ze zou maken.

internal event EventHandler<SearchDirectoryArgs> DirectoryChanged
{
    add { _directoryChanged += value; }
    remove { _directoryChanged -= value; }
}
private EventHandler<SearchDirectoryArgs>? _directoryChanged;

Op veel manieren weerspiegelt de code die u hier schrijft de code die de compiler genereert voor de veld gebeurtenisdefinities die u eerder hebt gezien. U maakt de gebeurtenis met behulp van syntaxis die vergelijkbaar is met eigenschappen. U ziet dat de handlers verschillende namen hebben: add en remove. Deze accessoren worden aangeroepen om zich te abonneren op de gebeurtenis of om zich af te melden voor de gebeurtenis. Houd er rekening mee dat u ook een private achterliggend veld moet declareren om de gebeurtenisvariabele op te slaan. Deze variabele wordt geïnitialiseerd op null.

Vervolgens gaan we de overbelasting toevoegen van de Search methode die door submappen gaat en beide gebeurtenissen genereert. De eenvoudigste manier is om een standaardargument te gebruiken om op te geven dat u in alle mappen wilt zoeken:

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;
    }
}

Nu kunt u de applicatie uitvoeren die de overloadfunctie aanroept voor het doorzoeken van alle submappen. Er zijn geen abonnees op de nieuwe DirectoryChanged gebeurtenis, maar het gebruik van de ?.Invoke() idioom zorgt ervoor dat deze correct werkt.

Laten we een handler toevoegen om een regel te schrijven waarin de voortgang in het consolevenster wordt weergegeven.

fileLister.DirectoryChanged += (sender, eventArgs) =>
{
    Console.Write($"Entering '{eventArgs.CurrentSearchDirectory}'.");
    Console.WriteLine($" {eventArgs.CompletedDirs} of {eventArgs.TotalDirs} completed...");
};

U hebt patronen gezien die in het hele .NET-ecosysteem worden gevolgd. Door deze patronen en conventies te leren, schrijft u snel idiomatische C# en .NET.

Zie ook

Vervolgens ziet u enkele wijzigingen in deze patronen in de meest recente release van .NET.