7 Mart 2023 Salı

C# Temelleri 5

 Merhaba direk bu sayfaya gelenler için belirteyim, bu yazı serinin beşinci yazısı. Önceki bölümler için 

https://ujk-ujk.blogspot.com/2023/01/c-temelleri-1.html ve 

https://ujk-ujk.blogspot.com/2023/02/c-temelleri-2.html ve

https://ujk-ujk.blogspot.com/2023/02/c-temelleri-3.html ve

https://ujk-ujk.blogspot.com/2023/02/c-temelleri-4.html

sayfalarına bakınız.


C# Bir Nesnenin Parametre Değeri Olarak Verilmesi

Yine örnek olarak bir Araç sınıfı tanımı ile başlayalım.

using System;

namespace CSharpTemelleri
{
    class Program
    {
        static void Main(string[] args)
        {
            Araç araç1 = new Araç("Mustang", "kırmızı");

            Console.ReadKey();
        }
    }
    class Araç
    {
        public String model;
        public String renk;

        public Araç(String model, String renk)
        {
            this.model = model;
            this.renk = renk;
        }
    }
}

Araç sınıfının 2 özelliği var model ve renk. Diyelim bir metod tanımlayıp araç1 nesnesinin rengini değiştirmek istiyoruz. Bu metoda da araç1 nesnesini parametre olarak göndermek istiyoruz. 

        static void Main(string[] args)
        {
            Araç araç1 = new Araç("Mustang", "kırmızı");

            RenkDeğiştir(araç1, "gümüş");
            Console.WriteLine(araç1.renk + " " + araç1.model);

            Console.ReadKey();
        }
        public static void RenkDeğiştir(Araç araç, String renk)
        {
            araç.renk = renk;
        }

Programı çalıştırırsak araç1 nesnesinin renk özelliği değerinin değiştiğini görürüz.

Anladım basitmiş diyenlere benim hala tam anlayamadığım noktayı bir örnekle gösterelim.

            int sayı = 5;
            SayıDeğiştir(sayı);
            Console.WriteLine(sayı);

            Console.ReadKey();
        }
        public static void SayıDeğiştir(int sayı)
        {
            sayı = 10;
        }

ve sonuç

araç1 nesnesinin özellik değerini değiştirebilirken sayı değişkeni değerini değiştiremedik. Uzun açıklaması var ama kısaca bildiğimi anlatayım. 

Bir metoda parametreler değer olarak (by value) ya da referans olarak (by reference) gönderilebilir. C# dilinde eğer bir şey belirtmezseniz parametreleri değer olarak kabul eder. Ama nesneler default olarak referans değerlerdir, bu yüzden metoda içlerinde sakladıkları değerler değil nesnenin kendisi gider. int bir değişkeni referans olarak göndermek için ayrıca belirtmemiz gerekir.

            int sayı = 5;
            SayıDeğiştir(ref sayı);
            Console.WriteLine(sayı);

            Console.ReadKey();
        }
        public static void SayıDeğiştir(ref int sayı)
        {
            sayı = 10;
        }

Gördüğümüz gibi hem metodun tanımında parametre öncesine hem de metodu çağırırken int değişkenin adından öncesine ref kelimesi ekleyerek derleyiciye değer değil referans gönderdiğimizi belirtiyoruz. 

Referans anlamını biraz açarsak değişkenleri içlerinde değerler olan kutulara benzetmiştik. Değer gönderirken o kutunun içindeki değeri gönderiyoruz, ama referans gönderirken "bu kutu" diyerek kutuyu gösteriyoruz, metod da gidiyor kutu üzerinde direk eylemini yapıyor gibi düşünülebilir.

Basit bir eşittir işleminde bile bu referans olarak ve değer olarak meselesi iş nesnelere gelince kafa karıştırır. Diyelim aracımızın bir kopyasını oluşturmak istiyoruz. 

            Araç araç1 = new Araç("Mustang", "kırmızı");
            Araç araç2 = araç1;
            araç2.renk = "mor";

            Console.WriteLine(araç1.renk + " " + araç1.model);
            Console.WriteLine(araç2.renk + " " + araç2.model);

bu kodu çalıştırırsak 

araç1 de araç2 de aynı nesne oldular. Birinde yapılan işlem aynen diğerinde de gerçekleşiyor. Çünkü nesneler default olarak referans tipi değerler olduğundan ikisi de aynı nesneyi (aynı kutuyu) ifade ediyor. Bir nesneden kopya çıkartmak için şöyle bir metod yazabiliriz.

        static void Main(string[] args)
        {
            Araç araç1 = new Araç("Mustang", "kırmızı");
            Araç araç2 = Kopyala(araç1);
            araç2.renk = "mor";

            Console.WriteLine(araç1.renk + " " + araç1.model);
            Console.WriteLine(araç2.renk + " " + araç2.model);

            Console.ReadKey();
        }
        public static Araç Kopyala(Araç araç)
        {
            return new Araç(araç.model, araç.renk);
        }

Metodda yeni bir Araç nesnesi üreterek onu geri dönüyoruz, böylece farklı nesneler elde ediyoruz. Şimdi kodu çalıştırırsak,

Nesnelerin kopyasını çıkarırken böyle biraz zahmet etmek gerekiyor. 




C# Alt Sınıflarda Metod Tekrar Tanımlamaları (overriding)

Ana sınıf tanımında tanımlanmış bir metodun ondan üretilen alt sınıfta farklı bir eylem yapmasını istersek metodu tekrar tanımlarız. Diyelim bir Hayvan ana sınıfımız ve bundan üretilmiş Kedi ve Köpek adında alt sınıflarımız var.

    class Program
    {
        static void Main(string[] args)
        {
           

            Console.ReadKey();
        }
    }
    class Hayvan
    {

    }
    class Kedi : Hayvan
    {

    }
    class Köpek : Hayvan
    {

    }

Ana sınıfta bir metod tanımlayalım.

    class Hayvan
    {
        public void Konuş()
        {
            Console.WriteLine("Hayvan *brr* dedi");
        }
    }

Şimdi bir kedi ve bir köpek nesnesi üretip kullanalım.

        static void Main(string[] args)
        {
            Kedi kedi = new();
            Köpek köpek = new();

            kedi.Konuş();
            köpek.Konuş();

            Console.ReadKey();
        }

Nesneleri tanımlarken kısaltılmış kodu kullandım. Bu kodu çalıştırırsak,

Fakat biz biliyoruz ki kedi de köpek de brr demez. Alt sınıf tanımlarında bu metod tanımının üzerine yazmak için (yani değiştirmek için) aynı metod tanımını alt sınıf içinde de ama bu sefer başında override kelimesi kullanarak yaparız. 

    class Kedi : Hayvan
    {
        public override void Konuş()
        {
            Console.WriteLine("Kedi *miyav* dedi");
        }
    }
    class Köpek : Hayvan
    {
        public override void Konuş()
        {
            Console.WriteLine("Köpek *havhav* dedi");
        }
    }

