C# Günlüğüm — Sayı #2

Merhabalar.

Bu yazımda ki konu başlıklarını zaten çoğu yazılımcı bilmekte. Fakat bir önceki yazımda da belirttiğim gibi, değindiğim konu başlıklarının sadece bana göre ilginç gelen yerlerini paylaşmaya özen gösteriyorum.

Bilinçsiz(Implicit) Tür Dönüşümü

Derleyici tarafından bir değişkeni tanımladığımız türün dışında geçici olarak başka türe çevirmeye bilinçsiz tür dönüşümü denir.

int s = 10;
float a;
a = s;

Not: Belirtilmedikçe bilinçsiz yapılan tür dönüşümlerinde bir nesnenin türü asla kalıcı olarak değiştirilemez!

Not: Büyük türler küçük türlere göre daha fazla bilgi içerdiklerinden küçük türler çoğu zaman büyük türlere dönüştürülebilir. Fakat büyük türler küçük türlere dönüştürülemezler. Yani büyük türlerin küçük türlere otomatik dönüştürülmesi C#’ ta yasaklanmıştır.

Bilinçli(Explicit) Tür Dönüşümü

Bilinçli tür dönüşüm genellikle derleyicinin izin vermediği dönüşümlerde yapılır. Bu tür dönüşümler veri kayıplarına sebeb olabildiği için dikkatli kullanılmalıdır.

Sakıncaları: Bilinçsiz tür dönüştürmelerinde büyük türler küçük türlere dönüştürülemiyordu. Fakat eğer tür dönüştürme operatörü kullanılarsak bu mümkündür.

int i = 10;
byte b = (byte)i;

Yukarıdaki programda daha büyük olan int türü byte türünden bir nesneye atanmıştır. Eğer i değişkeni 255 değerini geçiyorsa tür dönüşümünde veri kaybı olmaktadır.

Not: Mümkün olduğunca tür dönüştürme işlemlerini kullanma.

Checked ve Unchecked

Checked: Büyük türlerin küçük türlere dönüştürülmesi sırasında oluşabilecek veri kayıplarından bahsettik. Bu veri kayıplarına sebeb olan tür dönüştürme işleminde derleyici herhangi bir hata vermemektedir. Özellikle rastgele işlemleri yoğun bir şekilde kullanıldığı oyun programlarında bu tür durumlarla sıklıkla karşılaşılır. Bir değişkene tutabileceği maksimum değerden büyük bir değer atamak veri kaybına sebeb olacaktır. Herhangi bir zamanda bir değişkenin bu maksimum değerden büyük olamayacağını bilemeyiz. Bu yüzden veri kayıplarına karşı önlemler almalıyız. Bu önlemlerden biri checked anahtar sözcüğüdür.

Checked anahtar sözcüğü ile çalışma zamanında bu tür veri kayıplarının olabilceği durumlarda hata vermesini sağlarız.

int i = 256;
byte b;
checked{
b = byte(i);
}

Unchecked: Checked anahtar sözcüğünün tam tersidir. Yani işlevsel olarak normal bir kod ile unchecked blokları arasındaki kodun hiçbir farkı yoktur. Ancak bazı durumlarda checked blokları içindeki uzun kod bölümünde bazı yerleri unchecked durumuna getirmek isteyebiliriz.

Boxing ve Unboxing

Boxing: Bir nesnenin object türüne bilinçli yada bilinçsiz olarak dönüştürülmesidir. Bir önceki yazımda da dediğim gibi value tipleri dahil bütün veri türleri object türünden bir değişkene atanabilir. Object nesneler reference tipi olduğu için heap dediğimiz bellek bölgesinde tutulurlar. Halbuki value tipi nesneler stack dediğimiz bellek bölgesinde tutulurlar.

O halde bir value tipini reference tipinden bir nesneye atadığımızda stack bölgesinde tutulan veri, bit olarak heap alanına kopyalanır ve stack bölgesindeki object türünden olan değişken de bu heap bölgesini gösterecek şekilde ayarlanır. Bütün bu işlemlere boxing işlemi denmektedir.

Not: Boxing işlemi bilinçsiz olarak yapılabileceği gibi tür dönüştürme operatörü kullanılarak açıkça belirtilerek de yapılabilir.

BOXİNG

Görüldüğü gibi object türüne dönüştürülmüş değer unboxing işlemi yapılana kadar heap bellek bölgesinde tutulur.

