Bir interface bir sözleşmedir: bir tipin sağlamayı vaat ettiği üyelerin adlandırılmış bir listesi. Bir tipin ne yapabileceğini söyler, nasıl yapacağı hakkında hiçbir şey söylemez. Class'lar ve struct'lar interface'leri uygular ve interface'e karşı yazılmış kod, sonradan yazılanlar dahil her uygulamayla çalışır.
Bir interface bildirmek ve uygulamak
Bir interface üyeleri gövdesiz bildirir. Geleneksel olarak adı I ile başlar. Bir sınıf onu iki noktadan sonra listeleyip her biri için public bir üye sağlayarak uygular:
Çıktı:
15% off: 68
10 off: 70
Best for 80: 68
Best for 40: 30
BestPrice, PercentOff ya da FixedOff hakkında hiçbir şey bilmez. Yalnızca IDiscount'a bağımlıdır, bu yüzden sonradan bir BuyOneGetOne sınıfı eklemek onda hiçbir değişiklik gerektirmez. Interface'lerin var olmasının tüm nedeni bu ayrıştırmadır.
Bildirimle ilgili birkaç kural:
- Interface üyeleri varsayılan olarak public'tir ve uygulayan üyeler de public olmalıdır (aşağıdaki gibi açıkça uygulanmadıkça).
- Bir interface metotlar, property'ler, indeksleyiciler ve event'ler bildirebilir. Örnek alanları ya da constructor'lar bildiremez.
- Bir üyeyi atlayan sınıf derlenmez: CS0535 hatası, "'FixedOff' does not implement interface member 'IDiscount.Apply(decimal)'".
- Bir interface'i
newile oluşturamazsınız. Uygulayan bir sınıf oluşturur ve onu interface tipindeki bir değişkende tutabilirsiniz:IDiscount d = new FixedOff(5);.
Tip olarak interface
Bir değişken, parametre, alan ya da dönüş tipi bir interface olarak bildirilebilir. Bu durumda onu uygulayan her nesneyi kabul eder ve yalnızca interface'in üyelerini açar. Hangi uygulamayı aldığını umursamayan kod böyle yazılır ve testler sahte bir sürümü böyle yerine koyar:
public class Checkout
{
private readonly IPaymentGateway gateway; // not StripeGateway, not PayPalGateway
public Checkout(IPaymentGateway gateway) { this.gateway = gateway; }
public bool Pay(decimal amount) => gateway.Charge(amount);
}
// Production: new Checkout(new StripeGateway(apiKey))
// Unit test: new Checkout(new FakeGateway(alwaysSucceeds: true))
ASP.NET Core'daki dependency injection container'ları bunun üzerine kuruludur: servisler interface'e göre kaydedilir ve istenir.
Birkaç interface uygulamak
Bir sınıfın en fazla bir temel sınıfı vardır ama istediği sayıda interface uygulayabilir. Önce temel sınıf, sonra virgülle ayrılmış interface'ler:
Çıktı:
INVOICE Studio rent March: 950.00
True
True
Interface'ler başka interface'lerden de kalıtım alabilir: interface IRepository<T> : IReadRepository<T> kalıttıklarına üyeler ekler ve IRepository<T>'yi uygulayan bir sınıf iki kümeyi de sağlamalıdır.
Açık interface uygulaması
Bazen iki interface aynı adda ve farklı anlamda bir üye bildirir ya da bir interface üyesinin sınıfın kendi public yüzeyinde bir anlamı yoktur. Onu interface adını öne ekleyip erişim belirleyicisini yazmayarak açıkça uygulayın:
Çıktı:
Desk lamp,34.90
Desk lamp for 34.90
Açıkça uygulanmış bir üyeye yalnızca interface üzerinden ulaşılabilir. Framework bunu yaygın tiplerdeki kalabalığı azaltmak için kullanır: diziler IList.Add'i açıkça uygular (diziler sabit boyutlu olduğu için NotSupportedException fırlatır), bu yüzden array. yazdığınızda metot görünmez.
Uygulayacağınız framework interface'leri
Standart bir interface'i uygulamak tipinizi mevcut framework koduna bağlar. Üçü sürekli karşınıza çıkar.
IComparable<T> bir tipe doğal bir sıra verir; List<T>.Sort(), Array.Sort ve Max() bunu kullanır. CompareTo negatif bir sayı, sıfır ya da pozitif bir sayı döndürür:
Çıktı:
1.4, 2.9, 2.10, 10.0
Aynı sürümleri string olarak sıralamak 1.4, 10.0, 2.10, 2.9 verirdi. IComparable<T> olmadan Sort() InvalidOperationException fırlatır, çünkü iki Version nesnesini karşılaştırmanın bir yolu yoktur.
IEnumerable<T> bir tipi foreach'te ve LINQ ile kullanılabilir yapar. GetEnumerator'ı uygulamanın kolay yolu, IEnumerable ve yield sayfasında ele alınan yield return'dür.
IDisposable serbest bırakılması gereken bir şey (bir dosya tanıtıcısı, bir bağlantı, bir timer) tutan bir tipi işaretler. Tek metodu Dispose, using ifadesinin blok bittiğinde, bir istisna fırlatılsa bile çağırdığı şeydir:
Çıktı:
open sales.txt
write to sales.txt: March total: 12400
close sales.txt
after using
Varsayılan interface metotları (C# 8)
Yayımlanmış bir interface'e üye eklemek eskiden onu uygulayan her sınıfı bozardı. C# 8'den beri bir interface üyesinin, uygulayan sınıfların kendi sürümlerini sağlamadıkça kalıtım aldığı bir gövdesi olabilir:
public interface ILogger
{
void Write(string message);
// New in version 2 of the library. Existing implementers keep compiling.
void Error(string message) => Write("ERROR: " + message);
}
public class ConsoleLogger : ILogger
{
public void Write(string message) => Console.WriteLine(message);
}
ILogger log = new ConsoleLogger();
log.Error("disk full"); // ERROR: disk full
var direct = new ConsoleLogger();
// direct.Error("x"); // does not compile: the default method belongs to the interface
İnsanları şaşırtan kısım son satırdır: varsayılan bir metot sınıfın kendi üyelerine kalıtılmaz, bu yüzden yalnızca interface tipi üzerinden çağrılabilir. Varsayılan metotlar örnek alanlarını da kullanamaz (interface'lerin hiç yoktur); diğer interface üyeleri üzerinden çalışırlar. .NET Core 3.0 ya da sonrasını gerektirirler ve .NET Framework'te kullanılamazlar.
C# 8 interface'lerde statik üyelere de izin verdi ve C# 11, generic kodun T üzerinde statik bir metot ya da operatör çağırmasını sağlayan static abstract üyeleri ekledi. .NET 7'deki generic math bunun üzerine kuruludur (INumber<T>, where T : INumber<T>).
Interface mi, abstract sınıf mı?
İlgisiz tiplerin paylaşabileceği bir yetenek için, struct'lar katılmalıysa ya da bir sınıfın böyle birkaç role ihtiyacı varsa interface kullanın. Uygulamalar tek bir şeyin varyasyonlarıysa ve durumu ya da sabit bir algoritmayı paylaşıyorsa abstract bir sınıf kullanın. Abstract sınıflar sayfasında yan yana bir tablo vardır. Birçok tasarım ikisini birden kullanır: tüketiciler için bir interface ve uygulayanlar için sıkıcı kısımları uygulayan abstract bir temel sınıf.
Yaygın hatalar
- Eksik ya da public olmayan uygulama. Her interface üyesi eşleşen imzalı public bir üyeye ya da açık bir uygulamaya ihtiyaç duyar. Aksi halde CS0535 (ya da metot var ama public değilse CS0737).
- "Ne olur ne olmaz" diye tek uygulamalı interface'ler. Bir interface birkaç uygulama ya da bir test sahtesi olduğunda yerini hak eder. Aksi halde gezinilecek bir dosya daha demektir.
- Şişman interface'ler. Yirmi üyeli bir interface her uygulayanı yirmi üye yazmaya zorlar. Onu sınıfların birleştirdiği daha küçük rollere (
IReader,IWriter) bölün. - Bir interface'i tekrar bir sınıfa cast etmek.
((StripeGateway)gateway).Refund()ayrıştırmayı geri alır. ÇağıranlarınRefund'a ihtiyacı varsa o interface'e aittir. - Varsayılan bir interface metodunu sınıf üzerinde çağırmayı beklemek. Ona interface tipi üzerinden ulaşılır.
Sıkça Sorulan Sorular
C#'ta interface nedir?
Interface, bir tipin sağlamayı vaat ettiği, örnek durumu olmayan adlandırılmış bir üye kümesidir (metotlar, property'ler, event'ler, indeksleyiciler). interface IDiscount { decimal Apply(decimal price); } "IDiscount olan her şey kendini bir fiyata uygulayabilir" der. Class'lar ve struct'lar onu iki nokta ile uygular ve kod bundan sonra interface tipi üzerinden herhangi biriyle çalışabilir.
C#'ta bir interface nasıl uygulanır?
Onu sınıf bildiriminde iki noktadan sonra (varsa temel sınıftan sonra) listeleyin ve her interface üyesi için public bir üye sağlayın: class HolidayDiscount : IDiscount { public decimal Apply(decimal price) => price * 0.9m; }. Eksik bir üye CS0535 hatasıdır. Visual Studio ve Rider bir hızlı düzeltmeyle taslakları üretebilir.
Bir C# sınıfı birden fazla interface uygulayabilir mi?
Evet, gerektiği kadar, virgülle ayırarak: class Invoice : IPrintable, IComparable<Invoice>, IDisposable. Bu, C#'ın çoklu kalıtıma cevabıdır: bir sınıfın tek bir temel sınıfı vardır ama birçok rol oynayabilir. İki interface aynı üyeyi bildirirse tek bir public metot ikisini de karşılayabilir ya da her birine kendi üyesini vermek için açık uygulama kullanabilirsiniz.
C#'ta açık interface uygulaması (explicit implementation) nedir?
Bir üyeyi önünde interface adıyla ve erişim belirleyicisi olmadan uygulamaktır: string IExportable.Format() { ... }. Üye bundan sonra sınıf üzerinden değil, yalnızca interface tipindeki bir değişken üzerinden çağrılabilir. İki interface arasındaki ad çakışmalarını çözmek ve nadiren kullanılan interface üyelerini sınıfın public yüzeyinden uzak tutmak için kullanılır.
C#'ta varsayılan interface metotları nedir?
C# 8'den beri bir interface üyesinin gövdesi olabilir: void Error(string m) => Write("ERROR: " + m);. Uygulayan sınıflar onu bedavaya alır ve kendi sürümlerini sağlayabilir. Özellik, kütüphane yazarlarının yayımlanmış bir interface'e her uygulayanı bozmadan üye ekleyebilmesi için vardır. Varsayılan bir metoda sınıf üzerinden değil interface tipi üzerinden ulaşılır.
C# interface adları neden I ile başlar?
Bu .NET adlandırma geleneğidir (IEnumerable, IDisposable, IComparable<T>): büyük bir I ve ardından PascalCase bir ad, çoğu zaman "-able" ile biten bir sıfat. Derleyici bunu zorunlu tutmaz, ama izlemek interface tiplerini, özellikle temel sınıf ile interface'lerin tek bir listeyi paylaştığı bir sınıf bildiriminde, bir bakışta tanınır kılar.