Başka hiç kimse bu cevabı açıkça vermediği için aşağıdakileri ekleyeceğim:
Bir yapı üzerine bir arayüz uygulamanın hiçbir olumsuz sonucu yoktur.
Bir yapıyı tutmak için kullanılan arabirim türünün herhangi bir değişkeni , kullanılan yapının kutulu bir değeriyle sonuçlanacaktır. Yapı değişmez ise (iyi bir şey), o zaman bu en kötü ihtimalle bir performans sorunudur:
- ortaya çıkan nesneyi kilitleme amacıyla kullanmak (herhangi bir şekilde son derece kötü bir fikir)
- referans eşitliği semantiğini kullanarak ve aynı yapıdan iki kutulu değer için çalışmasını bekleyerek.
Bunların her ikisi de olası değildir, bunun yerine aşağıdakilerden birini yapmanız olasıdır:
Jenerikler
Arayüzleri uygulayan yapıların belki de birçok makul nedeni , kısıtlamalarla genel bir bağlam içinde kullanılabilmeleridir . Bu şekilde kullanıldığında değişken şöyle:
class Foo<T> : IEquatable<Foo<T>> where T : IEquatable<T>
{
private readonly T a;
public bool Equals(Foo<T> other)
{
return this.a.Equals(other.a);
}
}
- Yapının bir tür parametresi olarak kullanımını etkinleştirin
new()
veya benzeri başka bir kısıt class
kullanılmadığı sürece .
- Bu şekilde kullanılan yapılarda kutudan kaçınılmasına izin verin.
O zaman this.a bir arayüz referansı DEĞİLDİR, bu yüzden içine yerleştirilen şeyin bir kutusuna neden olmaz. Ayrıca, c # derleyici genel sınıfları derlediğinde ve Type parametresi T'nin örneklerinde tanımlanan örnek yöntemlerinin çağrılarını eklemesi gerektiğinde, kısıtlanmış işlem kodunu kullanabilir :
ThisType bir değer türü ise ve thisType yöntemi uygularsa, ptr, thisType tarafından yöntemin uygulanması için bir çağrı yöntemi talimatına 'this' işaretçisi olarak değiştirilmemiş olarak iletilir.
Bu, kutulamayı önler ve değer türü arabirim uyguladığından, yöntemi uygulamak zorundadır , böylece kutulama meydana gelmez. Yukarıdaki örnekte Equals()
çağrı, bunun üzerinde kutu olmadan yapılır. A 1 .
Düşük sürtünmeli API'ler
Çoğu yapı, bitsel özdeş değerlerin eşit 2 olarak kabul edildiği ilkel benzeri semantiğe sahip olmalıdır . Çalışma zamanı, bu tür davranışları örtük olarak sağlayacaktır, Equals()
ancak bu yavaş olabilir. Ayrıca, bu örtülü eşitlik edilir değil bir uygulama olarak ortaya IEquatable<T>
ve böylece açıkça kendileri uygulamadığınız sürece yapılar Sözlükler tuşları olarak kolayca kullanılıyor önler. Bu nedenle, birçok genel yapı türünün uyguladıklarını bildirmesi yaygındır IEquatable<T>
(neredeT
bunu daha kolay ve daha iyi performans göstermesinin yanı sıra CLR BCL içindeki birçok mevcut değer türünün davranışıyla tutarlı hale getirmek için kendileri .
BCL'deki tüm ilkeller minimumda uygular:
IComparable
IConvertible
IComparable<T>
IEquatable<T>
(Ve böylece IEquatable
)
Birçoğu da uygular IFormattable
, DateTime, TimeSpan ve Guid gibi Sistem tarafından tanımlanan değer türlerinin birçoğunu da uygular, bunların birçoğunu veya tamamını da uygular. Karmaşık sayı yapısı veya bazı sabit genişlikli metin değerleri gibi benzer şekilde 'yaygın olarak kullanışlı' bir tür uyguluyorsanız, bu ortak arabirimlerin çoğunu (doğru şekilde) uygulamak, yapınızı daha kullanışlı ve kullanışlı hale getirecektir.
İstisnalar
Açıkçası, eğer arayüz güçlü bir şekilde değişkenliği (örneğin ICollection
) ima ediyorsa, o zaman bunu uygulamak kötü bir fikirdir, çünkü yapıyı değiştirilebilir hale getirmişsinizdir (değişikliklerin orijinal yerine kutulu değerde meydana geldiği halihazırda açıklanan türden hatalara yol açar. ) veya gibi yöntemlerin sonuçlarını görmezden gelerek Add()
veya istisnalar atarak kullanıcıların kafasını karıştırırsınız .
Çoğu arayüz, değişkenliği (gibi IFormattable
) ifade ETMEZ ve belirli işlevselliği tutarlı bir şekilde ortaya çıkarmanın deyimsel bir yolu olarak hizmet eder. Genellikle yapının kullanıcısı, bu tür bir davranış için herhangi bir boks ek yükünü önemsemeyecektir.
Özet
Makul bir şekilde, değişmez değer türlerinde yapıldığında, kullanışlı arayüzlerin uygulanması iyi bir fikirdir
Notlar:
1: Derleyicinin bunu, belirli bir yapı türünde olduğu bilinen , ancak bir sanal yöntemi çağırması gereken değişkenler üzerinde sanal yöntemleri çağırırken kullanabileceğini unutmayın . Örneğin:
List<int> l = new List<int>();
foreach(var x in l)
;
List tarafından döndürülen numaralandırıcı, listeyi numaralandırırken ayırmayı önlemek için bir optimizasyon olan bir yapıdır (Bazı ilginç sonuçlarla ). Ancak foreach semantik listeleyicisi aletlerin eğer belirtmek IDisposable
sonraDispose()
yineleme tamamlandıktan çağrılacağını . Açıkçası, bunun kutulu bir çağrı yoluyla gerçekleşmesi, numaralandırıcının bir yapı olmasının herhangi bir faydasını ortadan kaldıracaktır (aslında daha kötü olurdu). Daha da kötüsü, eğer dispose çağrısı numaralandırıcının durumunu bir şekilde değiştirirse, bu durum kutulu örnekte olur ve karmaşık durumlarda pek çok ince hata ortaya çıkabilir. Bu nedenle, bu tür bir durumda yayılan IL:
IL_0001: newobj System.Collections.Generic.List..ctor
IL_0006: stloc.0
IL_0007: hayır
IL_0008: ldloc.0
IL_0009: callvirt System.Collections.Generic.List.GetEnumerator
IL_000E: stloc.2
IL_000F: br.s IL_0019
IL_0011: ldloca.s 02
IL_0013: System.Collections.Generic.List.get_Current çağrısı
IL_0018: stloc.1
IL_0019: ldloca.s 02
IL_001B: System.Collections.Generic.List.MoveNext çağrısı
IL_0020: stloc.3
IL_0021: ldloc.3
IL_0022: brtrue.s IL_0011
IL_0024: ayrılın. S IL_0035
IL_0026: ldloca.s 02
IL_0028: kısıtlı. System.Collections.Generic.List.Enumerator
IL_002E: callvirt System.IDisposable.Dispose
IL_0033: hayır
IL_0034: son olarak
Dolayısıyla, IDisposable'ın uygulanması herhangi bir performans sorununa neden olmaz ve Dispose yöntemi gerçekten bir şey yaparsa numaralandırıcının (üzücü) değiştirilebilir yönü korunur!
2: double ve float, NaN değerlerinin eşit kabul edilmediği bu kuralın istisnalarıdır.