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

Berkan Şaşmaz
SDTR
Published in
10 min readAug 21, 2019

Merhabalar.

Nesne Yönelimli Programlama

Nesne yönelim tekniği, gerçek hayatı programlar için simule edecek yöntemler topluluğudur. Her nesnenin kendine has özellikleri vardır. Nesneler tamamen birbirlerinden soyutlanarak farklılaştırılır. Tabi her nesnenin birbirine mesaj göndermesi de son derece doğaldır. Nesne yönelimli programlama tekniğinde gerçek bir sistem parçalara ayrılır ve bu parçalar arasında ilişkiler kurulur. Her bir parça hiyerarşik yapıda olabileceği gibi, parçalar birbirinden tamamen bağımsız da olabilir. Birbirinden bağımsız olan parçalar çeşitli yöntemlerle aralarında haberleşirler, bazen de büyük parçayı önceden tanımlanmış diğer küçük parçalar oluşturur. Burada önemli olan her parçayı etkili bir şekilde tasarlamaktır.

  1. Nesne yönelimli programlama tekniğinin en küçük yapı taşı nesnelerdir (object). Nesneler yapılarında veriler bulundururlar. Ayrıca bu veriler arasında belirli ilişkiler sağlayan fonksiyonlar vardır.
Sarmalama(Encapsulation)

Nesnelerin, verileri bu şekilde barındırması ve fonksiyonları içermesine sarmalama(encapsulation) denilmektedir.

2. Nesne içindeki veriler ve fonksiyonlar nesnenin dışarıya asıl bir hizmet verdiğinin belirler. Ancak bu hizmetin ne şekilde verildiği belli değildir. Nesnenin servislerden faydalanabilmek için nesnenin dış dünyaya sunduğu arayüzü bilmemiz yeterlidir. Nesne yönelimli teknik jargonunda buna bilgi saklama(information hiding) denilmektedir.

3. Nesneler birbirlerinden bağımsız olmasına rağmen aralarında mesajlaşabilirler. Hangi nesnenin hangi nesneye mesaj göndereceği derleme aşamasında belli olmayabilir. Bu durumda nesne yönelimli programlama tekniğinin diğer bir önemli özelliği olan geç bağlama(late binding) devreye girer.

4. Derleme zamanında hangi nesnelerin hangi fonksiyonlarının kullanılacağı belli olmayabilir. Aynı şekilde nesneler arası mesajlaşmanın hangi nesneler arasında olacağı da derleme zamanında belli olmayabilir. Bu durumda nesne yönelimli programlama tekniğinin çalışma zamanında bağlama mekanizmasından faydalanılır (late binding).

5. Tüm nesneler birer sınıf örneğidir. Sınıflar nesnenin özelliklerini belirler

6. Bir sınıf diğer bir sınıftan türediği zaman, türediği sınıfın bütün özelliklerinin içerir. Bunun yanında kendine has özellikleri de barındırabilir.

7. Nesne yönelimli programlama tekniğinde nesneler çok biçimli olabilir. Çok biçimlilik(polymorphism) kavramı da türetme ile yakından alakalıdır ve önemli bir kavramdır. Anlamı, bir nesnenin farklı şekillerde davranabilmesidir.

Nesne kavramı

Nesne yönelimli programlama tekniğinde sınıflar nesnelerin biçimini belirler. Her bir nesne kendi içinde tutarlı bir yapıya sahiptir. Yani veriler arasında sıkı bir bağ vardır(high cohesion). C#’ ta nesne yönelimli programlama yapısını sınıflar sağlar. Sınıfları kullanarak nesneler tanımlarız.

Not: Sınıf bir nesnenin şeklini belirlemektedir. Kısacası bir tür bilgisi saklamaktadır.

Kalıtım(Inheritance)

Kalıtım yolu ile nesneler birbirlerinden türetilir. Türeyen sınıflar türediği sınıfın özelliklerini kalıtım yolu ile devraldığı gibi kendisi de yeni özellikler tanımlayabilir. Türetme yolu ile bir nesne kendi özelliklerini yeni nesneye aktarır. İstenirse türeyen nesne kendine has özel özellikler de içerebilir. Türetme ile sınıflar arasında hiyerarşik bir yapı sağlanır.