Fakat bunu demek yetmez çünkü sistem bize "sen ana sınıfta bu metodun üzerine yazılabilir demedin" şeklinde bir uyarı verir.

Ana sınıf içinde de bu metodu virtual kelimesi ile üzerine yazılabilir olduğunu belirtmemiz gerekir.

    class Hayvan
    {
        public virtual void Konuş()
        {
            Console.WriteLine("Hayvan *brr* dedi");
        }
    }

ve çalıştıralım

Bir başka yöntem de ana sınıfı abstract tanımlayıp metodu da abstract tanımlamak. Ancak abtract metod tanımında kod bloğu olmaz. Şöyle ki,

    abstract class Hayvan
    {
        public abstract void Konuş();
    }

Ama genel eğilim virtual kelimesi kullanmaktır. Alt sınıfta metodun üzerine yazılmazsa ana sınıftaki metod geçerli olacaktır. Bir örnek olarak sınıf tanımlarımızda yapmadığımız ama sistem tarafından sınıflara otomatik eklenen ToString() metodu da üzerine yazılabilen bir metoddur ve istersek bu metodun yaptığı işi değiştirebiliriz. 

    class Kedi : Hayvan
    {
        public override void Konuş()
        {
            Console.WriteLine("Kedi *miyav* dedi");
        }
        public override string ToString()
        {
            return "Kedi *miyav* dedi";
        }
    }

Artık şunu da kullanabiliriz,

            Console.WriteLine(kedi.ToString());

ya da kısaca

            Console.WriteLine(kedi);




C# ToString() Metodu

ToString() metodu bütün sınıfların en büyük atası olan Object sınıfında tanımlanmış ve kalıtım yoluyla tüm üretilen sınıflarda da olacak olan bir metoddur. Temsil ettiği nesneyi yazılabilir bir String değer ile ifade etme görevi vardır. Örnek olarak eskilere dönelim, Araç adında bir sınıf tanımımız olsun.

    class Araç
    {
        public String marka;
        public String model;
        public int yıl;
        public String renk;
        public Araç(String marka, String model, int yıl, String renk)
        {
            this.marka = marka;
            this.model = model;
            this.yıl = yıl;
            this.renk = renk;
        }
    }

Ana programda bir araç nesnesi üretip ToString() metodu ile nesnemizi konsola yazdıralım.

        static void Main(string[] args)
        {
            Araç araç = new Araç("Chevy", "Corvette", 2002, "Mavi");

            Console.WriteLine(araç);

            Console.ReadKey();
        }

Console.WriteLine() metoduna bir nesneyi parametre olarak verirsek o nesnenin ToString() metodunun çıktısını yazar. Bu kodu çalıştırınca

Nesnemizin veri tipini elde ettik, çünkü default bu şekilde çalışıyor. Şimdi ToString() metodu üzerine yazarak tekrar tanımlayalım.

    class Araç
    {
        public String marka;
        ....
        public Araç(String marka, String model, int yıl, String renk)
        {
            ....
        }
        public override string ToString()
        {
            return $"Bu araç {this.renk} bir {this.yıl} {this.marka} {this.model}";
        }
    }

Ya da kısaca 

        public override string ToString()
        {
            return $"Bu araç {renk} bir {yıl} {marka} {model}";
        }

diyebiliriz. Çünkü üretici metod tanımındaki gibi aynı isimde başka değişken olmayınca this kelimesi ile ayrıca belirtmeye gerek kalmaz. Şimdi çalıştıralım

Gördüğümüz gibi Console.WriteLine() metodu her değer için akıllı bir nasıl yazacağı algoritmasına falan sahip değil. O nesnenin ToString() metodunu çağırıyor ve ondan dönen değeri yazıyor. Ben de sandım içinde double için ayrı, int için ayrı vs rutinler var, meğer bu kadar basitmiş. 




C# Polymorphism (çok şekillilik)

Buna bir Türkçe kelimeyi tam yapıştıramadım. Bir sınıf birden fazla sınıfın özelliklerini taşıyabilir. Gerçek hayattan örnek vermek gerekirse, bir Köpek aynı anda hem Hayvan, hem Kaniş hem de Organizma sınıflarının özelliklerine sahip olabilir. Daha önce bir Araç ana sınıfından alt sınıf olarak Otomobil , Bisiklet ve Tekne alt sınıfları üretmiştik. 

using System;

namespace CSharpTemelleri
{
    class Program
    {
        static void Main(string[] args)
        {
           

            Console.ReadKey();
        }
    }
    class Araç
    {

    }
    class Otomobil : Araç
    {

    }
    class Bisiklet : Araç
    {

    }
    class Tekne : Araç
    {

    }
}

Farz edelim bir yarış düzenleyeceğiz ve her 3 türden de bir aracımız var.

        static void Main(string[] args)
        {
            Otomobil otomobil = new Otomobil();
            Bisiklet bisiklet = new Bisiklet();
            Tekne tekne = new Tekne();

            Console.ReadKey();
        }

Bu 3 nesneyi tek bir array içinde yarışa katılanlar olarak ifade etmek istersek, hepsini Otomobil türünde bir array ile gösteremeyiz hata verecektir.

Ancak polymorphism tekniğine göre hepsinin ortak olarak üretildiği Araç sınıfından yararlanarak bu araçları aynı array içinde toplamamız mümkün olur. 

            Araç[] yarışanlar = { otomobil, bisiklet, tekne };

Şeklinde yazarsak sorun ortadan kalkacaktır. Farklı sınıfları birleştirmek için ortak olan taraflarını kullanabiliriz. Elmalar ile armutları toplayamayız ama bir tabakta 5 elma ve 3 armut varsa tabakta 8 meyve var diyebiliriz. 

Şimdi yarışa katılan her aracın Git() adında bir metodu olsun ve yarış başlatmak için bu metodları çağıralım.

    class Otomobil : Araç
    {
        public void Git()
        {
            Console.WriteLine("Otomobil hareket ediyor");
        }
    }
    class Bisiklet : Araç
    {
        public void Git()
        {
            Console.WriteLine("Bisiklet hareket ediyor");
        }
    }
    class Tekne : Araç
    {
        public void Git()
        {
            Console.WriteLine("Tekne hareket ediyor");
        }
    }

Şimdi yarışı başlatmak için bir foreach döngüsü içinde bu metodları çağıralım.

            Araç[] yarışanlar = { otomobil, bisiklet, tekne };

            foreach(Araç araç in yarışanlar)
            {
                araç.Git();
            }

Burada bir eksik var Araç ana sınıfı içinde Git() metodu tanımlamadığımız için derleyici ne yapmak istediğimizi anlayamaz. Ana metodda tanımlanan bir metodun alt sınıf tanımlarında nasıl üzerine tekrar tanımlanabileceğini görmüştük, virtual ve override kelimeleri kullanarak. 

    class Araç
    {
        public virtual void Git()
        {

        }
    }
    class Otomobil : Araç
    {
        public override void Git()
        {
            Console.WriteLine("Otomobil hareket ediyor");
        }
    }
    class Bisiklet : Araç
    {
        public override void Git()
        {
            Console.WriteLine("Bisiklet hareket ediyor");
        }
    }
    class Tekne : Araç
    {
        public override void Git()
        {
            Console.WriteLine("Tekne hareket ediyor");
        }
    }

