Jak tablice w C# częściowo implementują IList?

Jak zapewne wiesz, tablice w C# implementują IList<T>, między innymi interfejsy. W jakiś sposób jednak robią to bez publicznego implementowania własności Count IList<T>! Tablice mają tylko właściwość Length.

Czy to rażący przykład łamania przez C#/. Net własnych reguł dotyczących implementacji interfejsu, czy coś mi umyka?

Author: John Saunders, 2012-06-23

6 answers

Nowa odpowiedź w świetle odpowiedzi Hansa

Dzięki odpowiedzi udzielonej przez Hansa, widzimy, że implementacja jest nieco bardziej skomplikowana, niż mogłoby się wydawać. Zarówno kompilator, jak i CLR starają się bardzo ciężko sprawić wrażenie, że typ tablicy implementuje IList<T> - ale wariancja tablicy sprawia, że jest to trudniejsze. W przeciwieństwie do odpowiedzi Hansa, typy tablic (jednowymiarowe, w każdym razie oparte na zero) implementują zbiory ogólne bezpośrednio, ponieważ Typ dowolnej tablicy nie jest System.Array - to tylko Typ bazy tablicy. Jeśli zapytasz typ tablicy, jakie interfejsy obsługuje, zawiera ona typy generyczne:

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

Wyjście:

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]

Dla tablic jednowymiarowych, opartych na Odrach, jeśli chodzi o język , tablica tak naprawdę implementuje IList<T>. Sekcja 12.1.2 specyfikacji C# Tak mówi. Tak więc niezależnie od implementacji, język musi zachowuj się tak, jakby Typ T[] implementował IList<T> Jak każdy inny interfejs. Z tego punktu widzenia interfejs jest zaimplementowany, a niektóre z członków są jawnie zaimplementowane(np. Count). To najlepsze wytłumaczenie na poziomie języka tego, co się dzieje.

Zauważ, że dotyczy to tylko tablic jednowymiarowych (i tablic bazujących na 0, a nie tego, że C# jako język mówi coś o tablicach bazujących na 0). T[,] nie implementacja IList<T>.

Z perspektywy CLR dzieje się coś ciekawszego. Nie można uzyskać mapowania interfejsu dla typów interfejsu ogólnego. Na przykład:
typeof(int[]).GetInterfaceMap(typeof(ICollection<int>))

Daje wyjątek:

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

Więc skąd ta dziwność? Cóż, wierzę, że to naprawdę z powodu kowariancji, która jest brodawką w systemie typów, IMO. Mimo że IList<T> jestnie } kowariancją (i nie może być bezpiecznie), KOWARIANCJA tablicy pozwala to działać:

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

... co sprawia, że spójrz jak typeof(string[])} implementuje IList<object>, gdy tak naprawdę nie jest.

[[31]} CLI spec (ECMA-335) partycja 1, sekcja 8.7.1, ma to:
Podpis typu T jest zgodny z podpisem typu U wtedy i tylko wtedy, gdy co najmniej jeden z poniższych podpisów posiada

...

W przeciwieństwie do innych systemów, w których nie ma żadnej funkcji, nie można jej używać do tworzenia tablic, które nie są w pełni kompatybilne z tablicami.]}

(właściwie nie wspomina ICollection<W> lub IEnumerable<W> które Myślę, że to błąd w specyfikacji.)

Dla braku wariancji, Specyfikacja CLI idzie wraz ze specyfikacją języka bezpośrednio. Z sekcji 8.9.1 partycji 1:

W zależności od tego, czy dany element jest elementem składowym, czy też elementem składowym, jest to element składowy, który jest elementem składowym elementu składowego.]}

(Awektor jest jednowymiarową tablicą z zerową bazą.)

Teraz, jeśli chodzi o szczegóły implementacji , najwyraźniej CLR robi trochę ciekawego mapowania, aby zachować zgodność przypisań tutaj: kiedy {[23] } jest proszony o implementację ICollection<object>.Count, nie może poradzić sobie z tym w całkiem w normalny sposób. Czy to się liczy jako Jawna implementacja interfejsu? Myślę, że rozsądne jest traktowanie go w ten sposób, ponieważ jeśli nie poprosisz o mapowanie interfejsu bezpośrednio, zawsze {36]}zachowuje się {37]} w ten sposób z perspektywy języka.

A co z ICollection.Count?

Do tej pory mówiłem o interfejsach generycznych, ale potem jest nie-generyczny ICollection z jego Count właściwością. Tym razem możemy uzyskać mapowanie interfejsu, a w rzeczywistości interfejs jest zaimplementowany bezpośrednio przez System.Array. Dokumentacja do ICollection.Count implementacja właściwości w Array stwierdza, że jest zaimplementowana z jawną implementacją interfejsu.

Jeśli ktoś może wymyślić sposób, w jaki tego rodzaju implementacja explicit interface różni się od" normalnej " implementacji explicit interface, byłbym szczęśliwy przyjrzeć się temu dalej.

Stara odpowiedź wokół jawnej implementacji interfejsu

Pomimo powyższego, co jest bardziej skomplikowane ze względu na znajomość tablic, nadal można zrobić coś z tymi samymi efektami visible poprzez explicit interface implementation .

Oto prosty samodzielny przykład:]}
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
    }
}
 77
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-23 08:13:19