Yapıcı Metotların Kalıtımdaki Rolü

Bir Kedi nesnesi tanımlandığında Kedi sınıfın yapıcı metodunun çağrılacağı kesindir. Peki, Memeli sınıfının yapıcı metodu da çağrılacak mıdır?

class Memeli
{
protected double Boy;
protected double Agirlik;
public Mememli()
{
Console.WriteLine("Memeli hayvan olusturuldu.");
}

public void OzellikGoster()
{
Console.WriteLine("Boy = " + Boy);
Console.WriteLine("Agirlik = " + Agirlik);
}
}
class Kedi : Memeli
{
public string Turu;
public Kedi()
{
Console.WriteLine("Kedi olusturuldu.");
}

public void TurGoster()
{
Console.WriteLine(Turu + " kedisi");
}
}
class MainMetodu
{
static void Main()
{
Kedi kedi = new Kedi();
}
}

Çıktı:

Memeli hayvan olusturuldu.
Kedi olusturuldu.

Görüldüğü gibi hem Kedi hem de Memeli sınıfının yapıcı metodu çağrılmıştır. Ancak ilk çağrılan yapıcı metot Memeli sınıfının yapıcı metodu olmuştur. Eğer Memeli sınıfında yapıcı metot olmasaydı doğal olarak sadece Kedi sınıfının yapıcı metodu çağrılırdı. Yapıcı metotlarla ilgili en önemli konu her yapıcı metodun kendi tanımlandığı sınıfın üye elemanları ile ilgili iş yapması gerekliliğidir. Bu dilin bir zorunluluğu değildir. nesne yönelimli programlama tekniğini daha etkin kullanabilmek için gereklidir.

Kedi sınıfının Boy ve Agirlik özelliklerinin ilk değer atamasını yaparken, Kedi sınıfının yapıcı metodu mu yoksa Memeli sınıfının yapıcı metodu mu kullanılmalıdır? Kuşkusuz her ikisi de mümkündür. Ancak eğer ilk değer verme işlemini türeyen sınıfta yaparsak

Memeli memeli = new Memeli(7,14);

şeklinde bir nesne tanımlaması için temel sınıfta ayrıca bir yapıcı metodun daha tanımlanması gerekir. Bu yüzden her elemanın ilk değer ataması, tanımlandığı sınıfın yapıcı metodu ile yapılmalıdır. Yani Boy ve Agirlik özellikleri Memeli sınıfının yapıcı metodu ile, Turu özelliği ise Kedi sınıfının yapıcı metodu ile düzenlenmelidir. Bu durumda önümüze bir engel çıkmaktadır.

Kedi kedi = new Kedi("Van", 7, 14);

şeklinde bir kedi nesnesi tanımladığımızda 7 ve 14 değerlerinin Memeli sınıfının yapıcı metoduna nasıl aktaracağız? Bunun için türemiş sınıf içerisinden temel sınıfın elemanlarına erişmek için kullanılan base anahtar sözcüğünü kullanacağız.

public Kedi(string turu, int boy, int agirlik) : base(boy, agirlik)
{
this.Turu = turu;
}

Kedi sınıfının yapıcı metodu çağrıldığı anda base anahtar sözcüğü ile kedi nesnesi yapılandırmak için gelen boy ve agirlik değişkenleri Memeli sınıfının yapıcı metoduna gönderiliyor.

Not: base anahtar sözcüğü sınıf hiyerarşisinin en tepesindeki sınıfı temsil etmez.

İsim Saklama(Name Hiding)

Normal şartlarda türeyen sınıfta, temel sınıfta bulunan bir üye elemanının isminde bir eleman tanımlanabilir. Bu durumda temel sınıftaki üye elemana normal bir şekilde erişmek mümkün değildir. Çünkü türemiş sınıfta bulunan eleman temel sınıftaki elemanı gizlemiştir.