Artık sorun kalmadı, programı çalıştırabiliriz.

Hiç bir ortak noktaları olmasa bile tüm sınıflar Object sınıfınnın mirasçıları olduğu için 

            Object[] yarışanlar = { otomobil, bisiklet, tekne };

Şeklinde bir yazım da çalışır, ama zorunda olmadıkça kullanılmasını tavsiye etmem, ortak metodlar ve özellikler sorun olabilir. 





C# Sınıflarda Arayüz Şeklinde Tanımlama (interface)

Bir sınıf tanımı yaparken arayüz şeklinde tanımlama bir nevi kontrat yapmak gibidir. Tanımlanan bir arayüz ondan üretilen alt sınıfların nelere sahip olması gerektiğini belirler. Alt sınıfların tanımında da bu sahip olunması gerekenlerin nasıl olduğu belirtilir. 

Örnek olarak 3 sınıfımız var, Tavşan , Şahin ve Balık. 2 tane de arayüz tanımlayacağız av ve avcı. Arayüz tanımlarında interface kelimesi kullanılır ve arayüze isim verirken başına büyük I harfi konulması adettendir. 

    class Program
    {
        static void Main(string[] args)
        {
           

            Console.ReadKey();
        }
    }
    interface IAv
    {

    }
    interface IAvcı
    {

    }

Bu arayüzler temelinde kurulan sınıflar bu arayüzler içinde belirtilen özellik ve metod tanımlarına sahip olmak zorundadır. Mesela IAv arayüzünde bir sınıfın av olduğu için avcıdan kaçması için Kaç() adında bir metodu olmasını istiyoruz. 

    interface IAv
    {
        void Kaç();
    }

Sadece tanımlıyoruz ama kod bloğunu girmiyoruz. Bu arayüzü kullanan Tavşan sınıfımızı tanımlarsak bu metodun tanımı ve kodunu yazmak zorundayız. Arayüzü kullanan sınıf üretirken aynı ana sınıftan alt sınıf tanımlar gibi iki nokta üst üste işaretinden yararlanırız.

    class Tavşan : IAv
    {
        public void Kaç()
        {
            Console.WriteLine("Tavşan kaçıyor");
        }
    }

Şimdi bir tavşan nesnesi üretip kullanabiliriz.

        static void Main(string[] args)
        {
            Tavşan tavşan = new();

            tavşan.Kaç();

            Console.ReadKey();
        }

Avcı arayüzümüzde de Avla() adında bir metod zorunluluğu getirelim.

    interface IAvcı
    {
        void Avla();
    }

IAvcı arayüzünden üreteceğimiz Şahin sınıfı tanımında Avla() metodunu tanımlamak zorundayız.

    class Şahin : IAvcı
    {
        public void Avla()
        {
            Console.WriteLine("Şahin yiyecek arıyor");
        }
    }

Şimdi Şahin sınıfından da bir nesne üretip Avla() metodunu kullanalım.

            Tavşan tavşan = new();
            Şahin şahin = new();

            tavşan.Kaç();
            şahin.Avla();

çalıştırırsak,

Okey. 

Arayüz kullandığımızda birden fazla arayüzün kurallarına zorlanan bir sınıf da tanımlayabiliriz. Mesela Balık sınıfı hem av hem avcı olabilir. Bu durumda hem IAv hem de IAvcı arayüzlerinde belirtilenlere sahip olması gerekir. Birden fazla arayüz kullanan sınıflarda iki nokta üst üste işaretinden sonra arayüzlerin isimlerini aralarına virgül koyarak gireriz. 

    class Balık : IAv, IAvcı
    {
        public void Kaç()
        {
            Console.WriteLine("Balık yüzerek kaçıyor");
        }
        public void Avla()
        {
            Console.WriteLine("Balık daha küçük balıkları arıyor");
        }

    }

Her 2 arayüzde dayatılan metodları tanımlamak zorundayız. Hadi bir Balık nesnesi oluşturup kullanalım.

        static void Main(string[] args)
        {
            Balık balık = new();

            balık.Kaç();
            balık.Avla();

            Console.ReadKey();
        }

çalıştıralım,





C# Liste Kullanmak

Sınıf yapılarında çok dolaştık biraz veri yapılarına geri dönelim. List yapılar içlerinde nesne dizileri barındıran veri saklama yapılarıdır. Çalışma olarak biraz array yapılara benzerler. Benzerlik ve farklarını şimdi göreceğiz. array'lerden en önemli farkı program çalışırken boyutları (yani içlerinde barındırdıkları eleman sayısı) arttırılabilir ya da azaltılabilir. Hatırlarsak array'ler sabit eleman sayısında oluyordu. 

Hatırlamak için bir örnek verelim.

        static void Main(string[] args)
        {
            String[] yemekler = new String[3];
            yemekler[0] = "Pizza";
            yemekler[1] = "Hamburger";
            yemekler[2] = "Hotdog";

            foreach(String yemek in yemekler)
            {
                Console.WriteLine(yemek);
            }

            Console.ReadKey();
        }

Programda yemekler array'ine yeni bir eleman eklemeye kalkarsak, mesela,

            yemekler[3] = "Patates Kızartması";

Bu satır derlemede hata vermez ama programı çalıştırırsak çakılır ve IndexOutOfRangeException istisnai durumu oluşur. Çünkü array ilk tanımlanırken boyutu 3 olarak verildi ama 4üncü bir eleman değeri vermeye çalışıyoruz. 

Yazacağımız programlarda verilerimiz program çalıştıkça artar ya da azalır. Bu durumlarda array yapısı pek işimizi görmez. Bu durumlarda List kullanarak işimizi hallederiz. 

List kullanmaya başlamadan önce derleyiciye List yapısını da içinde barındıran System.Collections.Generic kütüphanesini de programımıza dahil etmesi için bir using satırını en üste eklemeliyiz.

using System;
using System.Collections.Generic;

namespace CSharpTemelleri
...

Şimdi List yapısını programımızda kullanabiliriz. Az önceki örneği List ile yapalım. Tanımlama şöyle olacak,

        static void Main(string[] args)
        {
            List<String> yemekler = new List<String>();

            Console.ReadKey();
        }

List kelimesi ve hemen yanında küçük-büyük işaretleri arasında (aslında bu işaretler arasında olan şeye açılı parantez içinde denir) listemizin içerdiği nesnelerin sınıf adını yazıyoruz. Eşitliğin sağ tarafı da aynı bir sınıftan nesne üretirken yaptığımız gibi sonunda parantez aç-kapa ekliyoruz. 

Daha önce nesne üretirken kullandığımız 

            List<String> yemekler = new();

şeklinde kısaltılmış yazım burada da geçerli tabi ki. 

