Standart .NET olay desenleri

Geri

.NET olayları genellikle bilinen birkaç deseni izler. Bu desenleri standartlaştırmak, geliştiricilerin herhangi bir .NET olay programına uygulanabilen bu standart desenler hakkında bilgiden yararlanabileceği anlamına gelir.

Standart olay kaynakları oluşturmak ve kodunuzda standart olaylara abone olmak ve işlemek için ihtiyacınız olan tüm bilgilere sahip olmak için bu standart desenleri inceleyelim.

Olay temsilcisi imzaları

.NET olay temsilcisinin standart imzası:

void EventRaised(object sender, EventArgs args);

Dönüş türü geçersiz. Olaylar temsilcileri temel alır ve çok noktaya yayın temsilcileridir. Bu, herhangi bir olay kaynağı için birden çok aboneyi destekler. Bir yöntemden tek dönüş değeri birden çok olay abonesine ölçeklendirilmiyor. Bir olay yükseltildikten sonra olay kaynağı hangi dönüş değerini görür? Bu makalenin ilerleyen bölümlerinde, olay kaynağına bilgi bildiren olay abonelerini destekleyen olay protokollerinin nasıl oluşturulacağını göreceksiniz.

Bağımsız değişken listesi iki bağımsız değişken içerir: gönderen ve olay bağımsız değişkenleri. Derleme zamanı türü sender , System.Objecther zaman doğru olabilecek daha türetilmiş bir tür bilmenize rağmen şeklindedir. Kurala göre kullanın object.

İkinci bağımsız değişken genellikle türünden System.EventArgstüretilmiştir. (Sonraki bölümde bu kuralın artık uygulanmadığını göreceksiniz.) Olay türünüzün herhangi bir ek bağımsız değişkene ihtiyacı yoksa, her iki bağımsız değişkeni de sağlamaya devam edebilirsiniz. Etkinliğinizin ek bilgi içermediğini belirtmek için kullanmanız gereken özel bir değer EventArgs.Empty vardır.

Şimdi bir dizindeki dosyaları veya bir deseni izleyen alt dizinlerini listeleyen bir sınıf oluşturalım. Bu bileşen, desenle eşleşen her dosya için bir olay oluşturur.

Olay modeli kullanmak bazı tasarım avantajları sağlar. Aranan bir dosya bulunduğunda farklı eylemler gerçekleştiren birden çok olay dinleyicisi oluşturabilirsiniz. Farklı dinleyicileri birleştirmek daha güçlü algoritmalar oluşturabilir.

Aranan dosyayı bulmak için ilk olay bağımsız değişkeni bildirimi aşağıdadır:

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

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

Bu tür küçük, yalnızca veri türü gibi görünse de, kuralı izlemeli ve bunu bir başvuru (class) türü yapmalısınız. Bu, bağımsız değişken nesnesinin başvuruyla geçirileceği ve verilerdeki tüm güncelleştirmelerin tüm aboneler tarafından görüntülendiği anlamına gelir. İlk sürüm sabit bir nesnedir. Olay bağımsız değişken türünüzdeki özellikleri sabit hale getirmeniz gerekir. Bu şekilde, bir abone değerleri başka bir abone görmeden değiştiremez. (Aşağıda göreceğiniz gibi bunun özel durumları vardır.)

Ardından FileSearcher sınıfında olay bildirimini oluşturmamız gerekir. Türünden yararlandığınızda EventHandler<T> başka bir tür tanımı oluşturmanız gerekmez. Yalnızca genel bir özelleştirme kullanırsınız.

Bir desenle eşleşen dosyaları aramak için FileSearcher sınıfını dolduralım ve bir eşleşme bulunduğunda doğru olayı tetikleyelim.

public class FileSearcher
{
    public event EventHandler<FileFoundArgs>? FileFound;

    public void Search(string directory, string searchPattern)
    {
        foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
        {
            RaiseFileFound(file);
        }
    }
    
    private void RaiseFileFound(string file) =>
        FileFound?.Invoke(this, new FileFoundArgs(file));
}

Alan benzeri olayları tanımlama ve oluşturma

Sınıfınıza olay eklemenin en basit yolu, önceki örnekte olduğu gibi bu olayı genel bir alan olarak bildirmektir:

public event EventHandler<FileFoundArgs>? FileFound;

Bu, nesne odaklı hatalı bir uygulama gibi görünen bir genel alan bildiriyor gibi görünüyor. Özellikler veya yöntemler aracılığıyla veri erişimini korumak istiyorsunuz. Bu kötü bir uygulama gibi görünse de, derleyici tarafından oluşturulan kod sarmalayıcılar oluşturur, böylece olay nesnelerine yalnızca güvenli yollarla erişilebilir. Alan benzeri bir olayda kullanılabilen tek işlem ekleme işleyicisidir:

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

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

fileLister.FileFound += onFileFound;

ve işleyiciyi kaldırma:

fileLister.FileFound -= onFileFound;

İşleyici için yerel bir değişken olduğunu unutmayın. Lambda gövdesini kullandıysanız, kaldırma düzgün çalışmaz. Temsilcinin farklı bir örneği olur ve sessizce hiçbir şey yapmaz.

Sınıfın dışındaki kod olayı tetikleyemez veya başka işlemler gerçekleştiremez.

Olay abonelerinden değer döndürme

Basit sürümünüz sorunsuz çalışıyor. Şimdi başka bir özellik ekleyelim: İptal.

Bulunan olayı yükselttiğiniz zaman, bu dosya aranan son dosyaysa dinleyicilerin daha fazla işlemeyi durdurabilmesi gerekir.

Olay işleyicileri bir değer döndürmez, bu nedenle bunu başka bir şekilde iletmeniz gerekir. Standart olay düzeni, olay abonelerinin iptali iletmek için kullanabileceği alanları eklemek için nesnesini kullanır EventArgs .

İptal sözleşmesinin semantiğine göre iki farklı desen kullanılabilir. Her iki durumda da bulunan dosya olayının EventArguments bölümüne boole alanı ekleyeceksiniz.

Tek bir düzen, herhangi bir abonenin işlemi iptal etmesine olanak tanır. Bu düzen için yeni alan olarak falsebaşlatılır. Herhangi bir abone bunu olarak truedeğiştirebilir. Tüm aboneler olayı gördükçe FileSearcher bileşeni boole değerini inceler ve eylemde bulunur.

İkinci desen yalnızca tüm aboneler işlemin iptalini isterse işlemi iptal eder. Bu düzende, işlemin iptal edilmesi gerektiğini belirtmek için yeni alan başlatılır ve herhangi bir abone işlemin devam etmesi gerektiğini belirtmek için bu alanı değiştirebilir. Tüm aboneler olayı tetikledikten sonra FileSearcher bileşeni boole değerini inceler ve işlem uygular. Bu düzende ek bir adım vardır: bileşenin olayı herhangi bir abonenin görüp görmediğini bilmesi gerekir. Abone yoksa, alan hatalı bir iptal olduğunu gösterir.

Şimdi bu örnek için ilk sürümü uygulayalım. Türüne adlı CancelRequestedFileFoundArgs bir boole alanı eklemeniz gerekir:

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

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

Bu yeni alan, bir alanın varsayılan değeri Boolean olarak otomatik olarak başlatılırfalse, bu nedenle yanlışlıkla iptal etmeyin. Bileşendeki diğer tek değişiklik, abonelerden herhangi birinin iptal isteğinde bulunan olup olmadığını görmek için olayı yükselttikte bayrağını denetlemektir:

private void SearchDirectory(string directory, string searchPattern)
{
    foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
    {
        FileFoundArgs args = RaiseFileFound(file);
        if (args.CancelRequested)
        {
            break;
        }
    }
}

private FileFoundArgs RaiseFileFound(string file)
{
    var args = new FileFoundArgs(file);
    FileFound?.Invoke(this, args);
    return args;
}

Bu düzenin bir avantajı, hataya neden olan bir değişiklik olmadığıdır. Abonelerden hiçbiri daha önce iptal isteğinde bulunmuyor ve yine de iptal edilmedi. Yeni iptal protokolünün desteklenmesini istemedikleri sürece abone kodundan hiçbirinin güncelleştirilmesi gerekli değil. Çok gevşek bir şekilde bağlanmış.