class X
{
protected int a;
public X()
{
}

public X(int a)
{
this.a = a;
}
public int A
{
get
{
Console.WriteLine("X sınıfı");
return a;
}
}
}
class Y : X
{
protected int b;
public Y()
{
}

public Y(int a)
{
this.b = a;
}
public int A
{
get
{
Console.WriteLine("Y sınıfı");
return b;
}
}
}
class MainMetodu
{
static void Main()
{
Y y = new Y(5);
int deneme = y.A;
}
}

Bu programı derlediğimizde aşağıdaki gibi bir uyarı ile karşılaşırız.

NameHiding.cs(42,15): warning CS0108: The keyword new is required on ‘Y.A’ because it hides inherited member ‘X.A’

Bu uyarıda Y.A elamanı X.A elamanını gizlediği söylenmiştir. Bu yüzden new anahtar sözcüğünün kullanılmasını tavsiye ediyor.

Biz her zaman isim gizlemesi yaparken isim gizlemeyi açıkça belirtmek için temel sınıftaki bir elemanı gizlerken türemiş sınıftaki elemanın bildirimine new anahtar sözcüğü eklemeliyiz.

class Temel
{
public int a;
}
class Tureyen: Temel
{
new int a;
}

Bu iki sınıfı içeren bir kaynak kod artık uyarı vermeden derlenecektir. İsim gizleme işlemini metotlar ve diğer üye yapılar içinde yapmak mümkündür.

Peki, isim gizlemesinin yapıldığı durumlarda temel sınıftaki üye elemana ulaşabilir miyiz? Evet, ulaşabiliriz. Bunun için base anahtar sözcüğü kullanılır.

class Temel
{
public int a;
public void Goster()
{
Console.WriteLine("Temel.a = " + a);
}
}
class Tureyen : Temel
{
new int a;

public Tureyen(int aTemel, int aTureyen)
{
a = aTureyen;
base.a = aTemel
}
new public void Goster()
{
base.Goster();
Console.WriteLine("Tureyen.a = " + a);
}
}
class MainMetodu
{
static void Main()
{
Tureyen nesne = new Tureyen(5,6);
nesne.Goster();
}
}

Çıktı:

Temel.a = 5
Tureyen.a = 6

Hem Temel hem de Tureyen sınıftaki Goster() metotları public olarak tanımlandığı için normal şartlarda her ikisine de erişmemiz mümkündür ancak isim benzerliğinden dolayı

Tureyen nesne = new Tureyen(5,6);
nesne.Goster();

deyimleri ile Tureyen sınıftaki Goster() metodu çağrılmaktadır.

Temel ve Türeyen Sınıf Nesneleri

C# dili tür güvenliğine(type safety) büyük önem vermektedir. Bu yüzden farklı türlerdeki nesnelerin birbirlerine özel durumların dışında atanması yasaklanmıştır. Bunun bir istisnası da temel ve türeyen sınıflarda görülmektedir. Türeyen sınıfa ilişkin bir referans temel sınıfa ilişkin bir referansa atanabilir. Temel ve türeyen sınıflar arasındaki bu ilişki nesne yönelimli programlama tekniğinin en önemli kavramlarından biri olan çok biçimliliğin altyapısını oluşturur.

Not: Biz istesek de istemesek de C#’ ta bütün değer ve referans türlerinin tamamı gizlice object sınıfından türetildiğini biliyoruz. Dolayısıyla her sınıf bir object’ ti aslında. Nasıl String türü bir object ise Kedi ve Memeli sınıfı türünden tanımlanmış her nesne de bir object’ tir.

Kendi tanımladığımız sınıflar da dahil olmak üzere bütün veri türlerinde bulunan ToString() metodunun aslında Object sınıfının bir üye metodu olduğunu bilmek gerekir. Diğer veri türleri de Object sınıfından türediği için her türde ToString() metodu mevcuttur. Biz kendi sınıflarımızda, new anahtar sözcüğünü kullanarak ya da kullanmayarak ToString() metodunu yeniden bildirebiliriz.

Not: Object türü genel bir veri türü olmasına rağmen tür güvenliği ve performans açısından kötü sonuçlara yol açabildiği için, çok gerekmediği sürece kullanmak pek tavsiye edilen bir durum değildir.

Sanal(Virtual) Metotlar

