Aracılığıyla paylaş


Konsol uygulaması

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

  • .NET CLI'nın temelleri
  • C# Konsol Uygulamasının yapısı
  • Konsol G/Ç
  • .NET'teki 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 çıktısı, yüksek sesle okuma hızıyla uyumlu olacak şekilde ayarlanmıştır. '<' (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 bir Docker kapsayıcısında çalıştırabilirsiniz.

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

Önkoşullar

Uygulamayı 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 komut 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 Hello World uygulamasını çalıştıralım. Uygulamayı oluşturduktan sonra komut istemine dotnet run yazın. Bu komut NuGet paketi geri yükleme işlemini çalıştırır, uygulama yürütülebilir dosyasını oluşturur ve yürütülebilir dosyayı çalıştırır.

Basit Hello World uygulama kodunun tümü Program.csiçindedir. Bu dosyayı sık kullandığınız metin düzenleyiciyle açın. Program.cs içindeki kodu aşağıdaki kodla değiştirin:

namespace TeleprompterConsole;

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

Dosyanın üst kısmında bir namespace ifadesine bakın. C#, kullanmış olabileceğiniz diğer Nesne Odaklı diller gibi türleri düzenlemek için ad alanlarını kullanır. Bu Hello World programı farklı değildir. Programın ad alanında TeleprompterConsoleadlı olduğunu görebilirsiniz.

Dosyayı Okuma ve Yansıtma

Eklenecek ilk özellik, bir metin dosyasını okuyup tüm bu metni konsolda görüntüleyebilmektir. İlk olarak bir metin dosyası ekleyelim. Bu örnek için GitHub deposundaki sampleQuotes.txt dosyasını proje dizininize kopyalayın. Bu, uygulamanız için komut dosyası işlevi görür. Bu öğretici için örnek uygulamayı indirme hakkında bilgi için Örnekler ve Öğreticileryönergelerine bakın.