Unboxing işlemi: Unboxing işlemi boxing işleminin tamamen tersidir. Yani heap bölgesindeki bir nesnenin değeri bit olarak stack bölgesine kopyalanır. Böylece reference türünü value türüne dönüştürmüş oluruz.

Not: Unboxing işleminin çalışma zamanında(run-time) hata vermemesi için sağlaması gereken iki önemli koşul vardır. Bunlar:

  1. Unboxing işlemine tabi tutulacak nesnenin daha önceden boxing işlemine tabi tutulmuş olması lazım.
  2. Boxing işlemine tabi tutulmuş olan bir nesnenin unboxing işlemi sırasında doğru türe dönüştürülmesi lazım.

Not: Unboxing işlemi bilinçsiz bir şekilde yapılamaz.

Bu yüzden tür dönüştürme operatörü kullanılmalıdır. Örneğin daha önceden object türüne dönüştürülmüş bir nesne aşağıdaki şekilde unboxing işlemine tabi tutulur.

int i = 60;
object o = i;
int j = (int)o;

Not: Boxing ve Unboxing işleminden sonra nesnelerin değerleri korunur.

System.Convert Sınıfı

C# dilinin yapısında bulunan tür dönüşüm mekanizmalarının yanı sıra .NET sınıf kütüphanesindeki bazı özel sınıfların statik metotları da tür dönüştürme işlemi yaparlar.

Not: Convert sınıfının bu metotları genellikle string türlerinin temel veri türlerine dönüşümü için kullanılır.


Operatörler

Operand: Operatörler gerekli işleri yapmaları için birtakım malzemeye ihtiyaç duyarlar. Örneğin + operatörünün toplama yapabilmesi için iki tane sayıya ihtiyacı vardır. Bu sayılar operand denilmektedir.

As operatörü: as operatörü uygun türler arasındaki dönüşümü sağlar.

as operatörünün ürettiği değer reference türündendir, eğer dönüşüm işlemi başarısızsa null değer üretilir. Örneğin object türünden olan bir nesne string türüne aşağıdaki şekilde döndürülür.

object i = “50”;
string s= i as string;

Is operatörü: is operatörü çalışma zamanında bir nesnenin türünün operand ile verilen türe uyumlu olup olmadığını kontrol eder. Boolean değer üretir.

int i = 50;
bool b1 = i is int // True
bool b2 = i is double //False

Bitsel Operatörler

Bitsel operatörler sayıları bir bütün olarak ele almazlar. Sayıları oluşturan her bit üzerinde ayrı ayrı işlem yaparlar.

Not: Bitsel işlemler yalnızca tamsayılar üzerinde yapılır. Gerçek sayılarla bitsel operatörler kullanılamaz.

~(Bitsel Değil) Operatörü: Tilda(~) bitsel değil operatörü bit seviyesinde değil işlemi almaktadır.

1 → 0
0 → 1

&(Bitsel Ve) Operatörü: Bitsel ve(&) operatörü operandları karşılıklı bitlerini and işlemine tabi tutar.

|(Bitsel Veya) Operatörü: Bitsel veya(|) operatörü operandları karşılıklı bitlerini or işlemine tabi tutar.

Araştırma: C# 7.0 versiyonu ile birlikte gelen Pattern Matching (Desen eşleştirme) konusunu araştır.


Diziler

Not: Bütün diziler System.Array sınıfından türetilmiştir. Yani biz bir dizi tanımladığımızda aslında Array sınıfı türünden bir nesne tanımlamış oluyoruz. Bu da dizinin her elemanına temel veri türleri için varsayılan değer, ilk değer olarak verileceğini gösterir.

Varsayılan değerler tablosu

foreach: foreach döngüsünde ulaştığımız eleman sadece okunabilir(readonly) özelliğine sahiptir. Bu yüzden eğer bir dizinin elemanlarını değiştirmek istiyorsak foreach yerine for döngüsü kullanmalıyız.

Not: Break anahtar sözcüğü döngü bloklarının dışında yada switch bloğunun dışında kullanmak mümkün değildir.

Araştırma: Düzensiz diziler(jagged arrays)

CreateInstance: int[] dizi = new int[3]; dediğimizde gizlice Array sınıfının CreateInstance metodunu kullanıyoruz. Aynı zamanda Array sınıfının CreateInstance metodunu kullanarak farklı bir yöntem ile dizi oluşturabiliriz.

Array dizi = Array.CreateInstance(typeof(int),5);
public static Array CreateInstance (Type elementType, int length);

Length: Dizinin eleman sayısını verir.

Rank: Dizinin boyutunu verir.