List'lere yeni eleman eklemek için sahip oldukları Add() metodunu kullanırız.

            List<String> yemekler = new();
            yemekler.Add("Pizza");
            yemekler.Add("Hamburger");
            yemekler.Add("Hotdog");

            foreach (String yemek in yemekler)
            {
                Console.WriteLine(yemek);
            }

Add() metoduna parametre olarak listeye eklemek istediğimiz nesneyi veriyoruz. Örneğimizde elemanlar String olduğu için Add() metoduna da parametre olarak String değerlerimizi veriyoruz. Bir çalıştırıp görelim.

foreach döngüsü aynı array'lerde olduğu gibi çalışıyor, döngü kodunu aynen kullanıyoruz. Artık program çalışması esnasında yemekler listesine yeni eleman ekleyebiliriz. 

            yemekler.Add("Patates Kızartması");
            Console.WriteLine("*** 4üncü eleman eklenmiş hali ***");

            foreach (String yemek in yemekler)
            {
                Console.WriteLine(yemek);
            }

Elemanlardan herhangi birine erişmek aynı array'lerde olduğu gibi köşeli parantez içinde index numarası vererek yapılır. Add() metodunu her kullandığımızda eklenen eleman listenin sonuna eklenir. Buna göre mesela 

            Console.WriteLine(yemekler[2]);

dediğimizde konsola "Hotdog" değeri yazılacaktır. Aynı notasyon ile değer de değiştirilebilir.

            yemekler[2] = "Duble Hotdog";

List sınıfının metodlarından biri de Remove() metodudur ve bir elemanı listeden silmek için kullanılır.. Parametre olarak silmek istediğimiz değeri veririz. 

            List<String> yemekler = new();
            yemekler.Add("Pizza");
            yemekler.Add("Hamburger");
            yemekler.Add("Hotdog");
            yemekler.Add("Patates Kızartması");

            Console.WriteLine(yemekler[2]);
            yemekler.Remove("Hotdog");
            Console.WriteLine(yemekler[2]);

çalıştırırsak

Index 2'deki Hotdog değeri silinince Index 3'te olan Patates Kızartması değeri Index 2'ye geçer. Yani silinen elemanın yarattığı boşluğa ondan sonraki elemanlar kayarak liste küçülür. Remove() metodu taramaya Index sıfırdan başlar ve ilk bulduğu eşleşmedeki elemanı siler. Mesela 2 tane elemanın değeri Hotdog olsun,

            List<String> yemekler = new();
            yemekler.Add("Pizza");
            yemekler.Add("Hamburger");
            yemekler.Add("Hotdog");
            yemekler.Add("Patates Kızartması");
            yemekler.Add("Hotdog");

            yemekler.Remove("Hotdog");

            foreach (String yemek in yemekler)
            {
                Console.WriteLine(yemek);
            }

Bu kodu çalıştırırsak Remove() metodu ilk eşleşmeyi silecektir.

İstediğimiz index'te bir eleman eklemek istersek Insert() metodu ile yaparız. 

            List<String> yemekler = new();
            yemekler.Add("Pizza");
            yemekler.Add("Hamburger");
            yemekler.Add("Hotdog");
            yemekler.Add("Patates Kızartması");

            yemekler.Insert(0, "Suşi");

Bu kod index sıfır yani en başa Suşi değerini ekler.

Insert() metodu kullanıldığında verilen index değeri ve daha büyük index değerli tüm elemanların index değerleri bir artacak ve böylece kaydırılacaktır. 

List'teki eleman sayısını öğrenmek için Count özelliğini kullanabiliriz.

            Console.WriteLine(yemekler.Count);

satırı konsola yemekler listesinin eleman sayısı olan 4 yazacaktır. 

Bir değerin List içinde hangi index değerinde olduğunu bilmek için IndexOf() metodunu kullanırız.

            Console.WriteLine(yemekler.IndexOf("Hotdog"));

satırı bize Hotdog değerinin index'i olan 2'yi verecektir. Ama olmayan bir değer sorgularsak, mesela:

            Console.WriteLine(yemekler.IndexOf("Duble Hotdog"));

satırı listede bu değeri bulamadığı için -1 değeri dönecektir. IndexOf() metodu index sıfırdan başlayıp bize ilk eşleşen değerin index'ini verir. Sonuncuyu bulmak için de LastIndexOf() metodu vardır,

            Console.WriteLine(yemekler.LastIndexOf("Hotdog"));

gibi. 

Bir değerin listede olup olmadığını test etmek için Contains() metodu kullanılır. true ya da false değer döner.

            Console.WriteLine(yemekler.Contains("Pizza"));

satırı konsola True yazacaktır. 

Listedekileri sıralamak için Sort() metodu kullanırız.

            yemekler.Sort();

satırı listedeki elemanları harf sırasına göre sıralar.

Default olarak String değerler a dan z ye, sayılar 0 dan 9 a, tarihler eskiden yeniye sıralanır. Sıralama kriterleri ayrıca belirtilebilir ancak bu daha ileri bir kurs konusu. 

List elemanlarının sırasını ters çevirmek için Reverse() metodu kullanılır.

            yemekler.Reverse();

satırını çalıştırırsak,

Tüm elemanları silmek için Clear() metodu kullanılır.

            yemekler.Clear();

tüm liste elemanlarını silecektir. İlk tanımladığımız andaki gibi boş bir liste olacaktır.

Son örnek olarak List elemanı array elemana dönüştürebiliriz.

            String[] yemekArray = yemekler.ToArray();

            foreach (String yemek in yemekArray)
            {
                Console.WriteLine(yemek);
            }

ToArray() metodu List türünü array türüne dönüştürmek için kullanılır.




C# Elemanları Kullanıcı Tanımlı Nesneler Olan List

Listeleri kendi tanımladığımız sınıflardan oluşturulan nesnelerin koleksiyonu olarak da kullanabiliriz. Örnek olarak bir oyunumuz olduğunu ve katılan oyuncuların bilgilerini bir listede toplamak amacıyla bir Oyuncu sınıfı tanımladığımızı düşünelim.

    class Program
    {
        static void Main(string[] args)
        {
           

            Console.ReadKey();
        }
    }
    class Oyuncu
    {
        public String isim;
        public Oyuncu(String isim)
        {
            this.isim = isim;
        }
    }

Şimdi bir liste oluşturup oyuncuları üretelim.

        static void Main(string[] args)
        {
            List<Oyuncu> oyuncular = new List<Oyuncu>();

            Oyuncu oyuncu1 = new("Ümit");
            Oyuncu oyuncu2 = new("Dilek");
            Oyuncu oyuncu3 = new("Hasan");

            Console.ReadKey();
        }

ve bu oyuncuları listeye ekleyelim

 ...
          oyuncular.Add(oyuncu1);
            oyuncular.Add(oyuncu2);
            oyuncular.Add(oyuncu3);

            Console.ReadKey();

bir foreach döngüsü ile katılan oyuncuların isimlerini konsola yazdıralım.

            foreach(Oyuncu oyuncu in oyuncular)
            {
                Console.WriteLine(oyuncu.isim);
            }

