Giriş: Sözleşmeler ve Yetenekler Dünyası
Nesne Yönelimli Programlamada sınıflar, nesnelerin planlarıdır ve hem veri (alanlar/özellikler) hem de davranış (metotlar) içerirler. Kalıtım (:), bir sınıfın başka bir sınıfın özelliklerini ve metotlarını devralmasını sağlar (“bir türüdür” — is-a ilişkisi). Ancak C#, Java gibi diller tekli sınıf kalıtımını (single class inheritance) destekler; yani bir sınıf doğrudan yalnızca bir tane üst sınıftan miras alabilir. Bu, bazen kısıtlayıcı olabilir. Örneğin, hem “uçabilen” hem de “yüzebilen” bir “AmfibiUçak” nesnesi modellemek istediğimizde ne yapacağız? Hem UcanArac hem de YuzenArac sınıflarından aynı anda miras alamayız (bu “ölümcül elmas problemi — deadly diamond of death” gibi sorunlara yol açabilirdi).
İşte bu noktada Arabirimler (Interfaces) devreye girer. Arabirimler, bir sınıfın veya yapının ne yapabileceğini (hangi yeteneklere sahip olması gerektiğini) tanımlayan bir sözleşmedir, ancak bunu nasıl yapacağını belirtmez. Bir arabirim, belirli metot imzalarını, özellikleri, olayları ve indeksleyicileri tanımlar, ancak bunların gövdelerini (uygulamalarını) içermez (C# 8 öncesi için bu kuraldı, C# 8+ ile varsayılan uygulamalar geldi, ancak temel fikir hala geçerlidir).
Bir sınıf veya yapı, bir veya birden fazla arabirimi uygulayabilir (implement). Bu, o sınıfın veya yapının, arabirimde tanımlanan tüm üyeleri (metotları, özellikleri vb.) kendi içinde somut olarak gerçekleştireceğine dair bir taahhütte bulunduğu anlamına gelir. Bu sayede, farklı sınıf hiyerarşilerinden gelen nesnelerin ortak bir yeteneğe sahip olmasını garanti edebiliriz (örn. hem Kus hem de Ucak sınıfları IUcabilir arabirimini uygulayabilir).
Arabirimlerin Amaçları ve Faydaları:
Soyutlama (Abstraction): Uygulama detaylarını gizleyerek sadece gerekli olan “ne” sorusuna odaklanır (nesnenin ne yapabildiğine).
Sözleşme Tanımlama: Bir sınıfın belirli bir işlevselliği sağlamasını garanti eder. Arabirimi uygulayan her sınıf, o sözleşmeye uymak zorundadır.
Çoklu Kalıtım Benzeri Davranış: Bir sınıf birden fazla arabirimi uygulayarak farklı yetenek setlerini bir araya getirebilir.
Gevşek Bağlılık (Loose Coupling): Kodun belirli bir somut sınıf yerine bir arabirime bağımlı olmasını sağlar. Bu, sistemin daha esnek, test edilebilir ve değiştirilebilir olmasına olanak tanır. Örneğin, bir metot parametre olarak List yerine IList (bir arabirim) kabul ederse, o metoda List, Array (örtük olarak uygular) veya IList’yi uygulayan herhangi başka bir koleksiyon türü gönderilebilir.
Tak-Çalıştır (Plug-and-Play) Mimarileri: Farklı bileşenlerin belirli arabirimleri uygulaması sayesinde, bu bileşenler kolayca değiştirilebilir veya sisteme yeni bileşenler eklenebilir.
Bölüm 1: Arabirim Tanımlama (interface)
C#’ta bir arabirim tanımlamak için interface anahtar kelimesi kullanılır.
Sözdizimi:
[erişim_belirleyici] interface IArabirimAdi // 'I' öneki yaygın bir kuraldır
{
// Arabirim Üyeleri (Genellikle gövdesiz):
// - Metot İmzaları
// - Özellik İmzaları (get/set erişimcileriyle)
// - Olay Bildirimleri
// - İndeksleyici İmzaları
// - Statik Üyeler (C# 8+)
// - Varsayılan Metot Uygulamaları (C# 8+)
}
erişim_belirleyici: Genellikle public veya internal olur. Varsayılanı internal’dır.
interface: Arabirim tanımını başlatan anahtar kelime.
IArabirimAdi: Arabirime verilen isim. C# topluluğunda arabirim isimlerinin büyük I harfi ile başlaması yaygın bir adlandırma kuralıdır (örn. IDisposable, IEnumerable, ILogger). Bu, onun bir arabirim olduğunu hemen anlamayı sağlar. PascalCase kullanılır.
{ … } (Arabirim Gövdesi): Arabirimin üyelerini içerir.
Önemli Kurallar (C# 8 Öncesi):
Arabirim üyelerinin (metotlar, özellikler vb.) gövdeleri olmaz. Sadece imzaları (adı, parametreleri, dönüş türü) tanımlanır. Uygulama, arabirimi uygulayan sınıfa bırakılır.
Arabirim üyelerinin varsayılan olarak erişim belirleyicisi public kabul edilir ve açıkça public yazılamaz (veya başka bir belirleyici).
Arabirimler alan (field) içeremezler. (Veri depolamazlar, sadece sözleşme tanımlarlar).
Arabirimler yapıcı (constructor) veya yıkıcı (destructor) içeremezler. Örneklenemezler (new IArabirimAdi() yapılamaz).
Arabirim üyeleri static olamazdı (C# 8+ ile değişti).
Örnek Arabirim Tanımı:
using System;
namespace ArabirimTemelleri
{
// Günlük kaydı yapabilen nesneler için bir sözleşme
public interface ILogger
{
// Metot imzası (gövdesi yok)
void LogMesaj(string mesaj);
void LogHata(string hataMesaji, Exception? ex = null); // Opsiyonel parametre olabilir
// Özellik imzası (get ve/veya set olabilir)
string LogSeviyesi { get; set; }
bool AktifMi { get; } // Sadece okunabilir özellik
}
// Taşınabilir nesneler için bir sözleşme
public interface ITasinabilir
{
int Agirlik { get; } // kg cinsinden
void Tasi(string yeniKonum);
}
// Hem loglama hem taşıma yeteneği olan bir nesne için
// Arabirimler birbirlerinden miras alabilir
public interface ILoglanabilirTasinabilir : ILogger, ITasinabilir
{
// Bu arabirim ILogger ve ITasinabilir'in tüm üyelerini içerir
// Ayrıca kendine özgü üyeler de ekleyebilir
DateTime SonTasimaZamani { get; }
}
}
Bölüm 2: Arabirim Uygulama (Implementation)
Bir sınıfın (veya yapının) bir arabirimin tanımladığı sözleşmeye uyacağını belirtmek için, sınıf tanımında sınıf adından sonra : ve ardından uygulanacak arabirimin (veya arabirimlerin) adı yazılır.
Sözdizimi:
class BenimSinifim : IArabirim1, IArabirim2 // Birden fazla arabirim virgülle ayrılır
{
// BenimSinifim, IArabirim1 ve IArabirim2'deki TÜM üyeleri
// AÇIKÇA veya ÖRTÜK olarak uygulamak ZORUNDADIR.
// ... sınıfın diğer üyeleri ...
}
struct BenimYapim : IArabirim1 // Struct'lar da arabirim uygulayabilir
{
// ... IArabirim1 üyelerinin uygulamaları ...
}
Bir sınıf birden fazla arabirimi uygulayabilir.
Bir sınıf hem bir üst sınıftan miras alıp hem de bir veya daha fazla arabirimi uygulayabilir. Bu durumda, üst sınıf adı :’dan sonra ilk sırada yazılmalıdır, ardından arabirimler gelir.
class GorselOge : TemelKontrol, IDrawable, IInputElement { // ... }
Uygulama Yöntemleri:
Arabirim üyelerini uygulamanın iki yolu vardır:
2.1. Örtük Uygulama (Implicit Implementation):
En yaygın kullanılan yöntemdir.
Sınıf içinde, arabirimdeki üye ile aynı imzaya (isim, dönüş türü, parametreler) sahip, public erişim belirleyicisine sahip bir üye (metot veya özellik) tanımlanır.
Arabirim adı veya özel bir niteleyici kullanılmaz.
Örnek (ILogger’ı Örtük Uygulama):
using System;
using ArabirimTemelleri; // Önceki namespace'i kullanıyoruz
public class KonsolLogger : ILogger // ILogger arabirimini uygular
{
// Özelliklerin örtük uygulaması (Otomatik özellikler kullanılabilir)
public string LogSeviyesi { get; set; } = "Info"; // Varsayılan değer atama
public bool AktifMi { get; private set; } = true; // private set ile salt okunur gibi
// Metotların örtük uygulaması
public void LogMesaj(string mesaj)
{
if (AktifMi)
{
Console.WriteLine($"[{LogSeviyesi} - {DateTime.Now:T}]: {mesaj}");
}
}
public void LogHata(string hataMesaji, Exception? ex = null)
{
if (AktifMi)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"[HATA - {DateTime.Now:T}]: {hataMesaji}");
if (ex != null)
{
Console.WriteLine($"Detay: {ex.Message}");
// Console.WriteLine(ex.StackTrace); // Gerekirse stack trace
}
Console.ResetColor();
}
}
// Sınıfın kendine özgü metotları da olabilir
public void AktifEt(bool durum)
{
this.AktifMi = durum; // private set'e sınıf içinden erişim
}
}
2.2. Açık Uygulama (Explicit Implementation):
Daha az yaygın kullanılır, ancak belirli senaryolarda gereklidir.
Sınıf içinde üye tanımlanırken, üye adının önüne ArabirimAdi. eklenir.
Açıkça uygulanan üyelerin erişim belirleyicisi (public vb.) yazılmaz (örtük olarak public değillerdir) ve virtual, abstract, override gibi değiştiriciler alamazlar.
Açıkça uygulanan bir üyeye, sınıfın nesnesi üzerinden doğrudan erişilemez. Ona erişmek için nesnenin arabirim türüne dönüştürülmesi (cast) gerekir.
Neden Açık Uygulama Kullanılır?
İsim Çakışmalarını Çözme: Bir sınıf, aynı isimde ve imzaya sahip metotlar içeren iki farklı arabirimi uyguluyorsa, bu çakışmayı çözmek için metotlardan en az birini veya her ikisini de açıkça uygulamak gerekir.
interface IKapi { void Ac(); } interface IPencere { void Ac(); } class Ev : IKapi, IPencere { // Doğrudan Ac() tanımlarsak hangisini uyguladığı belirsiz olur. // Açık uygulama ile çözme: void IKapi.Ac() // Sadece IKapi referansı üzerinden erişilebilir { Console.WriteLine("Kapı açılıyor..."); } void IPencere.Ac() // Sadece IPencere referansı üzerinden erişilebilir { Console.WriteLine("Pencere açılıyor..."); } // İstersek public bir Ac metodu da ekleyebiliriz (varsayılan davranış) public void GenelAc() { Console.WriteLine("Genel açma işlemi..."); ((IKapi)this).Ac(); // Açık uygulamayı içeriden çağırma } } // Kullanım: Ev evim = new Ev(); evim.GenelAc(); // evim.Ac(); // Hata! Doğrudan erişilemez. IKapi kapi = evim; // Arabirim türüne dönüştür kapi.Ac(); // Kapı açılıyor... IPencere pencere = evim; pencere.Ac(); // Pencere açılıyor..
- Arabirim Üyesini Gizleme: Bir arabirim üyesinin, sınıfın public arayüzünün doğrudan bir parçası olmasını istemiyorsak (belki daha spesifik bir metot sunuyoruz veya sadece belirli senaryolarda kullanılması gerekiyor), onu açıkça uygulayabiliriz. Bu sayede sadece arabirim referansı üzerinden erişilebilir olur.
Örtük vs. Açık Uygulama Seçimi:
Genellikle örtük uygulama daha basittir ve tercih edilir. Üyeler doğrudan nesne üzerinden çağrılabilir.
Açık uygulama, yukarıda belirtilen isim çakışması veya üye gizleme gibi özel durumlar için kullanılır.
Bölüm 3: Arabirimlerin Üyeleri
Arabirimler aşağıdaki üye türlerini tanımlayabilir:
Metotlar: Gövdesiz (C# 8 öncesi) veya varsayılan gövdeli (C# 8+) metot imzaları.
interface IOrnek { void Metot1(); // Gövdesiz (uygulanmalı) int Metot2(string s); // Gövdesiz }
Özellikler: get ve/veya set erişimcilerine sahip özellik imzaları. Erişimcilerin gövdesi olmaz.
interface IOrnek { string SaltOkunurOzellik { get; } int OkumaYazmaOzellik { get; set; } }
İndeksleyiciler: Nesneye dizi gibi indeksle (nesne[indeks]) erişim sağlayan üye imzaları.
interface IIndeksli { string this[int index] { get; set; } // int indeksli string döndüren/ayarlayan
Olaylar (Events): event anahtar kelimesiyle tanımlanan olay bildirimleri.
interface IButon { event EventHandler Tiklandi; // EventHandler bir delege türüdür
Statik Üyeler (C# 8+): Statik metotlar, özellikler, olaylar ve indeksleyiciler tanımlanabilir. Bunlar arabirim adı üzerinden çağrılır.
interface IStatik { static int VarsayilanDeger { get; } = 10; // Statik özellik static void YardimciMetot() => Console.WriteLine("Statik Yardımcı"); // Statik metot } // Kullanım: int v = IStatik.VarsayilanDeger; IStatik.YardimciMetot();
Bölüm 4: Varsayılan Arayüz Metotları (Default Interface Methods — C# 8+)
C# 8.0 ile birlikte arabirimlere önemli bir yenilik geldi: Arabirim metotlarına varsayılan bir uygulama (gövde) eklenebilmesi.
Neden Gerekli Oldu?
API Evrimi: Mevcut bir arabirime yeni bir metot eklemek, o arabirimi uygulayan tüm mevcut sınıfların kırılmasına (derleme hatası vermesine) neden oluyordu, çünkü yeni metodu uygulamak zorunda kalıyorlardı. Bu, özellikle yaygın olarak kullanılan kütüphane arabirimlerini güncellemeyi çok zorlaştırıyordu.
Tekrarlanan Kodun Azaltılması: Bazen bir arabirimi uygulayan sınıfların çoğu, belirli bir metot için aynı temel uygulamayı paylaşırdı. Varsayılan uygulama, bu ortak kodu doğrudan arabirimde tanımlamaya olanak tanır.
Nasıl Çalışır?
Arabirim içinde bir metoda normal bir metot gibi gövde ({…}) eklenebilir.
Bu arabirimi uygulayan bir sınıf, bu metodu uygulamak zorunda değildir. Eğer uygulamazsa, varsayılan uygulama kullanılır.
Eğer sınıf metodu kendi içinde uygularsa (örtük veya açık olarak), sınıfın kendi uygulaması varsayılan uygulamayı geçersiz kılar (override eder).
Varsayılan uygulamaya sahip bir metoda erişmek için genellikle nesnenin arabirim türüne dönüştürülmesi gerekir (özellikle sınıf kendi uygulamasını sunmuyorsa veya isim çakışması varsa).
Örnek:
public interface ILoggerV2
{
void Log(string message); // Uygulanması zorunlu eski metot
// Varsayılan Uygulamalı Metot (C# 8+)
void LogWarning(string message)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Log($"[UYARI]: {message}"); // Arabirimdeki diğer metodu çağırabilir
Console.ResetColor();
}
// Statik metot (C# 8+)
static string FormatMesaj(string type, string message) => $"[{type}] - {message}";
}
public class SimpleLogger : ILoggerV2
{
// Zorunlu metodu uygulama
public void Log(string message)
{
Console.WriteLine(ILoggerV2.FormatMesaj("INFO", message)); // Statik metodu kullanma
}
// LogWarning'i UYGULAMAK ZORUNDA DEĞİL! Varsayılan kullanılacak.
}
public class AdvancedLogger : ILoggerV2
{
public void Log(string message)
{
Console.WriteLine($"ADVANCED LOG: {message}");
}
// Varsayılan uygulamayı GEÇERSİZ KILMA (Override etme)
public void LogWarning(string message)
{
Console.WriteLine($"!!! ADVANCED WARNING: {message} !!!");
}
}
// Kullanım
ILoggerV2 logger1 = new SimpleLogger();
logger1.Log("Basit log mesajı.");
logger1.LogWarning("Bu bir basit uyarı."); // Varsayılan uygulama çalışır
ILoggerV2 logger2 = new AdvancedLogger();
logger2.Log("Gelişmiş log mesajı.");
logger2.LogWarning("Bu bir gelişmiş uyarı."); // Sınıfın kendi uygulaması çalışır
// SimpleLogger sLog = new SimpleLogger();
// sLog.LogWarning("..."); // Doğrudan çağrılamayabilir, arabirim üzerinden çağırmak daha garantilidir.
Varsayılan arayüz metotları, arabirimlerin evrimini kolaylaştıran ve bazı kod tekrarlarını azaltan güçlü bir özelliktir, ancak dikkatli kullanılmalıdır.
Bölüm 5: Arabirimler ve Kalıtım
Arabirim Kalıtımı: Bir arabirim, başka bir veya birden fazla arabirimden miras alabilir. Bu durumda, miras alan arabirim, hem kendi üyelerini hem de miras aldığı tüm arabirimlerin üyelerini içerir. Bu arabirimi uygulayan sınıf, tüm bu üyeleri uygulamak zorundadır.
interface IRead { void Oku(); } interface IWrite { void Yaz(); } interface IReadWrite : IRead, IWrite // Hem IRead hem IWrite'dan miras alır { void Kopyala(); // Kendine özgü üye } class DosyaStream : IReadWrite { /* Oku, Yaz ve Kopyala uygulanmalı */ }
Sınıf Kalıtımı vs. Arabirim Uygulama: Bir sınıf tek bir üst sınıftan miras alırken (:’dan sonra ilk sırada), birden fazla arabirimi uygulayabilir (:’dan sonra virgülle ayrılarak). Bu, C#’ın “çoklu kalıtım” sorununu çözerken farklı yetenek setlerini birleştirmesine olanak tanır.
Bölüm 6: Arabirimler vs. Soyut Sınıflar (Abstract Classes)
Arabirimler ve soyut sınıflar, her ikisi de soyutlama sağlamak için kullanılsa da önemli farkları vardır:
ÖzellikArabirim (interface)Soyut Sınıf (abstract class)Temel AmaçNe yapmalı? (Sözleşme, Yetenek)Nedir? (Ortak kimlik, temel uygulama)UygulamaGenellikle gövdesiz üyeler (C# 8+ default olabilir)Hem soyut (gövdesiz) hem de somut (gövdeli) üyeler içerebilirAlanlarİçeremezİçerebilir (instance veya static)YapıcılarİçeremezİçerebilirStatik ÜyelerEvet (C# 8+)EvetErişim Belirleyiciler (Üyeler)Hepsi public (C# 8+ default farklı olabilir)public, private, protected vb. olabilirKalıtımSınıflar birden fazla arabirimi uygulayabilirSınıflar sadece bir tane soyut sınıfı miras alabilirNe Zaman Kullanılır?Farklı hiyerarşilerdeki sınıflara ortak yetenek eklemek, sözleşme tanımlamak, gevşek bağlılık sağlamakYakından ilişkili sınıflar için ortak bir temel ve varsayılan davranışlar sağlamak
Kural: Eğer sadece bir sözleşme tanımlamak istiyorsanız ve uygulama detaylarını tamamen uygulayan sınıfa bırakmak istiyorsanız arabirim kullanın. Eğer sınıflar arasında ortak bir temel (“bir türüdür” ilişkisi) varsa ve bazı ortak uygulama detaylarını (alanlar veya somut metotlar) paylaşmak istiyorsanız soyut sınıf kullanın.
Bölüm 7: Sonuç — Esneklik ve Sözleşmenin Gücü
Arabirimler (interface), C#’ta soyutlama, modülerlik ve esnekliğin temelini oluşturan kritik bir yapıdır. Bir sınıfın veya yapının uygulamayı taahhüt ettiği bir davranış sözleşmesi tanımlayarak, farklı sınıf hiyerarşileri arasında ortak yetenekler oluşturmamızı ve çoklu kalıtım benzeri senaryoları güvenli bir şekilde yönetmemizi sağlarlar.
Örtük ve açık uygulama mekanizmaları, arabirim üyelerinin sınıflar tarafından nasıl gerçekleştirileceği konusunda esneklik sunar. C# 8 ile gelen varsayılan arayüz metotları ise, API evrimini kolaylaştırarak ve kod tekrarını azaltarak arabirimlerin gücünü daha da artırmıştır.
Arabirimleri etkin bir şekilde kullanmak, gevşek bağlı (loosely coupled) sistemler tasarlamamıza olanak tanır. Kodumuzun belirli somut uygulamalar yerine arabirimlere bağımlı olması, sistemin daha kolay test edilmesini, değiştirilmesini ve genişletilmesini sağlar. Bağımlılık Enjeksiyonu (Dependency Injection) gibi modern tasarım desenleri büyük ölçüde arabirimlere dayanır.
Sonuç olarak, arabirimler C#’ta sadece bir söz dizimi özelliği değil, aynı zamanda temiz, sürdürülebilir ve ölçeklenebilir yazılımlar oluşturmak için temel bir felsefi yaklaşımdır. Bu güçlü soyutlama mekanizmasına hakim olmak, her C# geliştiricisinin hedeflemesi gereken önemli bir adımdır.
Abdulkadir Güngör - Kişisel WebSite
Abdulkadir Güngör - Kişisel WebSite
Abdulkadir Güngör - Özgeçmiş
Github
Github
Linkedin