Więc jak zapewne wiesz, tablice w C# implementują IList<T>, między innymi interfejsy

Cóż, tak, ERM nie, niezupełnie. Jest to deklaracja dla klasy Array w. Net 4 framework:
[Serializable, ComVisible(true)]
public abstract class Array : ICloneable, IList, ICollection, IEnumerable, 
                              IStructuralComparable, IStructuralEquatable
{
    // etc..
}

Implementuje System.Kolekcje.IList, Nie System.Kolekcje.Ogólne.IList. Nie może, tablica nie jest ogólna. To samo dotyczy ogólnych interfejsów IEnumerable i ICollection.

Ale CLR tworzy konkretne typy tablic w locie, więc może technicznie stworzyć taki, który implementuje te interfejsy. Tak jednak nie jest. Wypróbuj ten kod na przykład:

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(); }
}

Wywołanie GetInterfaceMap() nie powiedzie się dla konkretnego typu tablicy z "Interface not found". Jednak Obsada do Ieenumerable działa bez problemu.

To jest quacks-like-a-duck typing. Jest to ten sam rodzaj typowania, który tworzy iluzję, że każdy typ wartości pochodzi od ValueType, który pochodzi od obiektu. Zarówno kompilator jak i CLR mają specjalne znajomość typów tablic, podobnie jak typów wartości. Kompilator widzi Twoją próbę castingu do IList i mówi: "OK, wiem jak to zrobić!". I emituje instrukcję castlass IL. CLR nie ma z tym problemu, wie jak dostarczyć implementację IList, która działa na bazowym obiekcie array. Posiada wbudowaną wiedzę o ukrytym systemie.Klasa SZArrayHelper, wrapper, który faktycznie implementuje te interfejsy.

Czego nie robi wprost jak wszyscy twierdzą, nieruchomość Count, o którą pytałeś, wygląda tak:

    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;
    }

Tak, na pewno można nazwać ten komentarz "łamaniem zasad":) jest inaczej cholernie przydatny. I bardzo dobrze ukryte, można to sprawdzić w SSCLI20, współdzielonej dystrybucji źródłowej dla CLR. Wyszukaj "IList", aby zobaczyć, gdzie ma miejsce podstawianie typu. Najlepszym miejscem, aby zobaczyć go w akcji jest clr / src/vm / array.cpp, GetActualImplementationForArrayGenericilistmethod() metoda.

Tego rodzaju substytucja w CLR jest dość łagodna w porównaniu do tego, co dzieje się w projekcji języka w CLR, która umożliwia pisanie zarządzanego kodu dla WinRT (aka Metro). Prawie każdy rdzeń typu. NET jest tam zastępowany. IList maps to IVector na przykład, całkowicie niezarządzany Typ. Sam substytut, COM NIE obsługuje typów generycznych.

To było spojrzenie na to, co dzieje się za kurtyną. To może być bardzo niewygodne, dziwne i nieznane morza ze smokami żyjącymi na końcu mapy. Bardzo przydatne może być uczynienie ziemi płaską i modelowanie innego obrazu tego, co naprawdę dzieje się w kodach zarządzanych. Mapowanie go do wszystkich ulubionych odpowiedzi jest wygodne w ten sposób. Co nie działa tak dobrze dla typów wartości(nie mutuj struktury!), ale ten jest bardzo dobrze ukryty. Błąd metody GetInterfaceMap() jest jedynym wyciekiem w abstrakcji, który przychodzi mi do głowy.
 82
Author: Hans Passant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-25 19:07:21

IList<T>.Count jest realizowany jawnie:

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

Jest to zrobione tak, że gdy masz prostą zmienną tablicy, nie masz zarówno Count, jak i Length bezpośrednio dostępnych.

Ogólnie rzecz biorąc, implementacja interfejsu jawnego jest używana, gdy chcesz zapewnić, że typ może być używany w określony sposób, bez zmuszania wszystkich konsumentów tego typu do myślenia o tym w ten sposób.

Edit : UPS, złe przypomnienie tam. {[4] } jest zaimplementowane jawnie. Na generic IList<T> jest traktowany jako Hans descibes poniżej .

 20
Author: dlev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:54

Explicit interface implementation . Krótko mówiąc, deklarujesz to jako void IControl.Paint() { } LUB int IList<T>.Count { get { return 0; } }.

 10
Author: Tim S.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-22 20:04:26

Nie różni się niczym od jawnej implementacji interfejsu IList. Tylko dlatego, że zaimplementowałeś interfejs, nie oznacza to, że jego członkowie muszą pojawiać się jako członkowie klasy. To robi implementuje właściwość Count, po prostu nie wystawia jej Na x [].

 1
Author: nitzmahone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-22 20:05:32

Z dostępnymi źródłami odniesienia:

//----------------------------------------------------------------------------------------
// ! 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 ... */
}

Konkretnie ta część:

Dyspozytor interfejsu stub traktuje to jako szczególny przypadek , ładuje SZArrayHelper, znajduje odpowiednią metodę generyczną (dopasowaną po prostu przez nazwę metody) , tworzy instancję dla type i wykonuje ją.

(moje)

Źródło (przewiń w górę).

 1
Author: AnorZaken,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-05 15:59:41