dilersek Oyuncu sınıfı ToString() metoduna müdahale ederek default olarak oyuncu adının yazılmasını sağlayabiliriz.

    class Oyuncu
    {
        public String isim;
        public Oyuncu(String isim)
        {
            this.isim = isim;
        }
        public override string ToString()
        {
            return isim;
        }
    }

artık konsola nesneyi direk yazdırırsak ismini yazacaktır.

            foreach(Oyuncu oyuncu in oyuncular)
            {
                Console.WriteLine(oyuncu);
            }

Oyuncu nesnelerini tek tek tanımlamaya gerek yok listeye eklerken anonim şekilde yeni nesne üreterek de ekleyebiliriz.

            List<Oyuncu> oyuncular = new List<Oyuncu>();

            oyuncular.Add(new("Ümit"));
            oyuncular.Add(new("Dilek"));
            oyuncular.Add(new("Hasan"));

Bu kadar. Kendi tanımladığımız sınıftan nesnelerle oluşturulmuş bir liste için yapmamız gereken açılı parantez içine sınıf adımızı yazmak.



C# Sınıf Tanımı get - set İşlemleri (getters and setters)

Sınıf tanımlarımızda özellik değerleri girerken bazen değerin belli kurallara uymasını isteriz. Örneğin bir Araç sınıfımız ve hız özelliği olsun. 

    class Program
    {
        static void Main(string[] args)
        {
            Araç araç = new Araç(400);

            Console.ReadKey();
        }
    }
    class Araç
    {
        public int hız;
        public Araç(int hız)
        {
            this.hız = hız;
        }
    }

hız değeri olarak integer olduğu için çok afaki değerler hatta eksi değerler bile verilebilir. Mesela yanlışlıkla

            Araç araç = new Araç(400);

            araç.hız = 100000;

Yok daha neler ama bu değer geçerli bir değer, bir şekilde olası olmayan değer girişini engellemeliyiz.

İkinci satırdaki nesnenin özelliğine direk erişimi kapatabiliriz. 

    class Araç
    {
        private int hız;
        public Araç(int hız)
        {
            this.hız = hız;
        }
    }

hız özelliğini private olarak tanımladığımız için sadece sınıf içindeki metodlar ona erişebilir. Ama hala ilk nesne üretimindeki yapılacak yanlış duruyor. Mesela,

            Araç araç = new Araç(100000);

yazılabilir. Bunu veya benzer kısıtlamaları yapabilmek için özellik değerlerine erişimde araya girip işlem yapan get ve set metodları kullanırız. 

Biz şimdiye kadar sınıf özellikleri belirlerken hep sınıf içinde geçerli değişkenler tanımladık aslında. Esas olarak bir sınıf tanımında özellik belirtmesi metod tanımlamasına benzer ve bu tanımlama içinde özellik değeri okunurken ve değere yazılırken ayrı işlem blokları belirtilir. Özellik değeri okunmak istendiğinde get bloğu çalışır ve bir değer döner. Özellik değeri değiştirilmek istendiğinde ise set bloğu çalışır ve dışarıdan gönderilen değeri işleyerek özelliğe yazar.

Bir de özellik isimleri büyük harfle başlar. Şimdi hız değerimiz için bir özellik tanımlaması yapalım.

    class Araç
    {
        private int hız;
        public Araç(int hız)
        {
            Hız= hız;
        }
        public int Hız
        {
            get     //okuma bloğu
            {
                return hız;
            }
            set     //yazma bloğu
            {
                hız = value;
            }
        }
    }

Metod tanımlar gibi başlıyoruz ama özellik adı olan Hız kelimesinden sonra parantezler yok. Bu durumda bu bir sınıf özelliği olarak değerlendirilir. Bu aslında default özellik tanımlama şekli. get ve set blokları da aslında özelliğin değerini okurken ya da yazarken kullanılan bloklardır ve dikkat edersek sadece isimleri ve arkasından blokları var parantez falan yok. Bunlar aslında metod değil setter ve getter blokları olarak bilinir. Şöyle biraz daha topluca gösterelim,

        private int hız;
        public int Hız
        {
            get { return hız; }
            set { hız = value; }
        }

Önce sınıf içinde dışarıdan erişilemeyecek hız değişkenini private olarak tanımlıyoruz. Hız özelliğini ise standart sınıf özelliği olarak tanımlıyoruz. Hız değerine ulaşmak isteyen her kod eğer Hız değerini okumak istiyorsa get bloğunu , eğer Hız özelliğine bir değer vermek istiyorsa set bloğunu otomatik olarak çağıracaktır. 

Hatta ne yaptık sınıf üretici metodunda?

        public Araç(int hız)
        {
            Hız= hız;
        }

private olan hız değişkenine yazmıyoruz özellik olan Hız değerine yazıyoruz. Bu demektir ki yeni nesne üretirken de set bloğu çalışacak. 

Nasıl çalışıyor kısaca bakalım,

            get { return hız; }

Burada deniyor ki Hız özelliği değeri okunmak istendiğinde nesne içinde kullandığım hız değişkeni değerini gönder. 

            set { hız = value; }

Burada da deniyor ki dışarıdan Hız özelliğine değer girilmek istenirse o girilen değeri hız dahili değişkenine yaz. value kelimesi burada dışarıdan girilen değeri ifade eder. Yani 

            araç.Hız = 120;

yazdığımızda value değeri 120 olarak set bloğu işletilir. 

            Araç araç = new Araç(100);

yazdığımızda da value değeri 100 olarak set bloğu işletilir. 

get ve set bloklarında şu anda hiç bir işlem yapmıyoruz. Sadece standart bir sınıf özelliği tanımı yaptık. Eğer özel bir işlem yapmayacaksak blokları ayrıca belirtmeye gerek yok. Kısaca şöyle yazılabilir.

    class Araç
    {
        public int Hız
        {
            get;
            set;
        }
        public Araç(int hız)
        {
            Hız = hız;
        }
    }

Ama biz değer girerken işlemler yapacağız , bu yüzden bu notasyonu kullanmayacağız. 

Devam etmeden bir konuya daha değineyim. Özellik tanım bloğunda sadece get işlem bloğu tanımlarsak set bloğu tanımlamazsak (ve set kelimesini hiç kullanmazsak), bu özelliğin sadece okunabilir olduğunu dışarıdan değiştirilemeyeceğini gösterir. Neye yarar derseniz String değerler için olan Length özelliğini gösterebilirim. Bu değer sadece okunabilir ve String değerin kaç karakter uzunlukta olduğunu bize bildirir. Yani Length değeri okunmaya çalışılınca sınıf tanımı içindeki get bloğu String değerin kaç karakter olduğunu hesaplayıp bize döner. 

Örneğimize dönelim. Hız değeri girilirken maksimum girilebilecek değeri 500 olarak sınırlamak istersek,

        public int Hız
        {
            get { return hız; }
            set
            {
                if (value > 500)
                {
                    hız = 500;
                }
                else
                {
                    hız = value;
                }
            }
        }

