Konsol uygulaması

Bu öğreticide size .NET ve C# dilindeki bir dizi özellik öğretildi. Şunları öğreneceksiniz:

  • .NET CLI'nın temelleri
  • C# Konsol Uygulamasının yapısı
  • Konsol G/Ç
  • .NET'te Dosya G/Ç API'lerinin temelleri
  • .NET'te Görev Tabanlı Zaman Uyumsuz Programlamanın temelleri

Metin dosyasını okuyan ve bu metin dosyasının içeriğini konsola yankılayan bir uygulama oluşturacaksınız. Konsol çıkışı, yüksek sesle okunmasıyla eşleşecek şekilde ilerler. '' (küçüktür) veya '<' (büyüktür) tuşlarına basarak hızı hızlandırabilir veya> yavaşlatabilirsiniz. Bu uygulamayı Windows, Linux, macOS veya Docker kapsayıcısında çalıştırabilirsiniz.

Bu öğreticide birçok özellik vardır. Şimdi bunları tek tek oluşturalım.

Önkoşullar

Uygulama oluşturma

İlk adım yeni bir uygulama oluşturmaktır. Bir komut istemi açın ve uygulamanız için yeni bir dizin oluşturun. Bunu geçerli dizin yapın. Komut istemine komutu dotnet new console yazın. Bu, temel bir "Merhaba Dünya" uygulaması için başlangıç dosyalarını oluşturur.

Değişiklik yapmaya başlamadan önce basit Merhaba Dünya uygulamasını çalıştıralım. Uygulamayı oluşturduktan sonra komut istemine yazın dotnet run . Bu komut NuGet paketi geri yükleme işlemini çalıştırır, uygulamanın yürütülebilir dosyasını oluşturur ve yürütülebilir dosyayı çalıştırır.

Basit Merhaba Dünya uygulama kodunun tümü Program.cs dosyasındadır. Bu dosyayı sık kullandığınız metin düzenleyiciyle açın. Program.cs dosyasındaki kodu aşağıdaki kodla değiştirin:

namespace TeleprompterConsole;

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
    }
}

Dosyanın en üstünde bir namespace deyimine bakın. C#, kullanmış olabileceğiniz diğer Nesne Odaklı diller gibi türleri düzenlemek için ad alanlarını kullanır. Bu Merhaba Dünya programı farklı değildir. Programın ad alanında adıyla TeleprompterConsoleolduğunu görebilirsiniz.

Dosyayı Okuma ve Yankılama

Eklenecek ilk özellik, bir metin dosyasını okuyup tüm bu metni konsolda görüntüleyebilmektir. İlk olarak bir metin dosyası ekleyelim. Bu örneğin GitHub deposundakisampleQuotes.txtdosyasını proje dizininize kopyalayın. Bu, uygulamanız için betik olarak görev yapacaktır. Bu öğretici için örnek uygulamayı indirme hakkında bilgi için Örnekler ve Öğreticiler'deki yönergelere bakın.

Ardından sınıfınıza Program aşağıdaki yöntemi ekleyin (yönteminin Main hemen altına):

