Así como usted puede saber, las matrices en C# implementar IList<T>, entre otras interfaces. De alguna manera, sin embargo, hacer esto sin públicamente la aplicación de la propiedad Count de IList<T>! Matrices sólo tienen una Longitud de propiedad.

Es este un ejemplo más flagrante de C#/.NET romper sus propias reglas acerca de la implementación de la interfaz o me estoy perdiendo algo?

  • Nadie dijo que la Array clase tenía que ser escrito en C#!
  • Array es una «magia» de la clase, que no podía ser implementado en C# o cualquier otro idioma de destino .net. Pero esta característica específica está disponible en C#.
InformationsquelleAutor MgSam | 2012-06-22

6 Comentarios

  1. 80

    Nueva respuesta en la luz de Hans respuesta

    Gracias a la respuesta dada por Hans, podemos ver la aplicación es un poco más complicado de lo que podríamos pensar. El compilador y el CLR intentar muy duro para dar la impresión de que un tipo de matriz implementa IList<T> – pero matriz de varianza hace esto más difícil. Contrario a la respuesta de Hans, el tipo de matriz (de una sola dimensión, basado en cero de todos modos) hacer aplicar las colecciones genéricas directamente, debido a que el tipo de matriz no System.Array – que es sólo el base tipo de la matriz. Si usted le pregunta a un tipo de matriz de qué interfaces que soporta, que incluye los tipos genéricos:

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

    De salida:

    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 una sola dimensión, basado en cero de las matrices, tan lejos como el idioma se refiere, la matriz realmente implementar IList<T> demasiado. Sección 12.1.2 de la C# especificación dice así. Así que sea cual sea el subyacente en la aplicación, el idioma tiene que se comportan como si el tipo de T[] implementa IList<T> como con cualquier otra interfaz. Desde esta perspectiva, la interfaz de es implementado con algunos de los miembros de ser implementado explícitamente (como Count). Esa es la mejor explicación en la idioma nivel de lo que está pasando.

    Tenga en cuenta que esto sólo vale para una sola dimensiones de las matrices (y cero basado en matrices, no se que C# como lenguaje dice nada acerca de la no-cero basados en matrices). T[,] no implementar IList<T>.

    De una CLR perspectiva, algo funkier que está pasando. Usted no puede obtener la interfaz de asignación para la interfaz genérica de los tipos. Por ejemplo:

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

    Da una excepción de:

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

    Entonces, ¿por qué la rareza? Bueno, creo que es realmente debido a la matriz de covarianza, que es una verruga en el tipo de sistema, de la OMI. Aunque IList<T> es no covariante (y no puede ser de forma segura), la matriz de covarianza permite que esto funcione:

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

    … lo que hace mirada como typeof(string[]) implementa IList<object>, cuando en realidad no.

    La CLI spec (ECMA-335) la partición 1, apartado 8.7.1, tiene esto:

    Un tipo de firma T es compatible con un tipo de firma U si y sólo si al menos uno de los siguientes sostiene

    T es una base cero rango-1 de la matriz de V[], y U es IList<W>, y V es la matriz de elemento-compatible-con W.

    (Que en realidad no mencionar ICollection<W> o IEnumerable<W> que yo creo que es un error en la especificación.)

    Para los no-varianza, la CLI spec va de la mano con el lenguaje de especificación directamente. De la sección 8.9.1 de la partición 1:

    Además, la creación de un vector con el elemento de tipo T, implementa la interfaz System.Collections.Generic.IList<U>, donde U := T. (§8.7)

    (Un vector es una sola matriz unidimensional con una base cero.)

    Ahora en términos de la detalles de implementación, claramente el CLR está haciendo algo de funky asignación de mantener la asignación de compatibilidad aquí: cuando un string[] se pide la aplicación de ICollection<object>.Count, que no puede manejar que en bastante la manera normal. ¿Esto cuenta como explícito implementación de la interfaz? Creo que es razonable para tratarlo de esa manera, ya que a menos que usted pida la interfaz de asignación directa, que siempre se comporta de esa manera, desde una perspectiva lingüística.

    Lo que acerca de ICollection.Count?

    Hasta ahora he hablado sobre las interfaces genéricas, pero entonces no la no-genérico ICollection con su Count de la propiedad. Esta vez nos puede la interfaz de asignación, y de hecho la interfaz es implementada directamente por Sistema.Matriz. La documentación de la ICollection.Cuenta la implementación de propiedades en Array los estados que se implementa con el consentimiento explícito de la interfaz de la aplicación.

    Si alguien puede pensar de una manera en la que este tipo de explícito de la interfaz de la aplicación es diferente de «normal» explícito de la interfaz de la aplicación, yo estaría feliz de mirar hacia el futuro.

    Respuesta anterior alrededor explícita implementación de la interfaz de

    A pesar de lo anterior, que es más complicado debido al conocimiento de las matrices, usted todavía puede hacer algo con el mismo visible efectos a través de explícito de la interfaz de la aplicación.

    Aquí es un simple autónomo ejemplo:

    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
        }
    }
    • Creo que va a obtener en tiempo de compilación falla en foo.M1(); no foo.M2();
    • El reto aquí es que no tiene una clase genérica, como una matriz, implementar una interfaz genérica tipo, como IList<>. El fragmento no hace eso.
    • Es muy fácil hacer una no-clase genérica implementar una interfaz genérica tipo. Trivial. No veo ningún indicio de que eso es lo que el OP estaba preguntando acerca de.
    • En realidad, no creo que nada de eso era inexacto antes. He ampliado mucho, y explicó por qué el CLR trata de matrices extraño – pero creo que mi respuesta explícita de la interfaz de la aplicación fue bastante correcto antes. ¿De qué manera usted no está de acuerdo? De nuevo, los detalles sería útil (posiblemente en su propia respuesta, si es apropiado).
    • Cuando se aplica a una matriz de un tipo derivado, la lista devuelta no ser exhaustiva, ya que una SiameseCat() implementará IList<Animal> pesar de que este último tipo no es ni aparece entre las interfaces compatibles, ni implícita por ellos (aunque IList<SiameseCat> implica IEnumerable<Animal>, IList<SiameseCat> no implica IList<Animal>).
    • Hola Jon! Honestamente yo no podía entender mucho, ya sea el suyo, o el de Hans respuesta. Parece una muy complicado tema. Pero desde el punto de vista de un laico será correcto decir que las Matrices simplemente hacer explícita la implementación de interfaces como IList y ICollection. No es sólo acerca de Count propiedad mencionada por el OP, pero otros contratos como el presente en IList interfaz por ejemplo,Add,Remove, etc. Yo siempre puedes hacer como este en el código – int[] myArr = new int[20];((IList)myArr).Add(4); var myCount = ((ICollection)myArr).Count;
    • Sí, aunque hay una diferencia en que el uso de Count está bien – pero Add siempre va a tirar, como matrices de tamaño fijo.
    • Me encontré con este tema cuando se intenta utilizar el IListen C++CLI. El compilador no es, obviamente, menos inteligente y no sabe que array<T> (C#: T[]) implementa IList<T>. Así que una conversión explícita es necesario, tal como se describe en stackoverflow.com/q/12152913/2505186, que enlaza aquí donde he encontrado tu y @HansPassant ‘s respuesta (grandes!). Lo que me pregunto acerca de: Es el compilador de C# mejor?
    • Seguro – es sin duda no sólo un compilador de materia, en que al hacer el CLR si un int[] implementa IList<int> por ejemplo (con isinst), devolverá true. Pero tal vez se requiere algo más de compilador conocimiento de que el compilador de C++ no tiene. Yo esperaría una conversión explícita a IList<T> para el trabajo, aunque…
    • Sí, una conversión explícita funciona bien, como el otro post dice. Es sólo un poco de tomarse la molestia de poner una conversión explícita en cada lugar… voy a reemplazar function (array<T>) y function (List<T>) por function (IList<T>), por lo que el compilador se quejará en algunos lugares.

  2. 84

    Así como usted puede saber, las matrices en C# implementar IList<T>, entre otras interfaces

    Bueno, sí, el mtc no, no realmente. Esta es la declaración de la clase Array en el .NET 4 framework:

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

    Se implementa el Sistema.Las colecciones.IList, no Sistema.Las colecciones.Genérica.IList<>. No, la Matriz no es genérico. Lo mismo va para los genéricos de IEnumerable<> y ICollection<> interfaces.

    Pero el CLR crea hormigón tipos de matriz sobre la marcha, por lo que técnicamente podría crear uno que implementa estas interfaces. Esto sin embargo no es el caso. Prueba este código por ejemplo:

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

    La GetInterfaceMap() la llamada falla, para un concreto tipo de matriz con «la Interfaz no se encontró». Sin embargo, un elenco de IEnumerable<> funciona sin problemas.

    Este es grazna como un pato escritura. Es el mismo tipo de escritura que crea la ilusión de que cada tipo de valor se deriva del tipo de valor que se deriva de Object. El compilador y el CLR tiene especial conocimiento de los tipos de matriz, así como de los tipos de valor. El compilador ve su intento de fundición a IList<> y dice: «bueno, yo sé cómo hacer eso!». Y emite el castclass de ALFIN. El CLR no tiene ningún problema con él, sabe cómo proporcionar una implementación de la interfaz IList<> que actúa sobre las causas de la matriz objeto. Se ha incorporado en el conocimiento de la otra ocultos del Sistema.SZArrayHelper clase, un contenedor que implementa realmente estas interfaces.

    Que no lo hace explícitamente, como todos los reclamos, la propiedad Count preguntó sobre este aspecto:

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

    Sí, claro que puedes llamar a ese comentario de «romper las reglas» 🙂 es lo contrario de hecho a mano. Y muy bien escondido, usted puede comprobar esto en SSCLI20, el origen compartido de distribución para el CLR. De la búsqueda para «IList» para ver donde el tipo de sustitución se lleva a cabo. El mejor lugar para ver en acción es clr/src/vm/array.cpp, GetActualImplementationForArrayGenericilistmethod() método.

    Este tipo de sustitución en el CLR es bastante leve en comparación con lo que sucede en el lenguaje de proyección en el CLR que permite la escritura de código administrado para WinRT (también conocido como Metro). Apenas alrededor de cualquier núcleo .Tipo de RED presenta sustituido allí. IList<> se asigna a IVector<> por ejemplo, un tipo no administrado. Propio de una sustitución, COM no admite los tipos genéricos.

    Bien, que era un vistazo a lo que sucede detrás de la cortina. Puede ser muy incómodo, extraño y desconocido de los mares con los dragones que viven en la final de la ruta. Puede ser muy útil para hacer de la Tierra plana y el modelo de una imagen diferente de lo que realmente está pasando en código administrado. La asignación a todos los favoritos de respuesta es cómodo de esa manera. Que no funciona tan bien para los tipos de valor (no mutar un struct!) pero este está muy bien escondido. El GetInterfaceMap() método el fracaso es la pérdida de la abstracción en el que puedo pensar.

    • Esa es la declaración de la Array clase, que no el tipo de matriz. Es el base typefor una matriz. Una sola dimensión de la matriz en C# no implementar IList<T>. Y no de tipo genérico sin duda puede implementar una interfaz genérica de todos modos… que funciona porque hay un montón de diferentes tipos de typeof(int[]) != typeof(string[]), so typeof(int[])` implementa IList<int>, y typeof(string[]) implementa IList<string>.
    • Veo que es más complicado (sólo mirar a la CLI de especificaciones) pero yo todavía no creo que esto es totalmente razonable en términos de la «no puede» parte. Estoy editando mi respuesta para dar más información de manera complementaria.
    • Ahora se encuentra directamente contradictorias parte de la CLI spec: «Además, la creación de un vector con el elemento de tipo T, implementa la interfaz System.Collections.Generic.IList<U>, donde U := T. (§8.7)»
    • En realidad no votada abajo esto debido a que la parte útil de alrededor de SZArrayHelper – pero es incorrecta según la especificación del lenguaje C# y la especificación CLI en torno a si es o no un tipo de matriz (no del Sistema.De la matriz, pero el tipo real) implementa IList<T>… y se comporta en todos los aspectos, como explícito implementación de la interfaz como de lo que puedo decir.
    • Por dios Jon, no dejes que esta respuesta a llegar a usted. La gente por lo general les resulta gratificante descubrir una nueva verdad, un pequeño punto en downvoting porque es inquietante. Intente razón el comentario en el fragmento copiado de get_Count() y coinciden con lo que las especificaciones que decir acerca de los métodos de instancia para ver al dragón.
    • Por favor no asuma que había downvote algo sólo por ser inquietante. El hecho es que tanto su razonamiento a través de Array (que, como espectáculo, es una clase abstracta, por lo que no puede ser, posiblemente, el real tipo de un objeto de matriz) y la conclusión (que no implementar IList<T>) son incorrectos de la OMI. El camino en el que se implementa IList<T> es inusual y muy interesante, estaré de acuerdo, pero esto es puramente un aplicación detalle. La afirmación de que se T[] no implementar IList<T> es engañosa la OMI. Esto va en contra de la especificación y todas comportamiento observado.
    • Además, asegúrese de que usted piensa que es incorrecto. Usted no puede hacer que concuerdan con lo que has leído en las especificaciones. Por favor, siéntase libre para ver a tu manera, pero nunca vas a encontrar una buena explicación de por qué GetInterfaceMap() falla. «Algo diferente» no es mucho de una idea. Estoy usando la aplicación gafas: por supuesto que no, es graznar como un pato escritura, un concreto tipo de matriz en realidad no implementar ICollection<>. Nada funky sobre ella. Dejémoslo aquí, nunca vamos a estar de acuerdo.
    • Lo que sobre, al menos, la eliminación de la falsa lógica de que las reclamaciones de las matrices no se puede implementar IList<T> porque Array no? Que la lógica es una gran parte de lo que estoy en desacuerdo. Más allá de eso, creo que tendríamos que estar de acuerdo en una definición de lo que significa para un tipo para implementar una interfaz: a mi mente, la matriz de tipos de visualización todas las características observables de los tipos que implementan IList<T>, otros que GetInterfaceMapping. De nuevo, la forma en que se logra es de menor importancia para mí, así como yo estoy bien con el, diciendo que System.String es inmutable, aunque el detalles de implementación son diferentes.
    • Cada montón de objetos cuyo tipo se deriva de ValueType es totalmente legítimo derivado de Object. La «ilusión» asociados con los tipos de valor que se expresa de otra manera-con el hecho de que una ubicación de almacenamiento de un tipo descendientes de Object (aparte de System.Enum) llevará a cabo ni una Object, ni una referencia a uno.
    • But the CLR creates concrete array types on the fly – Que son como los tipos anónimos o los creados por el compilador JIT para las clases genéricas con nombres raros, con una marca de verificación?
    • ¿Y el C++CLI compilador? Que, obviamente, dice que «no tengo idea de cómo hacer eso!» y emite un error. Se necesita una conversión explícita a IList<T> con el fin de trabajar.

  3. 20

    IList<T>.Count se implementa explícitamente:

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

    Esto se hace para que cuando usted tiene una simple variable de matriz, no tiene tanto Count y Length disponible directamente.

    En general, explícita implementación de interfaz se utiliza cuando desea asegurarse de que un tipo puede ser utilizado de una manera particular, sin forzar a todos los consumidores de este tipo para pensar de esa manera.

    Editar: Huy, mal recordar que hay. ICollection.Count se implementa de forma explícita. El genérico IList<T> se maneja como Hans describe a continuación.

    • Me pregunto, sin embargo, ¿por qué no acaba de llamar a la propiedad de lugar de la Longitud? La matriz es la única colección común que tiene una propiedad (a menos que cuentes string).
    • Una buena pregunta (y cuya respuesta no sé.) Me gustaría especular que la razón es porque «el conde» implica cierto número de elementos, mientras que una matriz tiene una inmutable «longitud» tan pronto como sea asignado (independientemente de que los elementos tienen valores.)
    • Gracias. Yo no había considerado ser implementado de forma explícita. El razonamiento detrás de esta haciendo una implementación explícita se rompe aunque si tenemos en cuenta LINQ, como las Matrices tienen el Count() método de extensión como todos los demás enumerable.
    • y, sin embargo, ReadOnlyCollection<T>’s de la propiedad .Cantidad: msdn.microsoft.com/en-us/library/ms132507.aspx, no .Longitud.
    • Buen punto. Que en realidad es uno de los motivos por los métodos de extensión debe ser el ámbito de aplicar tan estrechamente como sea posible, de modo que las «interfaces» están contaminados como poco como sea posible. Sin embargo, no hay nada que hacer acerca de la LINQ problema, mientras que la implementación explícita estaba disponible en el List<T>.Count caso.
    • Creo que se ha hecho porque ICollection declara Count, y sería incluso más confuso si un tipo con la palabra «colección» en no utilizar Count :). Siempre hay trade-offs en la toma de estas decisiones.
    • Yo realmente prefiero cómo es para los métodos de extensión en la que es visible en cualquier tipo de los que se apliquen. Me cuesta creer que la ocultación pública de la funcionalidad de un tipo que no se ha echado para nada es una buena cosa. Si sólo hay un único método público en un tipo, que debe estar disponible si es explícito o no. Creo que sólo si hay varias implementaciones, implícito y explícito, debe el explícitas estar oculto. Un buen ejemplo de esto es Claro(). No hay ninguna razón para que una matriz no sea visible función Clear (); es algo que la gente suele aplicar a sí mismos.
    • Supongo Clear() en una variable de tipo T[] conjuntos de cada elemento para default(T)? Curiosamente, no es un método público en Array que lo borra: msdn.microsoft.com/en-us/library/system.array.clear.aspx no estoy muy seguro de por qué el método es static.
    • Y de nuevo… sólo un downvote con sin información útil.
    • wnat tan difícil? No se trata de una implementación explícita.
    • Todavía no estoy convencido. Hans se ha referido a la SSCLI la aplicación, sino también afirmaron que los tipos de matriz no implementar incluso IList<T>, a pesar de que tanto el lenguaje como el CLI especificaciones que aparecen para el contrario. Me atrevo a decir que la forma en que la interfaz de la aplicación funciona bajo las cubiertas puede ser complicado, pero se que es el caso en muchas situaciones. ¿También downvote alguien diciendo que System.String es inmutable, sólo porque el interno los trabajos son mutables? Para todo los efectos prácticos, y, ciertamente, tan lejos como el lenguaje C# es que se trate – es es explícita impl.

  4. 2

    No es diferente de una explícita la implementación de una interfaz de interfaz IList. Sólo porque usted implementar la interfaz no significa que sus miembros han de aparecer como miembros de la clase. Es hace aplicar la propiedad Count, sólo que no se exponga en X [] [].

  5. 1

    Con la referencia de las fuentes disponibles:

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

    Específicamente esta parte:

    la interfaz de punta de despachador trata como un caso especial, cargas de hasta
    SZArrayHelper, encuentra la correspondiente método genérico (coincide simplemente
    por el nombre del método), crea una instancia de ella para el tipo y la ejecuta.

    (El énfasis es mío)

    Fuente (flecha hacia arriba).

Dejar respuesta

Please enter your comment!
Please enter your name here