C# interface, bir sınıfın veya struct'ın hangi davranışları sergilemesi gerektiğini belirleyen, ancak bu davranışların nasıl gerçekleştirileceğini söylemeyen bir sözleşmedir. Başka bir deyişle interface, "bu tipi kullanan herkes şu metotları mutlaka sağlamalı" diyen bir kural listesidir; içeriğin kendisini değil, imzasını tanımlar. Abstract class ise buna benzer ama farklı bir amaca hizmet eder: ortak kod paylaşımı ve "is-a" ilişkisi kurmak için kullanılır. Bu makalede interface kavramını sıfırdan ele alıp abstract class ile aradaki pratik farkları, gerçek bir IPayment örneği üzerinden adım adım göstereceğiz.
Interface Nedir? Sınıfın 'Ne Yapabileceğini' Taahhüt Eden Sözleşme
Bir interface'i en yalın haliyle şöyle düşünebilirsiniz: bir sınıfın "ne yapabileceğine" dair imzalanmış bir sözleşme. Bu sözleşme, sınıfın hangi metotlara, özelliklere veya olaylara sahip olacağını garanti eder; fakat bu üyelerin içinde ne olacağına dair hiçbir şey söylemez. Bir interface uyguladığınızda aslında şunu taahhüt etmiş olursunuz: "Bu sözleşmede listelenen her şeyi kendi mantığımla dolduracağım."
Bu noktada OOP'de sık karıştırılan bir ayrımı netleştirmek gerekir: kalıtım genellikle bir "is-a" (bir şeydir) ilişkisi kurar — örneğin bir Araba, bir Taşıt'tır. Interface ise "can-do" (yapabilir) ilişkisi kurar — bir sınıf uçabilir, yüzebilir ya da ödeme yapabilir. Bu ayrım önemlidir çünkü birbiriyle hiç akraba olmayan iki sınıf (örneğin bir Kuş ve bir Uçak) aynı davranışı sergileyebilir; interface tam da bu tür ortak yetenekleri, sınıf hiyerarşisinden bağımsız biçimde tanımlamanızı sağlar.
- Interface'in içinde yalnızca metot imzaları, özellik tanımları veya olay bildirimleri bulunur; gövde (uygulama kodu) yer almaz.
- Interface'i uygulayan her sınıf, sözleşmedeki tüm üyeleri kendi mantığıyla doldurmak zorundadır.
- Bir interface tek başına örneklenemez (new ile nesne oluşturulamaz); yalnızca onu uygulayan sınıflar üzerinden anlam kazanır.
Bu yapı, nesne yönelimli programlamanın temel taşlarından biri olan soyutlama ilkesiyle doğrudan bağlantılıdır. Soyutlama, bir şeyin nasıl çalıştığını gizleyip yalnızca ne yaptığını göstermektir; interface de tam olarak bunu yapar. Kodu kullanan kişi, arka planda hangi sınıfın çalıştığını bilmeden yalnızca sözleşmeye güvenerek metodu çağırabilir.
Bu mantık yalnızca C#'a özgü değildir; benzer soyutlama ilkeleri nesne yönelimli birçok dilde, farklı sözdizimleriyle de olsa aynı amaçla kullanılır. Örneğin Java'da da interface kavramı benzer bir sözleşme mantığıyla işler ve OOP'nin temel yapı taşlarından biridir; bu kavramı farklı bir dilde pekiştirmek isteyenler Java'da nesne yönelimli programlama dersleri kapsamında interface ve soyutlama konularını karşılaştırmalı olarak görebilir.
Interface ile Abstract Class Arasındaki Farklar
Interface ve abstract class çoğu zaman birbirinin yerine kullanılabilir gibi görünse de, aslında farklı problemleri çözmek için tasarlanmıştır. Aradaki temel farkları bir arada görmek, hangi durumda hangisinin mantıklı olduğunu anlamayı kolaylaştırır.
| Kriter | Interface | Abstract Class |
|---|---|---|
| Çoklu kalıtım desteği | Bir sınıf birden fazla interface uygulayabilir | Bir sınıf yalnızca tek bir abstract class'tan kalıtım alabilir |
| Ortak/somut kod barındırma | Yalnızca imza içerir, varsayılan olarak somut kod barındırmaz | Somut (gövdesi dolu) metotlar içerebilir, alt sınıflar bunları doğrudan miras alır |
| Alan (field) tanımlayabilme | Alan tanımlayamaz | Alan tanımlayabilir |
| Kurucu metot (constructor) | Kurucu metodu yoktur | Kurucu metodu olabilir, alt sınıflar bunu çağırabilir |
| Kullanım amacı | Farklı sınıflara ortak bir davranış sözleşmesi dayatmak | Ortak kod ve durumu paylaşan yakın akraba sınıflar oluşturmak |
| İlişki türü | "Can-do" (yapabilir) ilişkisi | "Is-a" (bir şeydir) ilişkisi |
Bu tablo genel bir çerçeve sunar; kesin bir "her zaman şunu kullan" kuralı yoktur. Sınıflarınız arasında gerçek bir akrabalık ve paylaşılacak somut kod varsa (örneğin ortak bir alan ya da hazır bir metot gövdesi), abstract class mantıksal olarak daha uygun bir seçenek olabilir. Buna karşılık birbiriyle akraba olmayan sınıflara ortak bir yetenek kazandırmak istiyorsanız — ya da sınıfınız zaten başka bir sınıftan kalıtım almışsa ve ek davranışlara ihtiyaç duyuyorsa — interface bu esnekliği sağlar. Seçim, projenin ihtiyacına ve sınıflar arasındaki gerçek ilişkiye göre şekillenmelidir.
Bir Sınıf Birden Fazla Interface'i Aynı Anda Uygulayabilir

