C # dizileri IList <T> 'yi kısmen nasıl uygular?


99

Bildiğiniz gibi, IList<T>diğer arayüzlerin yanı sıra C # dizileri de uygular . Her nasılsa, bunu Count özelliğini herkese açık bir şekilde uygulamadan yapıyorlar IList<T>! Dizilerin yalnızca Uzunluk özelliği vardır.

Bu C # /. NET'in arabirim uygulamasıyla ilgili kendi kurallarını çiğnemesinin bariz bir örneği mi yoksa bir şeyi mi kaçırıyorum?


2
Hiç kimse Arraysınıfın C # ile yazılması gerektiğini söylemedi !
user541686

ArrayC # ya da .net'i hedefleyen başka bir dil ile uygulanamayan "sihirli" bir sınıftır. Ancak bu belirli özellik C # 'da mevcuttur.
CodesInChaos

Yanıtlar:


81

Hans'ın cevabının ışığında yeni cevap

Hans'ın verdiği cevap sayesinde uygulamanın düşündüğümüzden biraz daha karmaşık olduğunu görebiliyoruz. Hem derleyici hem de CLR , bir dizi türünün uyguladığı izlenimini vermek için çokIList<T> çabalar - ancak dizi varyansı bunu daha zor hale getirir. Hans'ın cevabının tersine, dizi türleri (tek boyutlu, sıfır tabanlı) genel koleksiyonları doğrudan uygular, çünkü herhangi bir belirli dizinin türü değildir System.Array - bu yalnızca dizinin temel türüdür. Bir dizi türüne hangi arabirimleri desteklediğini sorarsanız, genel türleri içerir:

foreach (var type in typeof(int[]).GetInterfaces())
{
    Console.WriteLine(type);
}

Çıktı:

System.ICloneable
System.Collections.IList
System.Collections.ICollection
System.Collections.IEnumerable
System.Collections.IStructuralComparable
System.Collections.IStructuralEquatable
System.Collections.Generic.IList`1[System.Int32]
System.Collections.Generic.ICollection`1[System.Int32]
System.Collections.Generic.IEnumerable`1[System.Int32]

Tek boyutlu, sıfır tabanlı diziler için, dil söz konusu olduğunda, dizi gerçekten de uygular IList<T>. C # spesifikasyonunun 12.1.2 Bölümü öyle söylüyor. Bu nedenle, temel uygulama ne yaparsa yapsın, dil, başka herhangi bir arabirimde olduğu gibi uygulama türü gibi davranmalıdır . Bu açıdan bakıldığında, arayüz olan bazı üyelerinin açıkça (gibi uygulanıyor ile uygulanan ). Neler olup bittiğinin dil düzeyinde en iyi açıklaması budur.T[]IList<T>Count

Bunun yalnızca tek boyutlu diziler için geçerli olduğuna dikkat edin (ve sıfır tabanlı diziler, bir dil olarak C # 'ın sıfır tabanlı olmayan diziler hakkında bir şey söylediğini değil). T[,] gelmez uygulamak IList<T>.

CLR açısından daha eğlenceli bir şey oluyor. Genel arabirim türleri için arabirim eşlemesini alamazsınız. Örneğin:

typeof(int[]).GetInterfaceMap(typeof(ICollection<int>))

Bir istisna verir:

Unhandled Exception: System.ArgumentException: Interface maps for generic
interfaces on arrays cannot be retrived.

Öyleyse neden tuhaflık? Bunun gerçekten, IMO tip sistemindeki bir siğil olan dizi kovaryansından kaynaklandığına inanıyorum. Olsa IList<T>olduğu değil (ve güvenli olamaz) kovaryant, dizi kovaryans çalışmalarına bu sağlar:

string[] strings = { "a", "b", "c" };
IList<object> objects = strings;

... bu da gerçekten olmadığında, alet gibi görünmesini sağlıyor .typeof(string[])IList<object>

CLI spesifikasyonu (ECMA-335) bölüm 1, bölüm 8.7.1, şuna sahiptir:

Bir imza tipi T uyumludur - ancak ve ancak aşağıdaki durumlardan en az biri tutuyorsa U imza tipi ile uyumludur

...

T, bir sıfır temel seviye-1 dizidir V[]ve Uolduğunu IList<W>, ve V, W ile bir dizi element uyumlu-olduğu

(Aslında bahsetmiyor ICollection<W>veya IEnumerable<W>spesifikasyonda bir hata olduğuna inanıyorum.)

Varyans olmaması için, CLI spesifikasyonu doğrudan dil spesifikasyonuyla birlikte gider. Bölüm 1'in 8.9.1 bölümünden:

Ek olarak, öğe türü T ile oluşturulan bir vektör, arabirimi uygular System.Collections.Generic.IList<U>, burada U: = T (§8.7)

(Bir vektör , sıfır tabanlı tek boyutlu bir dizidir.)

Şimdi, uygulama ayrıntıları açısından , CLR burada atama uyumluluğunu korumak için bazı ilginç haritalama yapıyor: a'nın string[]uygulanması istendiğinde ICollection<object>.Count, bunu oldukça normal bir şekilde halledemez . Bu, açık bir arayüz uygulaması olarak sayılır mı? Arayüz haritalamasını doğrudan istemediğiniz sürece, bu şekilde davranmanın mantıklı olduğunu düşünüyorum, her zaman dil açısından bu şekilde davranır .

Peki ya ICollection.Count?

Şimdiye kadar jenerik arayüzlerden bahsettim, ancak bir de genel olmayan özelliği ICollectionile birlikte var Count. Bu sefer arayüz haritalamasını elde edebiliriz ve aslında arayüz doğrudan uygulanmaktadır System.Array. ICollection.CountÖzellik uygulamasına yönelik belgeler Array, açık arabirim uygulamasıyla uygulandığını belirtir.

Bu tür bir açık arabirim uygulamasının "normal" açık arabirim uygulamasından farklı olduğu bir yol düşünebilen biri varsa, onu daha ayrıntılı incelemekten memnuniyet duyarım.

Açık arabirim uygulamasıyla ilgili eski yanıt

Dizilerin bilgisi nedeniyle daha karmaşık olan yukarıdakilere rağmen, açık arabirim uygulaması yoluyla aynı görünür efektlerle bir şeyler yapabilirsiniz .

İşte basit bir bağımsız örnek:

public interface IFoo
{
    void M1();
    void M2();
}

public class Foo : IFoo
{
    // Explicit interface implementation
    void IFoo.M1() {}

    // Implicit interface implementation
    public void M2() {}
}

class Test    
{
    static void Main()
    {
        Foo foo = new Foo();

        foo.M1(); // Compile-time failure
        foo.M2(); // Fine

        IFoo ifoo = foo;
        ifoo.M1(); // Fine
        ifoo.M2(); // Fine
    }
}

5
Foo.M1 (); üzerinde derleme zamanı hatası alacağınızı düşünüyorum; foo.M2 () değil;
Kevin Aenmey

Buradaki zorluk, dizi gibi genel olmayan bir sınıfa sahip olmak, IList <> gibi genel bir arabirim türü uygulamaktır. Snippet'iniz bunu yapmaz.
Hans Passant

@HansPassant: Jenerik olmayan bir sınıfın genel bir arayüz tipini uygulaması çok kolaydır. Önemsiz. OP'nin sorduğu şeyin bu olduğuna dair herhangi bir belirti görmüyorum.
Jon Skeet

4
@JohnSaunders: Aslında daha önce hiçbirinin yanlış olduğuna inanmıyorum. Bunu çok genişlettim ve CLR'nin dizilere neden tuhaf davrandığını açıkladım - ancak açık arabirim uygulaması konusundaki cevabımın daha önce oldukça doğru olduğuna inanıyorum. Ne şekilde katılmıyorsun? Yine, ayrıntılar yararlı olacaktır (eğer uygunsa, muhtemelen kendi cevabınızda).
Jon Skeet

1
@RBT: Evet, kullanımda bir fark olsa Countda iyi - ama Adddiziler sabit boyutlu olduğundan her zaman atar.
Jon Skeet

86

Bildiğiniz gibi, C # dizileri IList<T>, diğer arabirimler arasında

Şey, evet, erm hayır, pek değil. Bu, .NET 4 çerçevesindeki Array sınıfının bildirimi:

[Serializable, ComVisible(true)]
public abstract class Array : ICloneable, IList, ICollection, IEnumerable, 
                              IStructuralComparable, IStructuralEquatable
{
    // etc..
}

Bu System.Collections.IList, uygulayan değil System.Collections.Generic.IList <>. Olmaz, Dizi genel değildir. Aynı şey genel IEnumerable <> ve ICollection <> arabirimleri için de geçerlidir.

Ancak CLR, anında somut dizi türleri oluşturur, böylece teknik olarak bu arabirimleri uygulayan bir tane oluşturabilir. Ancak durum böyle değil. Örneğin bu kodu deneyin:

using System;
using System.Collections.Generic;

class Program {
    static void Main(string[] args) {
        var goodmap = typeof(Derived).GetInterfaceMap(typeof(IEnumerable<int>));
        var badmap = typeof(int[]).GetInterfaceMap(typeof(IEnumerable<int>));  // Kaboom
    }
}
abstract class Base { }
class Derived : Base, IEnumerable<int> {
    public IEnumerator<int> GetEnumerator() { return null; }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}

GetInterfaceMap () çağrısı, "Arabirim bulunamadı" ile somut bir dizi türü için başarısız olur. Yine de IEnumerable <> 'a atama sorunsuz çalışır.

Bu bir ördek gibi şarlatan yazma. Her değer türünün Object'ten türetilen ValueType'tan türetildiği yanılsamasını yaratan aynı tür yazmadır. Hem derleyici hem de CLR, değer türleri konusunda yaptıkları gibi dizi türleri hakkında özel bilgiye sahiptir. Derleyici IList <> 'e çevrim yapma girişiminizi görür ve "tamam, bunu nasıl yapacağımı biliyorum!" Der. Ve castclass IL komutunu yayar. CLR'nin hiçbir sorunu yoktur, temeldeki dizi nesnesi üzerinde çalışan IList <> uygulamasının nasıl sağlanacağını bilir. Aksi halde gizli olan System.SZArrayHelper sınıfı, bu arabirimleri gerçekten uygulayan bir sarmalayıcıya ilişkin yerleşik bilgiye sahiptir.

Herkesin iddia ettiği gibi açıkça yapmadığı gibi, sorduğunuz Count özelliği şuna benzer:

    internal int get_Count<T>() {
        //! Warning: "this" is an array, not an SZArrayHelper. See comments above
        //! or you may introduce a security hole!
        T[] _this = JitHelpers.UnsafeCast<T[]>(this);
        return _this.Length;
    }

Evet, bu yoruma kesinlikle "kuralları çiğnemek" diyebilirsiniz :) Aksi takdirde işe yarayacaktır. Ve son derece iyi gizlenmiş, bunu CLR için paylaşılan kaynak dağıtımı olan SSCLI20'de kontrol edebilirsiniz. Tür değişiminin nerede gerçekleştiğini görmek için "IList" araması yapın. Eylemde görmek için en iyi yer clr / src / vm / array.cpp, GetActualImplementationForArrayGenericIListMethod () yöntemidir.

CLR'deki bu tür bir ikame, CLR'deki WinRT (diğer adıyla Metro) için yönetilen kod yazmaya izin veren dil projeksiyonunda olanlarla karşılaştırıldığında oldukça hafiftir. Hemen hemen her çekirdek .NET türü orada ikame edilir. IList <>, IVector <> ile eşleşir, örneğin, tamamen yönetilmeyen bir tür. COM, kendisi bir ikame, genel türleri desteklemez.

Bu perdenin arkasında ne olduğuna bir bakıştı. Haritanın sonunda yaşayan ejderhalar çok rahatsız edici, tuhaf ve alışılmadık denizler olabilir. Dünyayı düz hale getirmek ve yönetilen kodda gerçekte neler olup bittiğine dair farklı bir görüntü modellemek çok yararlı olabilir. Bunu herkesin en sevdiği yanıtla eşleştirmek bu şekilde rahattır. Değer türleri için pek işe yaramaz (bir yapıyı değiştirmeyin!) Ama bu çok iyi gizlenmiş. GetInterfaceMap () yöntemi hatası, soyutlamadaki aklıma gelen tek sızıntı.


1
Bu , bir dizi türü olmayanArray sınıf için bildirimdir . Bir dizi için temel tiptir. C # ' da tek boyutlu bir dizi uygular . Ve jenerik olmayan bir tür kesinlikle jenerik bir arabirim uygulayabilir ... ki bu işe yarar çünkü çok sayıda farklı tür vardır - ! = Typeof (string []) typeof (int []) `uygular ve uygular . IList<T>typeof(int[]), so IList<int>typeof(string[])IList<string>
Jon Skeet