if bloklarında tek satır olduğu için süslü parantez içinde göstermeye gerek yok kısaca,

            set
            {
                if (value > 500) hız = 500;
                else hız = value;
            }

yazabiliriz ya da üçlü operatör ile

            set
            {
                hız = (value > 500) ? 500 : value;
            }

Bunları unutmayın sık sık kullanın ki kod görünümü daha basit olsun. 




C# get - set Erişimcilerinin (accessor) Kısa Kullanımı

Bundan sonra sınıf tanımlarında bir özellik belirtirken öyle public değişken kullanmak yok , standart yapıları kullanacağız. Yukarıda kısaca get - set erişimcilerinin kısa kullanımından bahsettik. Şimdi bir örnekle bilgimizi pekiştirelim. Bir Araç sınıfı tanımımız ve Model özelliği olsun,

    class Araç
    {
        private String model;
        public String Model
        {
            get { return model; }
            set { model = value; }
        }
        public Araç(String model)
        {
            Model = model;
        }
    }

get ve set erişimcileri içinde standart dışı bir şey yapmayacaksak bu kodu çok daha kısa yazabiliriz.

    class Araç
    {
        public String Model { get; set; }
        public Araç(String model)
        {
            Model = model;
        }
    }

Bu kod yukarıdaki ile aynı işi yapar ama çok daha kısa. Kısa bir programla test edelim.

        static void Main(string[] args)
        {
            Araç araç = new Araç("Corvette");
            Console.WriteLine(araç.Model);

            Console.ReadKey();
        }

ve çalıştıralım

Sıkıntı yok. Model özelliğinin yalnızca okunabilir olmasını istersek,

        public String Model { get;  }

yazarız ve set erişimcisi olmadığı için Model özellik değeri değiştirilemeyecektir. Ancak programı tekrar çalıştırırsak hala sağlıklı çalıştığını görürüz. Bunun sebebi sınıf üretici metodunun tanımı yapılmasa da özellik set edebilmesidir. Ama dışardan nesne özelliğini değiştirmeye kalkarsak , mesela

            Araç araç = new Araç("Corvette");
            Console.WriteLine(araç.Model);
            araç.Model = "Mustang";

Son satır hata verecek ve Model özelliğinin salt okunur bir özellik olduğunu belirtecektir. Yani sadece yeni bir nesne üretirken Model girmek zorundayız ve sonradan bu değer sadece okunabilir, değiştirilemez. 




C# enum kullanımı (enumerations)

enum özel bir sınıf tanımıdır. İçinde isimlendirilmiş sabit tamsayı değerler barındırır. Bir şeyleri sayılarla ifade edeceksek , mesela mezuniyet bilgisi, 1:okumamış, 2:ilkokul, 3:ortaokul, 4:lise vs. Daha sonradan 1 neydi 2 neydi diye hatırlamaya çalışmak yerine kullanılabilecek yapılardır. Bir örnekle açıklarsak daha anlaşılır olacak. Gezegen isimlerini içeren bir enum tanımlaması yapalım.

    class Program
    {
        static void Main(string[] args)
        {
           

            Console.ReadKey();
        }
    }
    enum Gezegenler
    {
        Merkür, Venüs, Dünya, Mars, Jüpiter, Satürn, Uranüs, Neptün, Plüton
    }

İsimlerin bir String gibi tırnak içinde olmadığına dikkat edin. Sınıf tanımı gibi enum tanımlarını da çalıştığımız sınıfın dışında ama namespace içinde yapıyoruz. Özellikle belirtilmediği sürece ilk elemanın (Merkür) karşılığı olan tamsayı sıfır sonraki 1 vs şeklinde gider. Şimdi bu enum değerleri kullanalım

        static void Main(string[] args)
        {
            Console.WriteLine(Gezegenler.Plüton + " bir gezegendir");

            Console.ReadKey();
        }

ve sonuç

Şimdi bunu bir array içinde yazmış olsak

            Console.WriteLine(Gezegenler[8] + " bir gezegendir");

Gibi bir şey yazmamız gerekecek ve acaba Plüton 7 miydi? 9 muydu? falan deyip her seferinde tanımın yapıldığı yere dönecektik halbuki Gezegenler kelimesi yanına noktayı koyduğumuz anda editör bize hemen tüm değerleri gösterip yardımcı oluyor. Değerlerin tamsayı karşılıklarını kullanmak ise ileride enum yapıları kullanırken çok gerekecek bir şey. Tamsayı karşılık gelen değerleri almak için basit yerinde dönüştürme yaparız. Örnek

            Console.WriteLine(Gezegenler.Merkür +" gezegen numarası #" +
                (int)Gezegenler.Merkür);

 ve çıktısı,

Biz bu otomatik numaraları sevmedik kendi sayılarımızı girelim dersek,

    enum Gezegenler
    {
        Merkür = 1,
        Venüs = 2,
        Dünya = 3,
        Mars = 4,
        Jüpiter = 5,
        Satürn = 6,
        Uranüs = 7,
        Neptün = 8,
        Plüton = 9
    }

Şeklinde kendi istediğimiz numaraları da verebiliriz. Şimdi çalıştırırsak 

Bu sayılar bambaşka olabilir mesela gezegenlerin çapları yazıyor olabilir.

    enum GezegenÇapları
    {
        Merkür = 2439,
        Venüs = 6051,
        Dünya = 6371,
        Mars = 3389,
        Jüpiter = 69911,
        Satürn = 58232,
        Uranüs = 25362,
        Neptün = 24622,
        Plüton = 1188
    }

Şimdi bu değerleri kullanalım

            String isim = GezegenÇapları.Dünya.ToString();
            int çap = (int)GezegenÇapları.Dünya;

            Console.WriteLine("gezegen: " + isim);
            Console.WriteLine("çap: " + çap);

En üstte enum değerin ToString() metodunun kullanıldığına dikkat edelim. Bu değerler String değil konsola yazdırırken otomatik olarak ToString() metodu çağrılıyor, ama String bir değişkene değer vereceksek ToString() metodunu kendimiz çağırmalıyız. 

Gördüğümüz üzere hatırlamakta zorlanacağımız sayıları da böyle isimlendirebiliriz. Örneğimizi geliştirelim ve bir gezegenin hacmini hesaplayan bir metod yazalım.

        public static double Hacim(GezegenÇapları çap)
        {
            double hacim;
            hacim = (4.0 / 3.0) * Math.PI * Math.Pow((int)çap, 3);
            return hacim;
        }

çap değerinin küpünü almadan önce tamsayı karşılığı için int değere çevirmeliyiz. Dikkat edersek (4/3) değil (4.0/3.0) yazdık. Noktalı şekilde yazmazsak parantez içini hesaplarken tamsayı olarak bölme yapacaktı, ayrıca belirtmek lazım. Şimdi bu metodu da kullanalım,

            String isim = GezegenÇapları.Dünya.ToString();
            int çap = (int)GezegenÇapları.Dünya;
            double hacim = Hacim(GezegenÇapları.Dünya);

            Console.WriteLine("gezegen: " + isim);
            Console.WriteLine("çap: " + çap);
            Console.WriteLine("hacim: " + hacim);