C#'ta bir sınıf yalnızca tek bir sınıftan (veya abstract class'tan) kalıtım alabilir; dil, tek kalıtımı destekler. Ancak aynı sınıf, aynı anda birden fazla interface uygulayabilir. Bu, C#'ın çoklu kalıtımın getirdiği belirsizliklere düşmeden, bir nesneye birden fazla farklı yetenek kazandırmasının yoludur.
Aşağıdaki örnekte bir Duck sınıfı hem uçabilme hem de yüzebilme yeteneğini iki ayrı interface üzerinden kazanır:
public interface IFlyable
{
void Fly();
}
public interface ISwimmable
{
void Swim();
}
public class Duck : IFlyable, ISwimmable
{
public void Fly()
{
Console.WriteLine("Ördek kanatlarını çırparak uçuyor.");
}
public void Swim()
{
Console.WriteLine("Ördek suda yüzüyor.");
}
}
Burada Duck sınıfı, iki farklı davranış sözleşmesini aynı anda kabul etmiş olur. Eğer bu iki yetenek, gövdesi dolu iki ayrı sınıftan kalıtım yoluyla geliyor olsaydı, dil bu iki sınıftaki aynı isimli üyeler çakıştığında hangisinin geçerli olacağına karar veremeyebilirdi; bu duruma genellikle diamond problem (elmas problemi) adı verilir. C#'ın interface'lerde varsayılan olarak somut kod barındırmaması, bu tür isim ve davranış çakışmalarının önüne geçer: her sınıf, uyguladığı her interface'in metotlarını kendi gövdesiyle doldurmak zorunda olduğu için belirsizlik ortadan kalkar. Bu yaklaşım, çoklu kalıtımın esnekliğini korurken onun getirdiği karmaşıklıktan kaçınmanın pratik bir yoludur.
Interface Ne Zaman Tercih Edilmeli?
Bir interface kullanıp kullanmayacağınıza karar verirken sorulacak en pratik soru şudur: "Bu sınıfın nasıl çalıştığından bağımsız olarak, sadece ne yapabildiğiyle mi ilgileniyorum?" Cevap evetse, interface doğru araçtır. Aşağıdaki senaryolar bu kararı somutlaştırır:
- Bağımlılıkları gevşetme (loose coupling): Bir sınıf, başka bir sınıfın somut implementasyonuna değil, bir interface'e bağımlı olduğunda, o bağımlılığı değiştirmek çok daha kolay hale gelir. Örneğin bir sipariş servisi, doğrudan
EmailServicesınıfına değilINotificationServicearayüzüne bağlıysa, e-posta yerine SMS veya push bildirim eklemek mevcut kodu bozmaz. - Test edilebilirlik: Gerçek bir veritabanı veya dış servis çağrısı yapmadan test yazmak istediğinizde, interface sayesinde sahte (mock) bir nesne üretip gerçek sınıfın yerine geçirebilirsiniz. Bu, birim testlerinin hızlı ve öngörülebilir çalışmasını sağlar.
- Farklı sınıfları ortak bir tip altında yönetme: Birbirinden tamamen farklı yapıdaki sınıfları tek bir listede veya parametrede toplamak istediğinizde interface devreye girer; her biri kendi mantığını çalıştırır ama dışarıya aynı sözleşmeyi sunar.
Gerçek hayattan örnek vermek gerekirse, loglama servisleri genellikle ILogger gibi bir arayüz üzerinden tasarlanır; konsola, dosyaya veya uzak bir sunucuya log yazan farklı sınıflar aynı arayüzü uygular ve uygulama kodu hangi loglama yönteminin kullanıldığını bilmek zorunda kalmaz. Benzer şekilde bildirim gönderme servisleri de e-posta, SMS ve anlık bildirim gibi farklı kanalları tek bir INotificationService arayüzü altında toplayarak esnek bir yapı kurar.
Bu tür soyutlama kararları, özellikle OOP'ye yeni başlayanlar için kâğıt üzerinde basit görünse de gerçek bir proje içinde nereye interface, nereye somut sınıf koyulacağı konusunda kafa karıştırabilir. Böyle noktalarda kod üzerinden birebir geri bildirim almak öğrenme sürecini büyük ölçüde hızlandırır; bu ihtiyaç duyulduğunda birebir C# ve OOP dersleri ile tasarım kararlarınızı adım adım gözden geçirebilirsiniz.
Özgün Örnek: IPayment Arayüzü ile Esnek Ödeme Sistemi Tasarımı