Sanal metot kavramı ile çalışma zamanında metot seçme işinin nasıl olduğunu inceleyeceğiz.

Sanal metotlar temel sınıflar içinde bildirilmiş ve türeyen sınıflar içinde de tekrar bildirilen metotlardır. Sanal metotlar nesne yönelimli programlama tekniğindeki çok biçimliliği(polymorphism) uygulayan yapılardır. Temel sınıfta bir sanal metot bildirildiğinde bu temel sınıftan türeyen sınıflar, temel sınıftaki sanal metodu devre dışı bırakarak kendilerine uygun metot gövdesi oluşturabilirler. Sanal metotların en önemli özelliği geç bağlam(late binding) özelliğini desteklemesidir.

Sanal metotlar sayesinde Temel sınıf türünden bir referansa türeyen sınıf referansları aktarıldığında, temel sınıf referansı üzerinden kendisine aktarılan türeyen sınıfın sanal metodu çağrılabilir. Eğer türeyen sınıf sanal metodu devre dışı bırakmamışsa temel sınıftaki sanal metot çağrılır. Çağrılan metodun hangi türe ait olduğu ise çalışma zamanında belirlenir. Metotların bu şekilde çalışma zamanında belirlenmesine geç bağlama(late binding) denilmektedir.

Sanal metotlar sayesinde temel sınıf referanslarına türeyen sınıf referansları atandığında, temel sınıf referansı üzerinden türeyen sınıfa ait metotları çağırabilmekteyiz. Bu yöntemle sadece temel sınıfta sanal olarak bildirilmiş metotların türeyen sınıfta devre dışı bırakılmış olan metotlara erişilebilir.

Not: Türeyen sınıflar, temel sınıflardaki sanal metotları devre dışı bırakmak zorunda değildir. Bu durumda eğer temel sınıf referansına, türeyen sınıf referansı aktarılırsa bu referans üzerinden temel sınıfa ait sanal metot çağrılır.

