Como matrizes em c # parcialmente implementam IList ?

Então, como você deve saber, os arrays em C # implementam o IList , entre outras interfaces. De alguma forma, eles fazem isso sem implementar publicamente a propriedade Count de IList ! Matrizes têm apenas uma propriedade Length.

Este é um exemplo flagrante de c # / .net quebrando suas próprias regras sobre a implementação da interface ou estou faltando alguma coisa?

Nova resposta à luz da resposta de Hans

Graças à resposta dada por Hans, podemos ver que a implementação é um pouco mais complicada do que poderíamos pensar. Tanto o compilador quanto o CLR se esforçam muito para dar a impressão de que um tipo de array implementa IList – mas a variância de array torna isso mais complicado. Ao contrário da resposta de Hans, os tipos de matriz (unidimensionais, baseados em zero) implementam as collections genéricas diretamente, porque o tipo de qualquer matriz específica não é System.Array – que é apenas o tipo base da matriz. Se você perguntar a um tipo de matriz quais interfaces ele suporta, ele inclui os tipos genéricos:

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

Saída:

 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] 

Para matrizes unidimensionais, baseadas em zero, no que diz respeito à linguagem , a matriz realmente implementa o IList também. Seção 12.1.2 da especificação C # diz isso. Portanto, qualquer que seja a implementação subjacente, a linguagem tem que se comportar como se o tipo de T[] implementasse IList como em qualquer outra interface. A partir dessa perspectiva, a interface é implementada com alguns dos membros sendo explicitamente implementados (como o Count ). Essa é a melhor explicação no nível da linguagem para o que está acontecendo.