BinarySearch(): Tek boyutlu dizide binary search algoritmasına göre arama yapar.

Not: Sıralanmış diziler üzerinde arama yapar.

Clone(): Dizinin bit bit kopyasını çıkarır.

Copy(): Dizinin bir bölümünü başka bir diziye kopyalar. Gerekli tür dönüştürme ve boxing işlemleri yapılır.

CopyTo(): Bir dizinin belirlenen kısmını başka bir diziye kopyalar.


Fonksiyonlar

Fonksiyon sözcüğü daha çok tek başına iş yapan parçalar olarak ele alınır.

Not: Bir modülün işlevini yapamaz hale gelemesi diğer modülü etkilememelidir.

Not: Geri dönüş değeri olamayan metotlarda return anahtar sözcüğünün yanında herhangi bir ifade olmadan kullanılabilir.

Metotlar içinde tanımlanan değişkenler programın başından sonuna kadar durmaz. Bu değişkenler program akışı metoda geldiğinde tanımlanır. Metodun işlevi bittiğinde ise bellekten silinir. Program akışı tekrar metoda geldiğinde değişkenler yeniden tanımlanır ve sonunda yine bellekten silinir. Bu tür değişkenlere otomatik ömürlü nesneler denilmektedir.

Value ve Reference Parametreleri

Reference türleri metotlara aktarılırken bit bit kopyalanmazlar, yani metoda aktarılan sadece bir referanstır. Metodun içinde bu referans ile direkt işlem yapmak nesnenin değerini değiştirebilir. Bu yüzden metotlara parametre olarak reference tiplerini ve value tiplerini aktarırken dikkatli olmak gerekir.

Call by value: Bir nesnenin değerini metoda aktarmaya değer ile parametre geçme.

Call by reference: Bir nesnenin referansını metoda geçmeye ise referans ile parametre aktarımı denilmektedir.

C#’ bütün value tipleri metotlara bit bit kopyalanarak geçirilir. Yani int türünden bir nesneyi bir metoda parametre olarak geçirip metodun içinde bu parametreyi değiştirmek ana değişkeni değiştirmez. Çünkü int bir value tipidir. Bir reference türü olan dizileri metotlara parametre olarak geçmek ise tamamen farklıdır. Dizinin bütün elemanları metoda parametre olarak kopyalanmaz, parametre olarak kopyalanan sadece ilgili dizinin referansıdır. Metot içinde elimizde bulunan referansla ilgili değişiklik yaptığımızda orijinal nesneyide değiştirmiş oluruz. C#’ ta varsayılan olarak bütün value tipleri(struct ve temel veri tipleri) metotlara değer olarak; reference tipleri ise referans olarak aktarılır. Ancak bazı durumlarda value tiplerinde referans aktarmak isteyebiliriz. Bunun C# dilinde ref ve out anahtar sözcüklerinden yararlanacağız.

Not: Özellikle dizilerin metotlara referans olarak aktarılması performans açısından çok önemlidir. 500 elemanlı bir diziyi ekrana yazdıracak metodun parametreleri aktarılırken dizinin bütün elemanlarını tek tek kopyalanması performans açısından gerçekten çok kötü olurdu.

Ref ve Out

ref anahtar sözcüğü metotlara parametre aktarırken kullanılır. ref anahtar sözcüğü ile belirtilen parametreler value tipi de olsa referans olarak aktarılırlar. Genellikle value tiplerinin reference olarak metotlara geçirmeye zorlamak için kullanılır.

Not: ref anahtar sözcüğünün kullanımı ile ilgili diğer bir önemli özellik ise, ref parametrelerin aktarılırken muhakkak ilk değerin verilmiş olması gerekliliğidir. İlk değeri verilmemiş olan değişkenleri ref anahtar sözcüğü ile metotlara parametre olarak aktaramayız.

Bu tür hataları engelleme ve bu kullanımı geçerli kılmak için out anahtar sözcüğünü kullanırız. out anahtar sözcüğünün kullanımı ref anahtar sözcüğünün kullanımı ile aynıdır. Tek farkı out tanımlanmış parametrelere ilk değer verme gibi bir zorunluluğumuz yoktur. İlk değer metot içerisinde verilebilir.

ref return: C# 7.0 ile beraber artık ref anahtar sözcüğü, metotlardan değer yerine referans dönülmesine ve bunların yerel değişkenlerde referans olarak saklanmasına imkan sağlamaktadır. Geri dönüş değeri ref olarak işaretlenen fonksiyonlardan geriye bir değer döndürüleceği zaman da yine ref return kullandığımız metotları çağırırken de ref anahtar sözcüğünün kullanarak çağırmamız gerekmektedir. Aksi durumda(normal çağrımda olduğu gibi) metot referans değeri dönmek yerine değişken değeri dönecektir.