Ardından, Program sınıfınıza aşağıdaki yöntemi ekleyin (Main yönteminin 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, tembel bir şekilde değerlendirilen dizileri döndürür. Bu, dizideki her öğenin, sırayı kullanan kod tarafından istendiği şekilde oluşturulduğu anlamına gelir. Yineleyici yöntemleri, bir veya daha fazla yield return deyimi içeren yöntemlerdir. ReadFrom yöntemi tarafından döndürülen nesnesi, dizideki her öğeyi oluşturmaya yönelik kodu içerir. Bu örnekte, kaynak dosyadan bir sonraki metin satırını okumayı ve bu dizeyi döndürmeyi içerir. Çağıran kod diziden bir sonraki öğeyi her istediğinde, kod dosyadan 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. Bu yöntemdeki using deyimi kaynak temizlemeyi yönetir. using deyiminde başlatılan değişkenin (bu örnektereader) IDisposable arabirimini uygulaması gerekir. Bu arabirim, kaynağın yayımlanması gerektiğinde çağrılması gereken tek bir yöntem (Dispose) tanımlar. Derleyici, yürütme using deyiminin kapanış küme ayracına ulaştığında bu çağrıyı 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 var anahtar sözcüğü kullanılarak tanımlanır. var, örtük olarak yazılan biryerel değişken tanımlar. Bu, değişkenin türünün değişkene atanan nesnenin derleme zamanı türüne göre belirlendiği anlamına gelir. Burada, OpenText(String) yönteminden döndürülen değer, StreamReader nesnesidir.

Şimdi Main yöntemindeki dosyayı okumak için kodu dolduralım:

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

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

Çıkışa gecikmeler ekleme ve biçimlendirme

Elinizdeki ş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 çekirdek kodlardan bazılarını oluşturacaksınız. Ancak, bu ilk adımlar birkaç karşı-desen izler. Koda ekleme yapılırken anti-desens yorumlarda belirtilir ve kod sonraki adımlarda güncellenir.

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 tamamlandı. yield return line; deyimini 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, dosyanın satırlarını kullanma şeklinizi değiştirmeniz ve her sözcüğü yazdıktan sonra bir gecikme eklemeniz gerekir. Main yöntemindeki Console.WriteLine(line) deyimini 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 çıktıyı 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 olduğundan, görüntülenen çıktı bazı sorunları gösterir. Kaydırılırken bunu okumak zor olabilir. Bunu düzeltmek kolay. 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 ReadFrom yönteminde words bildiriminden sonra yerel bir değişken bildirin:

var lineLength = 0;

Ardından, yield return word + " "; deyiminden 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 yüksek sesle okuyabileceksiniz.

Eş Zamansız 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 şu ana kadar oluşturduğunuz kodu temsil eden zaman uyumsuz Task dönüş yöntemi oluşturmaktır.

Bu yöntemi Program sınıfınıza ekleyin (Main yönteminizin 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öntemin gövdesinde, bir görevin zaman uyumlu bir şekilde bitmesini beklemek için Wait() çağırmak yerine, bu sürüm await anahtar sözcüğünü kullanır. Bunu yapmak için yöntem imzasına async değiştiricisini eklemeniz gerekir. Bu yöntem bir Taskdöndürür. Task nesnesi döndüren herhangi bir dönüş ifadisi olmadığını fark edin. Bunun yerine, bu Task nesnesi, await işlecini kullandığınızda derleyicinin oluşturduğu kodla oluşturulur. Bu yöntem bir await'a ulaştığında geri döner diye 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. Tamamlanmaya kadar yürütülürken, döndürülen Task işlemin tamamlandığını gösterir. Çağrı kodu, döndürülen Task değerini izleyerek ne zaman bitirdiğini belirleyebilir.

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

await ShowTeleprompter();

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

static async Task Main(string[] args)

temel bilgiler bölümümüzde async Main yöntemi hakkında 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ını izlemeniz gerekir. Bu görev için eklediğiniz yöntem şu şekildedir:

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 ve kullanıcı '<' (küçüktür) veya '>' (büyüktür) tuşlarına bastığında gecikmeyi temsil eden yerel değişkeni değiştiren Action temsilcisini temsil eden bir lambda ifadesi oluşturur. Temsilci yöntemi, kullanıcı 'X' veya 'x' tuşlarına bastığında tamamlar 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 ReadKey() kullanır.

Bu özelliği tamamlamak için, bu görevlerin her ikisini de (GetInput ve ShowTeleprompter) başlatan ve bu iki görev arasındaki paylaşılan verileri yöneten yeni bir async Task 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 bu sınıfı gösterildiği gibi TeleprompterConsole ad alanına ekleyin. Ayrıca, kapsayan sınıf veya ad alanı adları olmadan Min ve Max yöntemlerine başvurabilmeniz için dosyanın en üstüne bir using static deyimi eklemeniz gerekir. using static deyimi, yöntemleri bir sınıftan içeri aktarır. Bu, staticiçermeyen ve ad alanından tüm sınıfları içeri aktaran using deyiminin aksinedir.

using static System.Math;

Ardından, yeni config nesnesini kullanmak için ShowTeleprompter ve GetInput yöntemlerini güncelleştirmeniz gerekir. Her iki görevi de başlatmak ve ilk görev tamamlandığında çıkmak için async yöntemini döndüren son bir Task 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 WhenAny(Task[]) çağrısıdır. Bu, argüman listesindeki görevlerden herhangi biri tamamlandığında sona eren bir Task oluşturur.

Ardından, gecikme için config nesnesini kullanmak için hem ShowTeleprompter hem de GetInput 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 ShowTeleprompter sürümü, TeleprompterConfig sınıfında yeni bir yöntem çağırır. Şimdi ShowTeleprompteryerine RunTeleprompter çağırmak için Main güncelleştirmeniz gerekir:

await RunTeleprompter();

Sonuç

Bu öğreticide, Konsol uygulamalarında çalışmayla ilgili C# dili ve .NET Core kitaplıkları ile ilgili bir dizi özellik 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 engelleyen ve engellemeyen kullanımını, C# diline yönelik bir turu, C# programlarının nasıl organize edildiğini ve .NET CLI'yi gördünüz.

Dosya G/Ç hakkında daha fazla bilgi için bkz. Dosya ve Akım 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.