2
@HansPassant: Lütfen rahatsız edici olduğu için bir şeyi olumsuz olarak değerlendireceğimi varsaymayın . Gerçek şu ki, hem aracılığınız Array(gösterdiğiniz gibi, soyut bir sınıftır, bu nedenle muhtemelen bir dizi nesnesinin gerçek türü olamaz ) hem de sonuç (uygulamadığı IList<T>) yanlış IMO'dur. Yolu o uygulayan ettiği IList<T>, sıradışı ve ilginç ben kabul edeceğiz - ama bu tamamen bir var uygulama detayı. T[]Uygulanmadığını iddia etmek IList<T>yanıltıcı IMO'dur. Spesifikasyona ve gözlemlenen tüm davranışlara aykırıdır.
Jon Skeet

6
Eminim yanlış olduğunu düşünüyorsun. Spesifikasyonlarda okuduklarınızı şaka yapamazsınız. Lütfen istediğiniz şekilde görmekten çekinmeyin, ancak GetInterfaceMap () 'in neden başarısız olduğuna asla iyi bir açıklama getirmeyeceksiniz. "Korkak bir şey" pek bir içgörü değil. Uygulama gözlükleri takıyorum: elbette başarısız oluyor, vakaya benzer bir ördeğe benziyor, somut bir dizi türü aslında ICollection <> uygulamıyor. Korkunç bir şey yok. Burada tutalım, asla anlaşamayız.
Hans Passant