Interface mantığını somutlaştırmanın en etkili yolu, gerçek bir senaryo üzerinden ilerlemektir. Bir e-ticaret uygulamasında farklı ödeme yöntemlerini destekleyen bir yapı kuracağımızı varsayalım. Amacımız, yeni bir ödeme sağlayıcısı eklendiğinde mevcut kodun mümkün olduğunca az değişmesini sağlamak. Bunu şu adımlarla kurabiliriz:
- IPayment interface'ini tanımla: Her ödeme yönteminin sahip olması gereken tek bir davranışı, örneğin
ProcessPaymentmetodunu, arayüz içinde imza olarak belirleriz. - CreditCardPayment sınıfını yaz: Bu sınıf
IPayment'ı uygular ve kredi kartı ile ödeme alma mantığını kendi içinde barındırır. - PaypalPayment sınıfını yaz: Aynı arayüzü uygulayan ikinci bir sınıf olarak, farklı bir ödeme akışını kendi içinde yönetir; dışarıya sunduğu sözleşme birinciyle aynıdır.
- Bu sınıfları IPayment tipi üzerinden çağıran bir yapı kur: Ödemeyi işleyen kod, parametre olarak somut sınıfları değil
IPaymenttipini kabul eder; hangi sınıfın geldiğini bilmedenProcessPaymentmetodunu çağırır. - Yeni bir ödeme yöntemi eklemenin mevcut kodu bozmadan nasıl yapılabileceğini göster: Örneğin bir
BankTransferPaymentsınıfı eklemek istediğinizde, sadeceIPayment'ı uygulayan yeni bir sınıf yazmanız yeterlidir; ödeme işleyen mevcut metotta tek satır bile değiştirmeniz gerekmez.
Bu yaklaşımın gerçek bir projede sağladığı en büyük fayda, yeni bir ödeme sağlayıcısı eklerken mevcut ve test edilmiş kodun dokunulmadan kalmasıdır. Bu mantık, nesne yönelimli tasarımda sıkça anılan "genişlemeye açık, değişikliğe kapalı" ilkesine yakın bir çalışma biçimidir: sistem yeni davranışlar için açık, ama mevcut davranışları bozacak müdahalelere kapalıdır. Interface burada bir çeşit sabit nokta görevi görür; üzerine yeni sınıflar eklenir ama sözleşme değişmez.
Tam Kod Örneği: IPayment, CreditCardPayment, PaypalPayment ve Polimorfik Kullanım
Yukarıda anlatılan adımları tek bir çalışan örnek üzerinde görmek, kavramı kalıcı hale getirir. Aşağıdaki kod, IPayment arayüzünü, iki farklı implementasyonunu ve bu sınıfları ortak tip üzerinden çağıran polimorfik kullanımı gösterir:
public interface IPayment
{
void ProcessPayment(decimal amount);
}
public class CreditCardPayment : IPayment
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"Kredi kartı ile {amount} TL tahsil edildi.");
}
}
public class PaypalPayment : IPayment
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"Paypal ile {amount} TL tahsil edildi.");
}
}
public class Program
{
static void Handle(IPayment payment, decimal amount)
{
payment.ProcessPayment(amount);
}
static void Main()
{
Handle(new CreditCardPayment(), 250);
Handle(new PaypalPayment(), 180);
}
}
Bu örnekte Handle metodu, parametre olarak CreditCardPayment veya PaypalPayment yerine sadece IPayment tipini bekler. Metot çalıştığında hangi sınıfın örneği verilmişse o sınıfın ProcessPayment implementasyonu devreye girer; bu da polimorfizmin tam olarak işlediği noktadır. Yeni bir ödeme sınıfı eklemek istediğinizde Handle metoduna hiç dokunmadan, sadece IPayment'ı uygulayan yeni bir sınıf yazmanız yeterli olur.
Interface Kullanırken Sık Yapılan Hatalar
Interface kavramı öğrenildikten sonra sık görülen bir eğilim, onu her yerde kullanma isteğidir. Oysa arayüzler amaç değil araçtır; doğru yerde kullanılmadığında kodu sadeleştirmek yerine gereksiz yere karmaşıklaştırır. Yeni başlayan geliştiricilerin en çok tekrarladığı hatalar şunlardır:
- Her sınıfı, gerçek bir ihtiyaç olmadan bir interface'e bağlamaya çalışmak; bu durumda proje küçük bir uygulama için bile gereksiz sayıda dosya ve katmandan oluşur.
- Tek bir implementasyonu olacağı baştan belli olan yapılarda bile interface açmak; örneğin projede tek bir ödeme yöntemi kullanılacaksa
IPaymentgibi bir soyutlama şimdilik fazladan bir katmandır. - Aşırı soyutlama yaparak kodun okunabilirliğini düşürmek; bir metodun gerçek davranışını görmek için üç dört farklı dosya arasında gidip gelmek zorunda kalmak, basit bir işlemi bile anlaşılması güç hâle getirir.
- Interface'i sadece "iyi bir pratik gibi göründüğü için" kullanmak, hangi problemi çözdüğünü düşünmeden koda eklemek.
Bu hataların ortak noktası, soyutlamanın gerçek bir ihtiyaçtan değil, alışkanlıktan doğmasıdır. Basit bir kural işe yarar: Eğer aynı davranışın gerçekten birden fazla farklı şekilde uygulanması bekleniyorsa (örneğin birden fazla ödeme yöntemi, birden fazla bildirim kanalı, birden fazla veri kaynağı) interface düşünmek mantıklıdır. Aksi hâlde önce somut bir sınıfla ilerlemek, ihtiyaç ortaya çıktığında interface'e geçiş yapmak çok daha sağlıklı bir yoldur. C# projelerinde soyutlama kararı genellikle sonradan, kod büyüdükçe netleşir; baştan aşırı planlama yapmak yerine kodun gerçek ihtiyaçlarını gözlemlemek daha güvenilir bir yaklaşımdır.
Bu tür tasarım kararlarını daha fazla örnek üzerinden görmek, hatanın nerede yapıldığını fark etmeyi kolaylaştırır. Bu noktada C# ve nesne yönelimli programlama üzerine hazırlanan yazı dizisi, farklı senaryolarda interface ve sınıf tasarımı kararlarının nasıl verildiğini adım adım incelemek isteyenler için pratik bir kaynak niteliği taşır.
Interface Bilginizi Pekiştirmek İçin Sonraki Adımlar
Buraya kadar anlatılanları özetlemek gerekirse: interface, bir sınıfın ne yapabileceğine dair bir sözleşme tanımlar ve uygulama detayını sınıfın kendisine bırakır. Bir sınıf aynı anda birden fazla interface'i uygulayarak farklı yeteneklere sahip olabilir, bu da C#'ta tek kalıtımın getirdiği sınırlamayı aşmanın doğal yoludur. Ayrıca interface kullanımı, kodun birbirine sıkı sıkıya bağlı olmadan (gevşek bağımlılıkla) çalışmasını sağlar; bir metot somut bir sınıfa değil, o sınıfın uyduğu sözleşmeye bağımlı olur. Bu sayede yeni bir davranış eklemek, mevcut kodu bozmadan mümkün hâle gelir.
Bu kavramları okuyarak anlamak önemli bir adımdır, ancak gerçek pekişme genellikle küçük alıştırmalar yaparak ve kendi hatalarınızı görerek oluşur. Bir interface tanımlayıp iki farklı sınıfla uygulamayı denemek, ardından bu sınıfları polimorfik bir listede kullanmaya çalışmak, konuyu teoriden pratiğe taşımanın en doğrudan yoludur. Bu aşamada nerede eksik olduğunuzu görmek isterseniz, C# bilgi seviyenizi ölçen kısa test interface, kalıtım ve temel OOP kavramlarındaki hakimiyetinizi hızlıca değerlendirmenize yardımcı olabilir.
Interface ve abstract class arasındaki farkı kavramak, C#'ta ilerledikçe karşınıza çıkacak birçok tasarım kararını daha rahat anlamanızı sağlayacaktır. Önemli olan bu kavramları ezberlemek değil, hangi durumda hangi aracın işe yaradığını küçük örnekler üzerinden görebilmektir; bu anlayış zamanla ve pratikle kendiliğinden yerleşir.
Sık Sorulan Sorular
C#'ta interface ile abstract class aynı sınıfta birlikte kullanılabilir mi?
Evet, bir sınıf aynı anda bir abstract class'tan miras alabilir ve bunun yanında bir veya birden fazla interface'i uygulayabilir. C#'ta bir sınıfın yalnızca tek bir sınıftan (abstract dahil) türeyebilmesine karşın, istediği kadar interface uygulayabilmesi bu iki yapının birlikte kullanılmasını yaygın bir tasarım deseni hâline getirir.
Interface içinde metot gövdesi (kod) yazılabilir mi?
Interface'in temel mantığı, yalnızca metotların imzasını (adını, parametrelerini ve dönüş tipini) tanımlamak, gövdesini ise uygulayan sınıfa bırakmaktır. Bu nedenle interface tasarlarken varsayılan olarak gövde yazılmaz; asıl amaç "ne yapılacağını" değil "ne yapılabileceğini" tanımlamaktır.
Bir sınıf aynı anda kaç tane interface uygulayabilir?
Bir sınıfın uygulayabileceği interface sayısında C# dilinde katı bir üst sınır yoktur; bir sınıf ihtiyaç duyduğu kadar farklı interface'i aynı anda uygulayabilir. Bu, çoklu kalıtımın getirdiği esnekliği interface'ler üzerinden sağlayan temel özelliktir.
Interface kullanmak programın çalışma performansını etkiler mi?
Interface kullanımı, günlük ölçekteki uygulamalarda fark edilir bir performans kaybına yol açmaz; asıl etkisi kodun tasarımı ve esnekliği üzerinedir. Performans kaygısıyla interface kullanımından kaçınmak yerine, tasarım ihtiyacına göre karar vermek daha doğru bir yaklaşımdır.
Hangi durumda abstract class, hangi durumda interface tercih edilmeli?
Ortak bir davranış hem tanımlanacak hem de bir kısmı hazır kod olarak paylaşılacaksa abstract class daha uygundur. Farklı sınıfların yalnızca ortak bir "ne yapabildiği" sözleşmesine ihtiyacı varsa ve bu sınıflar birbirinden tamamen bağımsız kalıtım hiyerarşilerinden geliyorsa interface tercih edilmelidir.
Interface isimlerinin başına neden 'I' harfi konur?
Bu, C# topluluğunda yaygın kabul görmüş bir isimlendirme alışkanlığıdır ve bir interface'i sınıflardan hızlıca ayırt etmeyi kolaylaştırır. Zorunlu bir dil kuralı değildir, ancak kodun okunabilirliğini artırdığı için yaygın olarak benimsenmiştir.
C# öğrenirken interface kavramını pekiştirmek için nasıl pratik yapmalıyım?
En etkili yöntem, küçük ve gerçekçi senaryolar üzerinden kendi interface'lerinizi tanımlayıp birden fazla sınıfla uygulamaktır. Ödeme yöntemleri, bildirim kanalları veya farklı veri kaynakları gibi günlük hayattan örnekler, soyutlamanın neden gerekli olduğunu somut biçimde görmenizi sağlar.
Interface ve abstract class arasındaki farkları anlamak, C#'ta sağlam bir OOP temeli kurmanın önemli adımlarından biridir. Konuyu daha geniş bir bağlamda görmek isterseniz C# ve nesne yönelimli programlama konularını içeren video eğitimleri incelemek, öğrendiklerinizi düzenli bir müfredat içinde pekiştirmenize yardımcı olabilir.