Observe que isso vale apenas para matrizes unidimensionais (e matrizes baseadas em zero, não que C # como uma linguagem diz algo sobre matrizes não baseadas em zero). T[,] não implementa IList .

Do ponto de vista do CLR, algo mais funk está acontecendo. Você não pode obter o mapeamento de interface para os tipos de interface genéricos. Por exemplo:

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

Dá uma exceção de:

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

Então, por que a estranheza? Bem, eu acredito que é realmente devido à covariância da matriz, que é uma verruga no sistema de tipos, IMO. Mesmo que IList não seja covariante (e não pode ser seguramente), a covariância de matriz permite que isso funcione:

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

… o que faz parecer que typeof(string[]) implementa IList , quando na verdade não.

A partição de especificação CLI (ECMA-335) 1, seção 8.7.1, tem isto:

Um tipo de assinatura T é compatível com um tipo de assinatura U se, e somente se, pelo menos um dos seguintes itens for retido

T é um array de rank 1 com base em zero V[] , e U é IList e V é compatível com elementos de matriz com W.

(Na verdade, ele não menciona ICollection ou IEnumerable que acredito ser um bug na especificação.)

Para não-variação, a especificação do CLI acompanha a especificação do idioma diretamente. Da seção 8.9.1 da partição 1:

Além disso, um vetor criado com o tipo de elemento T implementa a interface System.Collections.Generic.IList , onde U: = T. (§8.7)

(Um vetor é uma matriz unidimensional com uma base zero.)

Agora, em termos de detalhes de implementação , claramente o CLR está fazendo algum mapeamento funky para manter a compatibilidade da atribuição aqui: quando uma string[] é solicitada para a implementação de ICollection.Count , ela não pode lidar com isso caminho normal. Isso conta como implementação explícita da interface? Acho razoável tratá-lo dessa maneira, pois, a menos que você peça diretamente pelo mapeamento da interface, ele sempre se comportará dessa maneira, do ponto de vista da linguagem.

E quanto a ICollection.Count ?

Até agora, falei sobre as interfaces genéricas, mas há a ICollection não genérica com sua propriedade Count . Desta vez podemos obter o mapeamento da interface e, de fato, a interface é implementada diretamente pelo System.Array . A documentação para a implementação da propriedade ICollection.Count na Array indica que ela foi implementada com implementação de interface explícita.

Se alguém puder pensar em uma maneira na qual esse tipo de implementação de interface explícita é diferente da implementação de interface explícita “normal”, ficarei feliz em analisá-la ainda mais.

Resposta antiga em torno da implementação explícita da interface

Apesar do acima, que é mais complicado por causa do conhecimento das matrizes, você ainda pode fazer algo com os mesmos efeitos visíveis através da implementação explícita da interface .

Aqui está um exemplo simples e autônomo:

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

Então, como você deve saber, arrays em C # implementam IList , entre outras interfaces

Bem, sim, não, na verdade não. Esta é a declaração para a class Array no framework .NET 4:

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

Implementa System.Collections.IList, não System.Collections.Generic.IList <>. Não pode, Array não é genérico. O mesmo vale para as interfaces genéricas IEnumerable <> e ICollection <>.

Mas o CLR cria tipos de array de concreto em tempo real, portanto, pode tecnicamente criar um que implemente essas interfaces. Este não é o caso. Tente este código por exemplo:

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

A chamada GetInterfaceMap () falha para um tipo de matriz concreta com “Interface não encontrada”. No entanto, um cast para IEnumerable <> funciona sem nenhum problema.

Esta é a digitação do quacks-like-a-duck. É o mesmo tipo de digitação que cria a ilusão de que todo tipo de valor deriva de ValueType que deriva de Object. Tanto o compilador quanto o CLR possuem conhecimento especial dos tipos de array, assim como fazem dos tipos de valor. O compilador vê sua tentativa de transmitir para IList <> e diz “ok, eu sei como fazer isso!”. E emite a instrução IL da castclass. O CLR não tem problemas com isso, ele sabe como fornecer uma implementação de IList <> que funciona no object de matriz subjacente. Ele tem conhecimento interno da class System.SZArrayHelper oculta, um wrapper que realmente implementa essas interfaces.

O que não faz explicitamente como todo mundo afirma, a propriedade Count que você perguntou se parece com isso:

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

Sim, você pode certamente chamar esse comentário “quebrando as regras” 🙂 É de outra maneira acessível. E extremamente bem escondido, você pode verificar isso em SSCLI20, a distribuição de fonte compartilhada para o CLR. Procure por “IList” para ver onde ocorre a substituição do tipo. O melhor lugar para vê-lo em ação é o método clr / src / vm / array.cpp, GetActualImplementationForArrayGenericIListMethod ().

Esse tipo de substituição no CLR é bem leve comparado ao que acontece na projeção da linguagem no CLR que permite escrever código gerenciado para o WinRT (também conhecido como Metro). Apenas sobre qualquer tipo de núcleo .NET é substituído lá. IList <> mapeia para IVector <> por exemplo, um tipo inteiramente não gerenciado. Sendo uma substituição, o COM não suporta tipos genéricos.

Bem, isso foi uma olhada no que acontece por trás da cortina. Pode ser muito desconfortável, estranhos e desconhecidos mares com dragões que vivem no final do mapa. Pode ser muito útil tornar a Terra plana e modelar uma imagem diferente do que realmente está acontecendo no código gerenciado. Mapear isso para todas as respostas favoritas é confortável assim. Que não funciona tão bem para tipos de valor (não mude uma struct!) Mas esta está muito bem escondida. A falha do método GetInterfaceMap () é o único vazamento na abstração que eu posso pensar.

IList.Count é implementado explicitamente :

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

Isso é feito para que, quando você tiver uma variável de matriz simples, não tenha a Count e o Length diretamente disponíveis.

Em geral, a implementação explícita da interface é usada quando você quer garantir que um tipo possa ser usado de uma maneira particular, sem forçar todos os consumidores do tipo a pensar dessa maneira.

Edit : Whoops, mal lembro lá. ICollection.Count é implementado explicitamente. O IList genérico IList é tratado como Hans descrito abaixo .

Implementação explícita de interface . Em resumo, você declara isto como void IControl.Paint() { } ou int IList.Count { get { return 0; } } int IList.Count { get { return 0; } } .

Não é diferente de uma implementação de interface explícita do IList. Só porque você implementa a interface não significa que seus membros precisem aparecer como membros da class. Ele implementa a propriedade Count, apenas não expõe no X [].

Com fonts de referência disponíveis:

 //---------------------------------------------------------------------------------------- // ! 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, // IList, etc., etc. all the way up to IList. When the following call is // made: // // ((IList) (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  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[]" (ie 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 ... */ } 

Especificamente esta parte:

o interface stub dispatcher trata isso como um caso especial , carrega o SZArrayHelper, localiza o método genérico correspondente (correspondido simplesmente pelo nome do método) , instancia-o para o tipo e o executa.

(Ênfase minha)

Fonte (rolar para cima).