C# Jenerik Değerler (generic)

Jenerik değerler tek bir tür veriye bağlı olmayan içlerinde değişik veri tiplerini barındırabilen değerlerdir. Jenerik sınıflar, jenerik metodlar ve jenerik sınıf özellikleri olabilir. İsimlere açılı parantez içinde büyük T harfi ekleyerek (<T>) ifade edilirler. 3 değişik veri tipinde elemanlar barındıran array tanımları ile örnek verelim.

        static void Main(string[] args)
        {
            int[] intArray = { 1, 2, 3 };
            double[] doubleArray = { 1.0, 2.0, 3.0 };
            String[] stringArray = { "1", "2", "3" };

            Console.ReadKey();
        }

Bu array'lerin eleman değerlerini konsola yazdıracak bir metod tanımlamak isteyelim. Örneğin int değerler için şöyle yazarız,

        public static void ElemanlarıGöster(int[] array)
        {
            foreach (int eleman in array)
            {
                Console.WriteLine(eleman);
            }
        }

Bu metodu intArray değişkeni için kullanabiliriz.

            int[] intArray = { 1, 2, 3 };
            double[] doubleArray = { 1.0, 2.0, 3.0 };
            String[] stringArray = { "1", "2", "3" };

            ElemanlarıGöster(intArray);

Fakat aynı metodu double ve String değerli elemanlar içeren array'ler için kullanamayız. Şimdiye kadar öğrendiklerimize bakarsak tamamen yeni metodları bu sefer double ya da String değerleri işleyecek şekilde tanımlamak zorundayız. 

Aynı isimde metodlar tanımlıyarak bunu yapabileceğimizi gördük.

        public static void ElemanlarıGöster(int[] array)
        {
            foreach (int eleman in array)
            {
                Console.WriteLine(eleman);
            }
        }
        public static void ElemanlarıGöster(String[] array)
        {
            foreach (String eleman in array)
            {
                Console.WriteLine(eleman);
            }
        }
        public static void ElemanlarıGöster(double[] array)
        {
            foreach (double eleman in array)
            {
                Console.WriteLine(eleman);
            }
        }

Değişik tip parametreler ile çalışabilen 3 metod tanımı yaptık bir-iki fark dışında neredeyse aynı kodlar var. Bu kodu jenerik değerler kabul eden bir metod tanımı ile kısaltabiliriz.

        public static void ElemanlarıGöster<T>(T[] array)
        {
            foreach (T eleman in array)
            {
                Console.WriteLine(eleman);
            }
        }

Notasyondaki T harflerine dikkat edelim. Burada T harfi kullanma zorunluluğu yok, yeterki her kullanıldığı yere aynı şeyi yazalım. Yani 

        public static void ElemanlarıGöster<bişey>(bişey[] array)
        {
            foreach (bişey eleman in array)
            {
                Console.WriteLine(eleman);
            }
        }

şeklinde de yazabiliriz. Metodu int değerler ile çağırınca o bişey yazısı yerine otomatik olarak int gelir ve metod ona göre çalışır, double değerler ile çağrılınca bişey yazılan yerlerde double yazıyormuş gibi işlem yapılır vs. Artık tek bir metod tanımı birçok veri tipini kabul edebiliyor. Sadece örnekteki veri tipleri değil array olan her veriyi kabul eder. Mesela 

            Char[] charArray = { '1', '2', '3' };

            ElemanlarıGöster(charArray);

Bu da çalışacaktır. Tek bir metod bir çok veri tipini kabul ediyor ve çalışıyor. 




C# Çoklu İş Parçacıkları (multi thread)

Bazen programımız bir olayların oluşmasını beklerken paralelde başka işleri yapmasını isteriz. Bu en çok zamanlanmış işler ve dışarıdan bir veri ya da etkileşim beklerken ihtiyaç duyduğumuz bir şeydir. Mesela dakikada bir yapacağımız bir iş var ve saniyede bir yapacağımız başka bir iş var. Biz dakikada bir yapılacak iş için program dakikanın bitmesini beklerken saniyede bir yapılması gerekenleri yapmaya devam etmesini isteriz. Bunu kodlar , döngüler vs ile halledebiliriz ama işler çoğaldıkça kodumuz çorbaya dönecektir.

Bu sorunları çözmek için C# iş parçacıkları (thread) tanımlamayı ve birden çok iş parçacığının paralel çalışmasını kullanmamıza imkan sunar. 

Aslında şimdiye kadar kullandığımız Main() metodu da bir iş parçacığıdır ve Main thread olarak adlandırılır. Yani şimdiye kadar yaptığımız örneklerde sadece tek bir iş parçacığı vardı o da Main bloğu. 

İş parçacıklarını kullanmak için önce en üstte bir using yönergesi ile belirtmeliyiz. 

using System;
using System.Threading;

Örnek olarak içinde bulunduğumuz iş parçacığını algılayıp şöyle isimlendirebiliriz. 

        static void Main(string[] args)
        {
            Thread mainThread = Thread.CurrentThread;
            mainThread.Name = "Ana iş parçacığı";
            Console.WriteLine(mainThread.Name);

            Console.ReadKey();
        }

Thread.CurrentThread özelliği bize programın o anda içinde bulunduğu iş parçacığı nesnesini verir. Daha sonra bu iş parçacığının Name özelliğine değer giriyor ve konsolda yazdırıyoruz. 

Çoklu iş parçacıklarına örnek olarak paralel çalışan 2 sayaç yapacağız biri sıfırdan 10'a yukarı doğru sayarken diğeri 10'dan sıfıra aşağı doğru sayacak. Öncelikle 2 metod tanımlayacağız ilki yukarı doğru sayan bir metod.

        public static void yukarıSay()
        {
            for (int i=0; i<=10; i++)
            {
                Console.WriteLine("Yukarı sayaç : " + i + " saniye");
                Thread.Sleep(1000);
            }
            Console.WriteLine("Yukarı sayım bitti.");
        }

Burada yeni olan tek şey Thread.Sleep(1000) satırı. Bu komut milisaniye olarak yani 1000 ms = 1 saniye o satırda bekliyor. Böylece metodun saniyede bir sayı sayması sağlanıyor. Aslında bekleme 1000 ms , konsola yazma ve döngüye gidip tekrar gelme zaman gecikmeleri de 2 ms desek 1002 ms de bir kod döngü yapar. Ama burada derdimiz bu küçük hata değil. "Mühendislik ihmaller sanatıdır" derdi hocalarımız. İşimi engellemeyecek kadar küçük yanlışları yok sayıp devam ederiz yolumuza. 

Bu metodu ana program içinde kullanırsak sağlıklı saydığını görürüz. 

        static void Main(string[] args)
        {
            yukarıSay();

            Console.ReadKey();
        }