static IEnumerable<string> ReadFrom(string file)
{
    string? line;
    using (var reader = File.OpenText(file))
    {
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

Bu yöntem, yineleyici yöntemi olarak adlandırılan özel bir C# yöntemi türüdür. Yineleyici yöntemler, yavaş değerlendirilen dizileri döndürür. Bu, dizideki her öğenin, diziyi kullanan kod tarafından istendiği şekilde oluşturulduğu anlamına gelir. Yineleyici yöntemleri, bir veya daha fazla yield return deyim içeren yöntemlerdir. yöntemi tarafından ReadFrom döndürülen nesnesi, dizideki her öğeyi oluşturmaya yönelik kodu içerir. Bu örnekte, kaynak dosyadan sonraki metin satırını okuma ve bu dizeyi döndürmeyi içerir. Çağıran kod diziden bir sonraki öğeyi her istediğinde, kod dosyadan bir sonraki metin satırını okur ve döndürür. Dosya tamamen okunduğunda, sıra başka öğe olmadığını gösterir.

Sizin için yeni olabilecek iki C# söz dizimi öğesi vardır. using Bu yöntemdeki deyimi kaynak temizlemeyi yönetir. deyiminde başlatılan değişkenin using (readerbu örnekte) arabirimini IDisposable uygulaması gerekir. Bu arabirim, Disposekaynağın serbest bırakılması gerektiğinde çağrılması gereken tek bir yöntem tanımlar. Derleyici, yürütme deyiminin kapanış küme ayracına ulaştığında bu çağrıyı using oluşturur. Derleyici tarafından oluşturulan kod, using deyimi tarafından tanımlanan bloktaki koddan bir özel durum oluşturulsa bile kaynağın serbest bırakılmasını sağlar.

reader değişkeni anahtar sözcüğü kullanılarak var tanımlanır. varörtük olarak yazılan bir yerel değişkeni tanımlar. Bu, değişkenin türünün değişkene atanan nesnenin derleme zamanı türü tarafından belirlendiği anlamına gelir. Burada, yönteminden OpenText(String) döndürülen değerdir ve bu bir StreamReader nesnedir.

Şimdi yönteminin dosyasını Main okumak için kodu dolduralım:

var lines = ReadFrom("sampleQuotes.txt");
foreach (var line in lines)
{
    Console.WriteLine(line);
}

Programı çalıştırın (kullanarak dotnet run) ve konsola yazdırılan her satırı görebilirsiniz.

Gecikmeler ve Biçimlendirme çıkışı ekleme

Sahip olduğunuz şey yüksek sesle okunamayacak kadar hızlı görüntüleniyor. Şimdi çıkıştaki gecikmeleri eklemeniz gerekir. Başlarken, zaman uyumsuz işlemeyi etkinleştiren bazı temel kodları oluşturacaksınız. Ancak, bu ilk adımlar birkaç anti-deseni izler. Siz kodu eklerken açıklamalarda anti-desenler gösterilir ve kod sonraki adımlarda güncelleştirilir.

Bu bölümün iki adımı vardır. İlk olarak, yineleyici yöntemini satırların tamamı yerine tek sözcükler döndürecek şekilde güncelleştireceksiniz. Bu değişikliklerle yapılır. deyimini yield return line; aşağıdaki kodla değiştirin:

var words = line.Split(' ');
foreach (var word in words)
{
    yield return word + " ";
}
yield return Environment.NewLine;

Ardından, dosya satırlarını kullanma şeklinizi değiştirmeniz ve her sözcüğü yazdıktan sonra bir gecikme eklemeniz gerekir. Console.WriteLine(line) yöntemindeki deyimini Main aşağıdaki blokla değiştirin:

Console.Write(line);
if (!string.IsNullOrWhiteSpace(line))
{
    var pause = Task.Delay(200);
    // Synchronously waiting on a task is an
    // anti-pattern. This will get fixed in later
    // steps.
    pause.Wait();
}

Örneği çalıştırın ve çıkışı denetleyin. Şimdi her bir sözcük yazdırılır ve ardından 200 ms gecikme olur. Ancak, kaynak metin dosyasında satır sonu olmadan 80'den fazla karakter içeren birkaç satır bulunduğundan, görüntülenen çıktıda bazı sorunlar gösterilir. Kaydırırken bunu okumak zor olabilir. Bunu düzeltmek kolaydır. Yalnızca her satırın uzunluğunu takip eder ve çizgi uzunluğu belirli bir eşiğe ulaştığında yeni bir çizgi oluşturursunuz. satır uzunluğunu tutan yönteminde bildiriminden wordsReadFrom sonra bir yerel değişken bildirin:

var lineLength = 0;

Ardından deyiminden yield return word + " "; sonra (kapanış ayracından önce) aşağıdaki kodu ekleyin:

lineLength += word.Length + 1;
if (lineLength > 70)
{
    yield return Environment.NewLine;
    lineLength = 0;
}

Örneği çalıştırdığınızda, önceden yapılandırılmış hızıyla sesli okuyabileceksiniz.

Zaman Uyumsuz Görevler

Bu son adımda, çıktıyı bir göreve zaman uyumsuz olarak yazmak için kodu eklerken, metin görünümünü hızlandırmak veya yavaşlatmak ya da metin görünümünü tamamen durdurmak isteyen kullanıcıdan gelen girişleri okumak için başka bir görev çalıştıracaksınız. Bu işlemde birkaç adım vardır ve sonunda ihtiyacınız olan tüm güncelleştirmelere sahip olursunuz. İlk adım, dosyayı okumak ve görüntülemek için oluşturduğunuz kodu temsil eden zaman uyumsuz Task bir dönüş yöntemi oluşturmaktır.

Bu yöntemi sınıfınıza Program ekleyin (yönteminizin Main gövdesinden alınır):

private static async Task ShowTeleprompter()
{
    var words = ReadFrom("sampleQuotes.txt");
    foreach (var word in words)
    {
        Console.Write(word);
        if (!string.IsNullOrWhiteSpace(word))
        {
            await Task.Delay(200);
        }
    }
}

İki değişiklik fark edeceksiniz. İlk olarak, yönteminin gövdesinde bir görevin zaman uyumlu bir şekilde bitmesini beklemek için çağırmak Wait() yerine bu sürüm anahtar sözcüğünü await kullanır. Bunu yapmak için değiştiriciyi async yöntem imzasına eklemeniz gerekir. Bu yöntem bir Taskdöndürür. Nesne Task döndüren return deyimi olmadığına dikkat edin. Bunun yerine, bu Task nesne işlecini kullandığınızda await derleyicinin oluşturduğu kodla oluşturulur. Bu yöntemin bir awaitöğesine ulaştığında döndürdüğünü düşünebilirsiniz. Döndürülen Task , çalışmanın tamamlanmadığını gösterir. Beklenen görev tamamlandığında yöntemi sürdürülür. Tamamlanmak üzere yürütülürken döndürülen Task , işlemin tamamlandığını gösterir. Çağıran kod, ne zaman tamamlandığını belirlemek için döndürülen Task kodu izleyebilir.

çağrısından ShowTeleprompterönce bir await anahtar sözcük ekleyin:

await ShowTeleprompter();

Bunun için yöntem imzasını Main şu şekilde değiştirmeniz gerekir:

static async Task Main(string[] args)

Temel bilgiler bölümünde yöntem hakkında async Main daha fazla bilgi edinin.

Ardından, Konsoldan okumak için ikinci zaman uyumsuz yöntemi yazmanız ve '' (küçüktür), '<' (büyüktür) ve '>X' veya 'x' anahtarları için watch gerekir. Bu görev için eklediğiniz yöntem aşağıdadır:

private static async Task GetInput()
{
    var delay = 200;
    Action work = () =>
    {
        do {
            var key = Console.ReadKey(true);
            if (key.KeyChar == '>')
            {
                delay -= 10;
            }
            else if (key.KeyChar == '<')
            {
                delay += 10;
            }
            else if (key.KeyChar == 'X' || key.KeyChar == 'x')
            {
                break;
            }
        } while (true);
    };
    await Task.Run(work);
}

Bu, konsoldan bir anahtar okuyan bir Action temsilciyi temsil eden bir lambda ifadesi oluşturur ve kullanıcı '' (küçüktür) veya '<>' (büyüktür) tuşlarına bastığında gecikmeyi temsil eden bir yerel değişkeni değiştirir. Temsilci yöntemi, kullanıcı 'X' veya 'x' tuşlarına bastığında sona erer ve bu da kullanıcının metin görüntüsünü istediği zaman durdurmasına olanak tanır. Bu yöntem, kullanıcının bir tuşa basmasını engellemek ve beklemek için kullanır ReadKey() .

Bu özelliği tamamlamak için, bu görevlerin (GetInput ve ) ikisini de başlatan ve ShowTeleprompterbu iki görev arasındaki paylaşılan verileri yöneten yeni async Task bir dönüş yöntemi oluşturmanız gerekir.

Bu iki görev arasındaki paylaşılan verileri işleyebilen bir sınıf oluşturmanın zamanı geldi. Bu sınıf iki genel özellik içerir: gecikme ve dosyanın tamamen okunduğunu belirten bir bayrak Done :

namespace TeleprompterConsole;

internal class TelePrompterConfig
{
    public int DelayInMilliseconds { get; private set; } = 200;
    public void UpdateDelay(int increment) // negative to speed up
    {
        var newDelay = Min(DelayInMilliseconds + increment, 1000);
        newDelay = Max(newDelay, 20);
        DelayInMilliseconds = newDelay;
    }
    public bool Done { get; private set; }
    public void SetDone()
    {
        Done = true;
    }
}

Bu sınıfı yeni bir dosyaya yerleştirin ve gösterildiği gibi bu sınıfı TeleprompterConsole ad alanına ekleyin. Ayrıca, kapsayan sınıf veya ad alanı adları olmadan ve Max yöntemlerine başvurabilmeniz Min için dosyanın en üstüne bir using static deyim eklemeniz gerekir. Deyimi using static , yöntemleri bir sınıftan içeri aktarır. Bu, ad alanından tüm sınıfları içeri aktaran olmadan staticdeyiminin using aksinedir.

using static System.Math;

Ardından, yeni config nesneyi kullanmak için ve GetInput yöntemlerini güncelleştirmeniz ShowTeleprompter gerekir. Her iki görevi de başlatmak ve ilk görev tamamlandığında çıkmak için son Task bir dönüş async yöntemi yazın:

private static async Task RunTeleprompter()
{
    var config = new TelePrompterConfig();
    var displayTask = ShowTeleprompter(config);

    var speedTask = GetInput(config);
    await Task.WhenAny(displayTask, speedTask);
}

Buradaki yeni yöntemlerden biri çağrıdır WhenAny(Task[]) . Bu, bağımsız değişken listesindeki görevlerden herhangi biri tamamlandığında biten bir Task oluşturur.

Ardından, gecikme için nesnesini kullanmak config üzere hem ve GetInputShowTeleprompter yöntemlerini güncelleştirmeniz gerekir:

private static async Task ShowTeleprompter(TelePrompterConfig config)
{
    var words = ReadFrom("sampleQuotes.txt");
    foreach (var word in words)
    {
        Console.Write(word);
        if (!string.IsNullOrWhiteSpace(word))
        {
            await Task.Delay(config.DelayInMilliseconds);
        }
    }
    config.SetDone();
}

private static async Task GetInput(TelePrompterConfig config)
{
    Action work = () =>
    {
        do {
            var key = Console.ReadKey(true);
            if (key.KeyChar == '>')
                config.UpdateDelay(-10);
            else if (key.KeyChar == '<')
                config.UpdateDelay(10);
            else if (key.KeyChar == 'X' || key.KeyChar == 'x')
                config.SetDone();
        } while (!config.Done);
    };
    await Task.Run(work);
}

Bu yeni sürümü ShowTeleprompter sınıfında yeni bir yöntem TeleprompterConfig çağırır. Şimdi, yerine öğesini çağırmak RunTeleprompterShowTeleprompteriçin güncelleştirmeniz Main gerekir:

await RunTeleprompter();

Sonuç

Bu öğreticide, Konsol uygulamalarında çalışmayla ilgili C# dili ve .NET Core kitaplıkları ile ilgili bazı özellikler gösterilmiştir. Dil ve burada tanıtılan sınıflar hakkında daha fazla bilgi edinmek için bu bilgiyi temel alabilirsiniz. Dosya ve Konsol G/Ç'nin temellerini, Görev tabanlı zaman uyumsuz programlamanın engelleyici ve engelleyici olmayan kullanımını, C# dilinin bir turunu ve C# programlarının nasıl düzenleniyor olduğunu ve .NET CLI'yı gördünüz.

Dosya G/Ç hakkında daha fazla bilgi için bkz. Dosya ve Akış G/Ç. Bu öğreticide kullanılan zaman uyumsuz programlama modeli hakkında daha fazla bilgi için bkz . Görev Tabanlı Zaman Uyumsuz Programlama ve Zaman Uyumsuz programlama.