Araştırma: C# 7.2 ile gelen performans özellikleri

Metotların Aşırı Yüklenmesi ve İmza kavramı

Metotların aşırı yüklenerek(overload) aynı isimli birden fazla metot tanımlanabilir. Peki, çalışma zamanında bu metotlar nasıl belirlenecek, isimler aynı olduğuna göre hangi metodu çağırdığımız nasıl anlaşılacak?

İşte bunun için metotların imzalarına(method signature) bakılır. Metot imzası bir metodun karakteristik özelliklerini içeren bir tür bilgisidir. Bir metodun imzası metodun adı, parametrelerin sayısı ve türleri ile alakalıdır. Metodun geri dönüş değerileri metodun imzasına dahil değildir.

Metot imzaları parametrelerin türünü de kapsadığı için aynı isimli fakat farklı türden parametre alan metotlar bildirebiliriz.

Aşırı Yüklemeler

Derleyici hangi metodu çağıracağına karar vermek için önce metot bildirimi ile metot çağrımı arasındaki tam uyumu arar, yani iki int türünden parametre alacak şekilde bildirilmiş bir metodu, iki int türünden parametre ile çağırmak her zaman tam uyumu gösterir.

Not: Eğer bu tam uyumluluk bulunamaz ise parametreler arasında küçük türün büyük türe dönüştürülmesinin yasal olabileceği metot çağrılır.

.NET sınıf kütüphanesindeki birçok metoda aşırı yüklemeler yapılmıştır. Örneğin Console sınıfının WriteLine metodunu 19 değişik biçimde kullanabiliriz. Bu sayede bu metot ile ekrana istediğimiz türden verileri rahatlıkla yazabiliyoruz. Eğer her tür için farklı isimde metotlar olsaydı bu hem hızlı program geliştirmemize hem de birçok metot ismi ezberlememiz gerekirdi.

Not: Aşırı yüklenmiş metotlara aynı görevler verilmelidir. İsimleri aynı olan metotlar hemen hemen aynı görevi yapmalıdır.

Değişken Sayıda Parametre Alan Metotlar

C ve C++ programcıları göndermek istedikleri parametreleri bir diziye yerleştirip dizinin adresini metoda gönderirler. C# dilinde de buna benzer bir mekanizma vardır. Ancak benzer olan sadece mekanizmanın alt yapısıdır. Biz sanki değişken sayıda parametre olan metot varmış gibi istediğimiz sayıda parametreyi bir fonksiyona gönderebiliriz. Bu parametreler metoda dinamik bir dizi içinde aktarılır.

Değişken sayıda parametre alan metotlar tanımlamak için params anahtar sözcüğü kullanılır. params anahtar sözcüğü değişken sayıda eleman içerebilen bir veri yapısı tanımlar. Metoda gelen her bir parametre bu dizinin bir elemanı olacak şekilde ayarlanır.

İsimlendirilmiş(Named) ve Opsiyonel(Optional) Parametreleri

Named parametreler sayesinde bir metot çağrımında parametre sırasından bağımsız olarak parametrelerinin adı belirtilerek parametreler metoda gönderilmektedir. Optional parametreler ile ise metot bildirimi sırasında istenen parametreye varsayılan değer verilerek ilgili parametrenin gönderilmediği durumda metot içinde olacağı değer belirtilmektedir.

Bu yöntemle bir metodun farklı overload versiyonlarının da yazılmasının önüne geçmektedir.

Not: Opsiyonel ve isimlendirilmiş parametreler yapıcı metotlarda ve indeksleyicilerde de kullanılabilir.

Yerel Metotlar(Local Functions)

Local Functions

C# 7.0 ile beraber artık metot içerisinde sadece tanımlandığı metot içerisinde çağrılmak üzere başka metotlar tanımlayabiliyoruz. Bazen bir yardımcı metot sadece onu kullanan tek bir metodun içinde kullanılmaktadır. Bu gibi durumlarda bu yardımcı metotları diğer çağrılacağı metot gövdelerinin içerisinde yerel bir metot olarak bildirebiliriz.

Kaynaklar:

Sefer Algan, HER YÖNÜYLE C# , Pusula Yayıncılık, İstanbul, 2003

CBU YZM2105: OOP Ders Materyalleri