Şimdi bir de aşağı sayan metod yazalım

        public static void aşağıSay()
        {
            for (int i = 10; i >= 0; i--)
            {
                Console.WriteLine("Aşağı sayaç : " + i + " saniye");
                Thread.Sleep(1000);
            }
            Console.WriteLine("Aşağı sayım bitti.");
        }

Bu metodu da ana program içinde kullanalım.

        static void Main(string[] args)
        {
            Thread mainThread = Thread.CurrentThread;
            mainThread.Name = "Ana iş parçacığı";

            yukarıSay();
            aşağıSay();

            Console.WriteLine(mainThread.Name + " tamamlandı");

            Console.ReadKey();
        }

Bu kodu çalıştırırsak önce sıfırdan 10'a kadar sayacak ve "Yukarı sayım bitti." yazacak , sonra 10'dan sıfıra aşağı doğru sayacak ve "Aşağı sayım bitti." yazacak en son da "Ana iş parçacığı tamamlandı" yazacaktır.

Ancak biz sayaçların aynı anda çalışmalarını istiyoruz. Bunu sağlamak için bu 2 metodu başka başka iş parçacıkları içinde çalıştırmalıyız. Bunu yapmak için 2 yeni iş parçacığı (Thread) üretiriz ve bunlara metodlarımızı işlemelerini emrederiz. 

            Thread mainThread = Thread.CurrentThread;
            mainThread.Name = "Ana iş parçacığı";

            Thread thread1 = new Thread(yukarıSay);
            Thread thread2 = new Thread(aşağıSay);
            thread1.Start();
            thread2.Start();

            Console.WriteLine(mainThread.Name + " tamamlandı");

İş parçacıklarının çalıştıracağı metodların ismini verirken parantez kullanmıyoruz sadece metod ismini yazıyoruz dikkat edelim. Eğer parantez kullanmaya kalkarsak program o ana gelince metodu çalıştırır ve metoddan dönen ne varsa onu parametre olarak new Thread() metoduna gönderir, halbuki biz metodu komple iş parçacığına gönderiyoruz. Böylece thread1.Start() ile iş parçacığı çalıştırılınca bizim metodumuz çalıştırılacaktır. 

Bu kodu çalıştırınca şu cevabı görürüz.

Neler oluyor kod üzerinden anlayalım. Anlamaya çalışırken şunu unutmayalım kodumuzu işleyen bir işlemci var. Bunu bir görevlinin bizim satırlarımıza bakıp orada denilenleri yaptığını düşünerek hayal edebiliriz.  Önce thread1 iş parçacığı tanımlanıyor ve yukarıSay metodu ona iliştiriliyor. Bu sadece bir tanımlama bu satırda metod çalıştırılmaz. Sonra aynı şekilde thread2 iş parçacığı tanımlanıyor ve ona da aşağıSay metodu iliştiriliyor. 

thread1.Start() komutunu görünce işlemci bulunulan iş parçacığından çıkar (mainThread iş parçacığından çıkar) ve thread1 iş parçacığında verilen yukarıSay metodunu çalıştırmaya başlar. Bu metod ilk satırını yazar ve Thread.Sleep(1000) satırına gelince metod bitmemiş olduğu halde iş parçacığı geçici olarak bırakılıp ana koda geri döner. 

Sırada thread2.Start() satırı var. Bu sefer işlemci bulunulan iş parçacığından çıkar (mainThread iş parçacığından çıkar) ve thread2 iş parçacığında verilen aşağıSay metodunu çalıştırmaya başlar. Bu metod da ilk satırını yazar ve Thread.Sleep(1000) satırına gelince metod bitmemiş olduğu halde iş parçacığı geçici olarak bırakılıp ana koda geri döner. 

Şimdi sırada Console.WriteLine(mainThread.Name + " tamamlandı") satırı vardır ve konsola "Ana iş parçacığı tamamlandı" yazar ve Console.ReadKey() satırına gelir. Buraya geldiğimize göre bir tuşa basarsak program sonlanacak diye düşünürüz ama içinde bulunduğumuz mainThread iş parçacığının bitmesi için onun içinde başlatılan iş parçacıklarının sonlanmasını bekler

Bu yüzden işlemci sırayla thread1 ve thread2 iş parçacıklarına dönerek kontrol yapar. Ne zaman ki Thread.Sleep(1000) ile beklenen 1000 ms dolar döngü sonraki adıma geçer. Bu işlem bir ona bir diğerine geçerek döngüler bitene ve bitiş mesajları da yazılıp metodlar sonlanana kadar devam eder. yukarıSay metodu çalıştırması bitince thread1 iş parçacığı sonlanır. aşağıSay metodu çalıştırması bitince de thread2 iş parçacığı sonlanır. İçinde başlatılan her 2 iş parçacığı da bitince mainThread iş parçacığı da artık bitebilir demektir. 

Denemek isterseniz sayma işlemi devam ederken bir tuşa basın. Sistemin tuşu aldığını fakat saymanın devam ettiğini göreceksiniz. Sayma işlemleri bittikten sonra önceden basmış olduğunuz tuş olduğu için program beklemeden hemen sonlanacaktır. 

Metodlar parametreli ise parantez kullanmadan nasıl çağıracağız? Sorusu akla takılmazsa olmaz. Bu durumda özel bir notasyon kullanacağız. Diyelim metodlarımız parametre olarak isim değeri istiyorlar.

        public static void yukarıSay(String isim)
        {
            ...
        }
        public static void aşağıSay(String isim)
        {
            ...
        }

Thread nesnesi tanımlamamız şöyle olmalıdır.

            Thread thread1 = new Thread(() => yukarıSay("Sayaç 1"));
            Thread thread2 = new Thread(() => aşağıSay("Sayaç 2"));
            thread1.Start();
            thread2.Start();

() => yukarıSay("Sayaç 1") komutu lambda operatör ile işlem tanımlama olarak bilinir..Şöyle düşünebiliriz, parametresi olmayan bir metod tanımlıyoruz ama o metod içinde yukarıSay("Sayaç 1") metodunu parametre ile çağırıyoruz. 

Biraz daha açalım

        public static void ys()
        {
            yukarıSay("Sayaç 1");
        }

şeklinde bir metod tanımlayıp bunu yukarıda 

            Thread thread1 = new Thread(ys);

şeklinde kullansak program çalışır. Lambda notasyonu ile metodu tanımlasak 

        public static void ys() => yukarıSay("Sayaç 1");

Şeklinde tanımlarız ve bu da çalışacaktır.Hiç metod tanımlamayıp direk parametre yerine 

            Thread thread1 = new Thread(() => yukarıSay("Sayaç 1"));

yazdığımızda da çalışacaktır. 

Bu yazı dizisi burada bitiyor. Yeni başlayacak olanlar ve başlangıç seviyeden biraz orta seviyelere çıkmak isteyenler için yaralı olacağını düşünerek bu yazı dizisini yazdım Umarım işinize yarar.

Sonraki yazılarda buluşmak ümidiyle kalın sağlıcakla..





Hiç yorum yok:

Yorum Gönder