4
Ne en azından iddialar diziler uygulayamaz o sahte mantığı kaldırma hakkında IList<T> çünkü Array değil mi? Bu mantık, katılmadığım şeyin büyük bir kısmı. Bunun ötesinde, bir tipin bir arayüz uygulamasının ne anlama geldiğine dair bir tanım üzerinde anlaşmamız gerektiğini düşünüyorum: aklıma, dizi türleri dışında, uygulayan tiplerin tüm gözlemlenebilir özelliklerini gösterir . Yine, bunun nasıl başarıldığı benim için daha az önemli, tıpkı uygulama detayları farklı olsa da bunun değişmez olduğunu söylemekte iyi olduğum gibi . IList<T>GetInterfaceMappingSystem.String
Jon Skeet

1
C ++ CLI derleyicisi ne olacak? Bu açıkça "Bunu nasıl yapacağım hakkında hiçbir fikrim yok!" Diyor . ve bir hata verir. IList<T>Çalışması için açık bir kadroya ihtiyacı var .
Tobias Knauss

21

IList<T>.Countaçıkça uygulanır :

int[] intArray = new int[10];
IList<int> intArrayAsList = (IList<int>)intArray;
Debug.Assert(intArrayAsList.Count == 10);

Bu, basit bir dizi değişkeniniz olduğunda, her ikisine de sahip olmayacağınız Countve Lengthdoğrudan erişebileceğiniz şekilde yapılır.

Genel olarak, açık arabirim uygulaması, bir türün tüm tüketicileri bu şekilde düşünmeye zorlamadan belirli bir şekilde kullanılmasını sağlamak istediğinizde kullanılır.