İlk yürütülebilir dosyayı bulduğunda iptal isteğinde bulunabilmesi için aboneyi güncelleştirelim:

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

Başka bir olay bildirimi ekleme

Şimdi bir özellik daha ekleyelim ve olaylar için diğer dil deyimlerini gösterelim. Şimdi dosya aramasında Search tüm alt dizinlerden geçen yöntemin aşırı yüklemesini ekleyelim.

Bu, birçok alt dizini olan bir dizinde uzun bir işlem olabilir. Şimdi her yeni dizin araması başladığında tetiklenen bir olay ekleyelim. Bu, abonelerin ilerleme durumunu izlemesine ve kullanıcıyı ilerleme olarak güncelleştirmesine olanak tanır. Şimdiye kadar oluşturduğunuz tüm örnekler geneldir. Bunu bir iç olay yapalım. Bu, bağımsız değişkenler için kullanılan türleri de iç hale getirebileceğiniz anlamına gelir.

Başlangıç olarak yeni dizini ve ilerleme durumunu raporlamak için türetilmiş yeni EventArgs sınıfını oluşturacaksınız.

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

Yine önerileri izleyerek olay bağımsız değişkenleri için sabit bir başvuru türü oluşturabilirsiniz.

Ardından olayı tanımlayın. Bu kez farklı bir söz dizimi kullanacaksınız. Alan söz dizimini kullanmaya ek olarak, işleyicileri ekleyip kaldırarak özelliğini açıkça oluşturabilirsiniz. Bu örnekte, bu işleyicilerde ek koda ihtiyacınız olmayacaktır, ancak bu, bunları nasıl oluşturabileceğinizi gösterir.

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

Burada yazdığınız kod birçok yönden derleyicinin daha önce gördüğünüz alan olayı tanımları için oluşturduğu kodu yansıtır. Olayı, özellikler için kullanılan söz dizimine çok benzer bir söz dizimi kullanarak oluşturursunuz. İşleyicilerin farklı adlara sahip olduğuna dikkat edin: add ve remove. Bunlar, olaya abone olmak veya etkinlik aboneliğini kaldırmak için çağrılır. Olay değişkenini depolamak için özel bir yedekleme alanı da bildirmeniz gerektiğine dikkat edin. Null olarak başlatılır.

Şimdi alt dizinleri geçen ve her iki olayı da tetikleyen yöntemin Search aşırı yüklemesini ekleyelim. Bunu yapmanın en kolay yolu, tüm dizinlerde arama yapmak istediğinizi belirtmek için varsayılan bir bağımsız değişken kullanmaktır:

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)
        {
            RaiseSearchDirectoryChanged(dir, totalDirs, completedDirs++);
            // Search 'dir' and its subdirectories for files that match the search pattern:
            SearchDirectory(dir, searchPattern);
        }
        // Include the Current Directory:
        RaiseSearchDirectoryChanged(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))
    {
        FileFoundArgs args = RaiseFileFound(file);
        if (args.CancelRequested)
        {
            break;
        }
    }
}

private void RaiseSearchDirectoryChanged(
    string directory, int totalDirs, int completedDirs) =>
    _directoryChanged?.Invoke(
        this,
            new SearchDirectoryArgs(directory, totalDirs, completedDirs));

private FileFoundArgs RaiseFileFound(string file)
{
    var args = new FileFoundArgs(file);
    FileFound?.Invoke(this, args);
    return args;
}

Bu noktada, tüm alt dizinleri aramak için aşırı yüklemeyi çağıran uygulamayı çalıştırabilirsiniz. Yeni DirectoryChanged olay üzerinde abone yok, ancak deyimini ?.Invoke() kullanmak bunun düzgün çalışmasını sağlar.

Konsol penceresinde ilerleme durumunu gösteren bir satır yazmak için bir işleyici ekleyelim.

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

.NET ekosistemi genelinde izlenen desenler gördünüz. Bu desenleri ve kuralları öğrenerek, idiomatic C# ve .NET'i hızla yazacaksınız.

Ayrıca bkz.

Ardından, .NET'in en son sürümünde bu desenlerde bazı değişiklikler göreceksiniz.