Öğrendiklerimizi pekiştirmek gerekirse;

  1. Eğer metot virtual olarak bildirilmemişse, derleyici nesnelerin tür bilgisinden faydalanarak derleme zamanında hangi metodun çağrılacağını bilir.
  2. Eğer metot virtual olarak bildirilmişse, derleyici derleme aşamasında ürettiği kod ile çalışma zamanında referansın türüne göre ilgili sınıfın devre dışı bırakılmış metodunu çağırır.
  3. Hangi metodun çağrılacağının çalışma zamanında belirlenmesine geç bağlama(late binding) denilmektedir.
  4. Türeyen sınıfta devre dışı bırakılan metotların parametrik yapısı temel sınıftaki sanal metodun parametrik yapısı ile aynı olmalıdır(Method İmzası (Method Signature).
  5. Statik metotlar virtual olarak bildirilemez

Özet(Abstract) Sınıflar

Nesne yönelimli programlamada sınıf hiyerarşisi oluştururken bazen hiyerarşinin tepesinde bulunan sınıf türünden nesnelerin programcı için pek bir anlamı olmayabilir. Hiyerarşinin en tepesinde bulunan sınıfın kendisinden türetilecek diğer sınıflar için ortak özellikleri bir arada toplayan bir arayüz gibi davranmasını isteyebiliriz. Yani en tepedeki sınıf diğer sınıfların neye benzeyeceğini belirtsin isteyebiliriz. Örneğin, Memeli sınıfında tanımlanan Konus() isimli bir metot çağrıldığından yapılacak iş belli değildir. Çünkü hangi memeli hayvanın mimikleri ile konuşulacağı belli değildir. Eğer Konus() metodu Memeli sınıfında bildirilmezse bu sefer Memeli sınıfından türeyecek sınıfların(Kedi, Koyun) çok biçimliliği desteklememesi söz konusu olur. O zaman bir tek çözüm var: Memeli sınıfında Konus() metodu bildirilsin ama herhangi bir iş yapması için özelleştirilmesin, sadece Memeli sınıfından türeyecek diğer sınıflar için bir arayüz oluştursun.

Bir temel sınıf içinde bildirilen abstract metotların temel sınıf içerisinde gövdesi yoktur. Ancak bu temel sınıftan türeyen bütün sınıflar bu metodu devre dışı(override) bırakmalıdır. Abstract metotlar içsel olarak zaten sanal oldukları için ayrıca virtual anahtar sözcüğü kullanarak sanal olduklarını belirtmeye gerek yoktur.

Bir abstract sınıf türünden nesneler tanımlanamaz. Abstract sınıflar sadece kendilerinden türeyen sınıflar için arayüz oluştururlar.

Abstract sınıflar temel sınıfın tek başına anlamlı bir nesneyi ifade etmediği durumlarda kullanılır. Abstract sınıfları kullanabilmek için bu sınıflardan türeyen yeni sınıflar bildirmemiz gerekir.

Not: Abstract sınıftan türeyen sınıf nesneleri üzerinden abstract sınıfa ait yapıcılar kullanılarak abstract sınıfın üye değişkenleri değiştirilebilir.

Türeyen sınıflarda temel sınıfta bulunan bulunan bütün abstract metotların devre dışı bırakılması gerekir.

Not: Abstract sınıflar içinde abstract olmayan metotlar da bildirilebilir. Ancak bir sınıf içerisinde bir abstract metot bildirilmişse sınıfın da mutlaka abstract olması gerekir.

Öğrendiklerimizi pekiştirmek gerekirse;

  1. Abstract sınıflar türünden nesneler tanımlanamaz.
  2. Abstract sınıflar, abstract metotlar içerebilir. Abstract metotlar ancak abstract sınıfların içinde bildirilebilir.
  3. Metodun gerçek gövdesi türeyen sınıflarda yazılmalıdır.
  4. Abstract metotlar içsel olarak zaten sanal oldukları için ayrıca virtual anahtar sözcüğü kullanarak sanal olduklarını belirtmeye gerek yoktur.
  5. Statik metotlar özet olarak bildirilemez.
  6. Sınıflarda abstract özellikler de bildirilebilir, türeyen sınıflarda bu özet özellikler override anahtar sözcüğü ile tekrar bildirilmelidir.

Sealed Anahtar Sözcüğü

Bazı durumlarda oluşturduğumuz sınıfların türetilmesine engel olmak isteyebiliriz. Bunun birçok sebebi olabilir. Birincisi gerçekten ilgili sınıftan türetme yapmanın anlamsız olmasıdır. İkincisi ise güvenlik sorunları olabilir. sealed anahtar sözcüğü özellikle bütün metotları statik olan sınıflar için gerçekten uygun görünüyor. Bütün metotları statik olan sınıfı türetmenin çok fazla anlamlı olmadığı açıktır. Çünkü temel sınıfa ilişkin nesne referansı üzerinden herhangi bir metot zaten çağrılmamaktadır.

Arayüzler(Interface)

Bir önceki kısımda gördüğümüz özet(abstract) metotlar ile bir özet metodun bulunduğu sınıftan türeyen bir sınıfın mutlaka bu özet metodu devre dışı bırakıp kendine göre uygulaması gerekirdi. Burada yaptığımız aslında bir sınıfın neye benzediğini tespit etmekti. Arayüzlerin bütün metotları ve özelliklerinin özet olarak bildirilmiş sınıflardan çok fazla farkı yoktur. Dolayısıyla arayüzlerdeki metotların ve özelliklerin gövdesi yazılmaz. Kısaca arayüzler, kendisini uygulayan sınıfların kesin olarak içereceği özellikleri ve metotları belirler.

Arayüzler kişisel uygulamalarda pek fazla kullanılmaz ancak özellikle firmanın ya da programcının üzerinde çalıştığı projelerde ortak bir zemin oluşturabilmek için arayüzlerden faydalanılır.

Bir arayüzde özellik, metot, indexer, delegate, ve event bildirimi yapılabilir.

  1. Arayüzdeki elemanları statik olarak bildiremeyiz.
  2. Arayüzdeki eleman bildirimleri içsel olarak public oldukları için ayrıca bir elemanı erişim belirleyici ile bildirmek yasaklanmıştır.
  3. Arayüzler herhangi bir üye değişken içeremez.
  4. Arayüzlerde yapıcı ve yıkıcı metotlar tanımlanamaz ya da bildirilemez.
interface IArayuz
{
int Siradaki();
void Sifirla();

int Deger
{
get;
set;
}
int this[int indeks]
{
get;
}
}

Bu arayüzü uygulayan bir sınıfta Arayüzde bulunan bütün elemanların uygulanması gerekir. Deger özelliğinin hem get hem de set blokları olmasına karşın indeksleyicinin sadece get bloğu vardır. Buna rağmen bu arayüzü uygulayacak sınıfta bildirilen indeksleyicinin hem get hem de set bloğu ya da sedece get bloğu olabilir, fakat sadece set bloğu olamaz.

Not: Arayüz uygulamada, arayüz türetmedeki temel sınıf yerine geçer.

Not: Sınıflar birbirlerinden nasıl türetilebiliyorsa Arayüzler de birbirlerinden türetilebilir. Temel arayüzdeki bütün elemanlar arayüze aktarılır. Arayüzleri türetirken new anahtar sözcüğü kullanılarak temel arayüzdeki bir eleman gizlenebilir. Bu şekilde türeyen arayüzde temel arayüzdeki bir elemanla aynı isimle bildirilebilir.

interface Arayuz1
{
void Metot1();
}
interface Arayuz2 : Arayuz1
{
new void Metot1();
}

Şimdi aklınıza şöyle bir soru gelmiş olabilir. Arayuz2' yi uygulayan bir sınıfta Metot1() metodu uygulanmadığında geçerli bir bildirim olur mu? Cevabımız hayır. Çünkü her ne kadar Arayuz1' deki Metot1() gizlenmiş olsa da kalıtım yolu ile Arayuz2' ye aktarılmıştır. Bu yüzden Arayuz2' yi uygulayan bir sınıf hem Arayuz1.Metot1() hem de Arayuz2.Metot1() metodunu uygulamalıdır.

Arayüz Referansları

Arayüzler ile referanslar oluşturabilir. Evet yanlış duymadınız arayüz referansları oluşturulabilir. Peki bir arayüz referansı ne anlama gelir? Açıkça söylemek gerekirse tek başına bir arayüz referansı herhangi bir anlam ifade etmez. Ancak arayüz referanslarına kendisini uygulayan herhangi bir sınıf nesnesinin referansı atanabilir. Bu durumda arayüz referansı ile arayüzde bulunan metot ya da özellikler hangi sınıf referansı tutuluyorsa o sınıf türünden bir nesne için çağrılabilir.

Açık(Explicit) Arayüz Uygulama

C# dilinde arayüzleri uygulamananın bir yolu daha vardır: Explicit Interface Implementation. Bu yöntem daha önce bahsedilen yöntemlerin aşağıdaki kısıtlarından kaynaklanmaktadır.

  • Açıkça arayüz uygulama yöntemi ile istenirse türeyen sınıflarda arayüzde bulunan üye elemanlar açık bir şekilde nesneler tarafından erişilemez hale getirilebilir. İlgili üye elemanlarına sadece arayüz ile erişilmesi sağlanır.
  • Birden fazla arayüz uygulandığı durumlarda eğer aynı isimli üye elemanlar varsa isim çakışmasının önüne bu yöntem geçilebilmektedir.

Not: Açık arayüz uygulaması yapmak için arayüzün üye elemanları arayüz isimleri ile beraber belirtilir.

interface IMemeli
{
void Konus();
}
class IKedi : IMemeli
{
void IMemeli.Konus()
{
Console.WriteLine("miyav");
}
}
class Program
{
static void Main(string[] args)
{
Kedi k = new Kedi();
((IMemeli)k).Konus();
}
}

Yukarıdaki örnekte de görüldüğü üzere Konus() metodunun çağrılabilmesi için IMemeli arayüzü referansına çevrilmesi gerekiyor. Direkt Kedi nesneleri üzerinden Konus() metodu çağrılmayacaktır. Böylece sınıf nesneleri için arayüz üye elemanları private gibi davranmaktadır. Normal yöntemle bunu yapmak mümkün değildir.

--

--