Düzenleme : Maalesef kötü hatırlama var. ICollection.Countaçıkça uygulanır. Jenerik IList<T>, Hans'ın aşağıda tanımladığı gibi ele alınmıştır .


4
Yine de merak etmeme neden oluyor, neden mülkiyete Uzunluk yerine Count demediler? Dizi, böyle bir özelliğe sahip tek ortak koleksiyondur (siz saymadığınız sürece string).
Tim S.

5
@TimS İyi bir soru (ve cevabını bilmediğim bir soru.) Bunun nedeninin "sayım" ın bir dizi öğeyi ifade etmesi, oysa bir dizinin tahsis edilir edilmez değişmeyen bir "uzunluğa" sahip olması olduğunu tahmin ediyorum ( hangi öğelerin değerlere sahip olduğuna bakılmaksızın.)
dlev

1
@TimS Bence bu , ICollectionbildirdiği için yapıldı Countve içinde "koleksiyon" kelimesi bulunan bir tür kullanmasaydı daha da kafa karıştırıcı olurdu Count:). Bu kararları verirken her zaman ödünleşimler vardır.
dlev

4
@JohnSaunders: Ve yine ... sadece bir downvote hiçbir yararlı bilgi.
Jon Skeet

5
@JohnSaunders: Hala ikna olmadım. Hans, SSCLI uygulamasına atıfta bulundu, ancak IList<T>hem dil hem de CLI belirtimlerinin aksine görünmesine rağmen dizi türlerinin uygulanmadığını iddia etti . Arayüz uygulamasının kapaklar altında çalışma şeklinin karmaşık olabileceğini söyleyebilirim, ancak çoğu durumda durum budur. Sırf işleyişler değişebilir diye, System.Stringbunun değişmez olduğunu söyleyen birine de olumsuz oy verir misiniz ? İçin tüm pratik amaçlar - ve kesinlikle kadarıyla C # dili söz konusu olduğunda - bu olduğunu açıkça impl.
Jon Skeet


2

IList'in açık bir arayüz uygulamasından farklı değildir. Arabirimi uygulamanız, üyelerinin sınıf üyesi olarak görünmesi gerektiği anlamına gelmez. Bu does Count özelliğini uygulamak, sadece X [] üzerine göstermiyor.


1

Mevcut referans kaynakları ile:

//----------------------------------------------------------------------------------------
// ! READ THIS BEFORE YOU WORK ON THIS CLASS.
// 
// The methods on this class must be written VERY carefully to avoid introducing security holes.
// That's because they are invoked with special "this"! The "this" object
// for all of these methods are not SZArrayHelper objects. Rather, they are of type U[]
// where U[] is castable to T[]. No actual SZArrayHelper object is ever instantiated. Thus, you will
// see a lot of expressions that cast "this" "T[]". 
//
// This class is needed to allow an SZ array of type T[] to expose IList<T>,
// IList<T.BaseType>, etc., etc. all the way up to IList<Object>. When the following call is
// made:
//
//   ((IList<T>) (new U[n])).SomeIListMethod()
//
// the interface stub dispatcher treats this as a special case, loads up SZArrayHelper,
// finds the corresponding generic method (matched simply by method name), instantiates
// it for type <T> and executes it. 
//
// The "T" will reflect the interface used to invoke the method. The actual runtime "this" will be
// array that is castable to "T[]" (i.e. for primitivs and valuetypes, it will be exactly
// "T[]" - for orefs, it may be a "U[]" where U derives from T.)
//----------------------------------------------------------------------------------------
sealed class SZArrayHelper {
    // It is never legal to instantiate this class.
    private SZArrayHelper() {
        Contract.Assert(false, "Hey! How'd I get here?");
    }

    /* ... snip ... */
}

Özellikle bu kısım:

arabirim saplama dağıtıcısı bunu özel bir durum olarak ele alır , SZArrayHelper'ı yükler , karşılık gelen genel yöntemi bulur (basitçe yöntem adıyla eşleştirilir ) , tür için başlatır ve çalıştırır.

(Vurgu benim)

Kaynak (yukarı kaydırın).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.