metodos - propiedades array c#
¿Por qué la clase Array no expone su indexador directamente? (9)
algo para mencionar para responder:
No se preocupe por la variación , mientras que el elemento en cuestión es
Array
lugar deT[]
.Un caso similar para matrices multidimensionales es [ here ]
Es decir, N-dims a transformada lineal, siempre es posible. Así que esta pregunta atrajo especialmente mi atención, ya que ya implementó IList
para un indexador lineal.
Pregunta:
En mi código, tengo la siguiente declaración:
public static Array ToArray<T>(this T source);
Mi código sabe cómo hacer una fuente presenta una matriz (en tiempo de ejecución). Y estoy tratando de permitir que el código consumidor acceda directamente a su indexador. Pero sin "como IList", no se puede hacer. Para devolver el object[]
posible que requiera conversión / lanzamiento adicional, eso es lo que estoy impidiendo hacer. Lo que puedo hacer es:
public static IList ToArray<T>(this T source);
Pero creo que un método llamado ToArray
devuelve un IList
ve extraño.
Por lo tanto, estoy confundido con eso:
En la declaración de Array
, hay
object IList.this[int index];
Para que podamos
Array a;
a=Array.CreateInstance(typeof(char), 1);
(a as IList)[0]=''a'';
Pero no podemos
a[0]=''a'';
Excepto si fue declarado como
public object this[int index];
La única diferencia que puedo ver es que requiere que usemos su indexador explícitamente a través de la interfaz IList
mediante la cual se implementó, pero ¿por qué? ¿Hay beneficios? ¿O hay problemas de exposición?
Creo que una razón por la que Array
no implementa ese indexador directamente es porque todos los tipos de arreglos específicos (como char[]
) se derivan de Array
.
Lo que esto significa es que un código como este sería legal:
char[] array = new char[10];
array[0] = new object();
El código como este no debería ser legal, porque no es seguro para el tipo. Lo siguiente es legal y lanza una excepción:
char[] array = new char[10];
array.SetValue(new object(), 0);
Pero SetValue()
no se usa normalmente, por lo que no es un gran problema.
Desde msdn:
La clase Array es la clase base para las implementaciones de lenguaje que admiten matrices. Sin embargo, solo el sistema y los compiladores pueden derivar explícitamente de la clase Array. Los usuarios deben emplear las construcciones de matriz proporcionadas por el lenguaje.
Si le proporciona un indexador, contradice la intención original de la clase Array. Debe utilizar la implementación del compilador.
Nuevamente desde msdn:
Importante: A partir de .NET Framework 2.0, la clase Array implementa las interfaces genéricas System.Collections.Generic.IList, System.Collections.Generic.ICollection y System.Collections.Generic.IEumerable. Las implementaciones se proporcionan a los arreglos en tiempo de ejecución y, por lo tanto, no son visibles para las herramientas de compilación de la documentación. Como resultado, las interfaces genéricas no aparecen en la sintaxis de declaración para la clase Array, y no hay temas de referencia para los miembros de la interfaz a los que solo se puede acceder mediante la conversión de una matriz al tipo de interfaz genérica (implementaciones de interfaz explícita). La clave a tener en cuenta cuando emite una matriz a una de estas interfaces es que los miembros que agregan, insertan o eliminan elementos emiten NotSupportedException.
Es una idea de último momento, supongo.
El problema con los métodos de IList<T>
en la clase Array
, incluido su indexador, es que sus implementaciones explícitas se agregan a los objetos Array
de la clase en tiempo de ejecución :
A partir de .NET Framework 2.0, la clase
Array
implementa las interfaces genéricasSystem.Collections.Generic.IList<T>
,System.Collections.Generic.ICollection<T>
ySystem.Collections.Generic.IEnumerable<T>
. Las implementaciones se proporcionan a los arreglos en tiempo de ejecución y, por lo tanto, no son visibles para las herramientas de compilación de la documentación. Como resultado, las interfaces genéricas no aparecen en la sintaxis de declaración para la claseArray
, y no hay temas de referencia para los miembros de la interfaz a los que solo se puede acceder mediante la conversión de una matriz al tipo de interfaz genérica (implementaciones de interfaz explícita).
Cuando las clases implementan interfaces de forma explícita , el acceso a los métodos de interfaz requiere una conversión :
Una clase que implementa una interfaz puede implementar explícitamente un miembro de esa interfaz. Cuando un miembro se implementa explícitamente, no se puede acceder a él a través de una instancia de clase, sino solo a través de una instancia de la interfaz.
El problema de proporcionar una implementación de interfaz "regular" (a diferencia de una "explícita") es el hecho de que la clase Array
no es genérica: sin un parámetro de tipo, no puede escribir
class Array : IList<T>
simplemente porque T
no está definido. El entorno no puede imponer una implementación de interfaz en la clase Array
hasta que se conozca el tipo de parámetro T
, lo que puede ocurrir solo en tiempo de ejecución:
// The type of [T] is char
Array a = Array.CreateInstance(typeof(char), 1);
// The type of [T] is int
Array b = Array.CreateInstance(typeof(int), 1);
// The type of [T] is string
Array c = Array.CreateInstance(typeof(string), 1);
Al mismo tiempo, el tipo estático de a
, b
y c
sigue siendo el mismo: es System.Array
. Sin embargo, en el tiempo de ejecución a
estará implementando IList<char>
, b
implementará IList<int>
, y c
- IList<string>
. Nada de esto se conoce en el momento de la compilación, impide que el compilador "vea" el indexador y otros métodos de IList<T>
.
Además, no todas las instancias de Array
implementan IList<T>
; solo las matrices con una sola dimensión :
Array x = new int[5];
Console.WriteLine(x.GetType().GetInterface("IList`1") != null); // True
Array y = new int[5,5];
Console.WriteLine(y.GetType().GetInterface("IList`1") != null); // False
Todo lo anterior impide que el compilador acceda a IList<T>
métodos IList<T>
, incluido el indexador, sin una conversión explícita.
Especificaciones de C # "12.1.1 El tipo System.Array" dice: "Tenga en cuenta que System.Array no es en sí mismo un tipo de matriz"
Porque no es un tipo de matriz.
Y tenga en cuenta que "6.1.6 Conversiones de referencia implícitas" dice, "Desde un tipo de matriz unidimensional S [] a System.Collections.Generic.IList y sus interfaces base, siempre que haya una identidad implícita o conversión de referencia de S a T
Especificaciones de C #: http://www.microsoft.com/en-us/download/details.aspx?id=7029
Sobre por qué el acceso al indexador es un misterio, consulte esta otra publicación de SO: implícita frente a la implementación explícita de la interfaz
Espero eso ayude.
Incluso si los arreglos fueran todos 1D, todavía tendrías un problema de covarianza y contraprestación:
Si la clase base tuviera una
public Object this[int index] { get; set; }
propiedad de indexador, entonces las propiedades de indexador de tipos concretos
public TValue this[int index] { get; set; }
chocaría con el del tipo base (ya que el parámetro es del configurador es el mismo, pero el valor de retorno no lo es).
Convertir la clase base en una interfaz base o una interfaz genérica como IList o IList resuelve esto, ya que el indexador no específico puede implementarse explícitamente. Esto es lo mismo con el
Add(Object value)
contra
Add(TValue value)
metodos
El problema multidimensional podría , teóricamente, superarse definiendo una conversión entre índices 1D e índices nD (por ejemplo, [n] = [n / length (0), n% length (0)]) ya que las matrices nD se almacenan como una sola buffer.
Técnicamente hay dos tipos de matrices. Tipos de vectores o tipos de matrices. El tiempo de ejecución se refiere a los tipos de vectores como Sz_Array
y son el tipo que se obtiene al declarar una matriz 1d *. No tengo ni idea de por qué. Los tipos de matrices representan matrices multidimensionales. Desafortunadamente, ambos heredan de Array
y ningún otro tipo de intermediario.
¿Por qué la clase Array no expone su indexador directamente?
La razón por la que solo puede acceder al indexador para matrices 1d cuando lo tiene como T[]
es porque el indexador para matrices 1d se implementa en el tiempo de ejecución a través de los códigos de operación de IL.
p.ej
static T GetFirst<T>(T[] a)
{
return a[0]:
}
Se traduce a lo siguiente il ::
L_0000: ldarg.0
L_0001: ldc.i4.0
L_0002: ldelem.any !!T
L_0007: ret
Donde como el siguiente C #
private static T GetFirst<T>(IList<T> a)
{
return a[0];
}
se traduce a esta IL.
L_0000: ldarg.0
L_0001: ldc.i4.0
L_0002: callvirt instance !0 [mscorlib]System.Collections.Generic.IList`1<!!T>::get_Item(int32)
L_0007: ret
Así que podemos ver que uno está usando un código de operación ldelem.any
, y el otro es un método de callvirt
.
El tiempo de ejecución inyecta IList<T>,IEnumerable<T>
en tiempo de ejecución para los arreglos. La lógica para ellos en la MSIL se encuentra en la clase SZArrayHelper
El tiempo de ejecución que proporciona la implementación también crea dos métodos auxiliares para cada matriz generada para ayudar a los idiomas que no admiten indizadores (si existe dicho idioma) C # no los expone, pero son métodos válidos para llamar. Son:
T Get(Int32)
void Set(Int32, T)
Estos métodos también se generan para matrices de tipos matriciales en función de la dimensión y son utilizados por C # cuando se llaman indizadores.
Sin embargo, a menos que realmente especifique que es una matriz escrita, no obtendrá el indexador. Como Array no tiene un método de indexador. Los códigos de operación no se pueden utilizar porque, en el momento de la compilación, debe saber que la matriz en cuestión es un tipo vectorial y el tipo de elemento de la matriz.
Pero Array implementa IList! Que tiene un indexador no puedo llamarlo?
Sí, pero la implementación de los métodos de IList son implementaciones explícitas, por lo que solo se pueden llamar en C # cuando se lanzan o cuando están vinculadas por una restricción genérica. Probablemente, debido a que para cualquier tipo de vector que no sea un arreglo, lanza una excepción no compatible cuando llama a cualquiera de sus métodos. Debido a que solo es compatible condicionalmente, los creadores del tiempo de ejecución probablemente deseen que lo conviertas para los momentos en que sabes a ciencia cierta que se trata de una matriz 1d, pero no puedo nombrar el tipo en este momento.
¿Por qué array implementa
IList
si para cualquier matriz multidimensional la implementación no es compatible?
Este es probablemente un error que estuvo ahí desde 1.0. No pueden arreglarlo ahora como alguien por el motivo que sea que esté lanzando una matriz multidimensional a un IList
. En 2.0 agregaron algo de magia de tiempo de ejecución para agregar implementación a las clases de tipo vectorial en tiempo de ejecución, de modo que solo los arreglos 1d implementan IList<T>
e IEnumerable<T>
.
Y podemos interpolar su siguiente pregunta:
¿Cómo puedo hacer que los indexadores aparezcan sin lanzar? Cambia la firma del método a algo como:
public static T[] ToArray<T>(this T source)
Pero podría decir que su ToArray no devuelve una T [], devuelve otra cosa, ¿qué hago? Si puedes especificar el tipo de retorno explícitamente, hazlo. Si es un tipo de referencia, siempre puedes abusar de la covarianza de la matriz y cambiar el tipo de retorno a object[]
pero entonces estás a merced de ArrayTypeMismatchException. Esto no funcionará si obtiene un tipo de valor de nuevo, ya que ese lanzamiento es ilegal. En este punto, solo puede devolver IList
pero luego está encajonando los elementos y todavía está a merced de ArrayTypeMismatchException. Array es la clase base para todos los tipos de arreglos por una razón que tiene métodos auxiliares para ayudarlo a acceder al contenido como GetValue
y SetValue
y SetValue
que tienen sobrecargas que toman arreglos de índices para que pueda acceder a los elementos en Nd, así como 1D matrices. p.ej
IList myArray = ToArray(myValues);
// myArray is actually a string array.
myArray[0]=''a'';
// blows up here as you can''t explicitly cast a char to a string.
Así que, en resumen, no sabes el tipo explícito. Y cada heredero de Array
implementa IList
, incluso cuando no tiene mucho sentido, es un detalle de implementación que no pueden cambiar desde que estuvo allí desde la versión 1.0.
- Técnicamente esto no es un 100% cierto. Puede crear una matriz de tipo matriz que solo tiene 1 dimensión. Esta abominación de derivación puede crearse en IL o puede crear el tipo usando
typeof(int).MakeArrayType(1)
Observe cómo ahora tiene unSystem.Int32[*]
lugar deSystem.Int32[]
Array
no puede tener un indexador porque debe poder representar una matriz con cualquier número de dimensiones. El indexador para una matriz bidimensional tiene una firma diferente a la de una matriz unidimensional.
Si se proporcionó un indexador y se usó en una Array
que representaba una matriz bidimensional, ¿qué debería suceder?
La solución que eligieron los diseñadores de idiomas fue simplemente no incluir un indexador.
Si sabe que su método ToArray
siempre devolverá una matriz unidimensional, entonces considere usar:
public static T[] ToArray<T>(this T source);
Eso tendrá un indexador.
Si los elementos de la matriz no serán todos de tipo T
entonces puede devolver un object[]
:
public static object[] ToArray<T>(this T source);
a as IList
es (básicamente) el casting. Así que basta con lanzarlo primero:
char[] a = (char[])Array.CreateInstance(typeof(char), 1);
a[0] = ''a'';
Editar : el motivo es: porque la interfaz de Array
simplemente no define un indexador. Utiliza SetValue(Object, Int32)
y Object GetValue(Int32)
. Fíjate en las cosas ominosas de los Object
allí. Array
no es de tipo específico; está construido para el denominador común más bajo: Object
. Podría haber definido un indexador con la misma facilidad, pero en la práctica, todavía tendría el problema de desajuste / boxeo.
Respuesta corta :
System.Array es una clase base para matrices ND (no solo 1-D), por eso el indexador 1-D (objeto este [i] {get; set;}) no puede ser un miembro base.
Respuesta larga :
Si supongamos que crea una matriz bidimensional e intenta acceder a su indexador IList:
Array a;
a=Array.CreateInstance(typeof(char), 1,1);
(a as IList)[0]=''a'';
Obtendrá excepción no soportada.
Buena pregunta sería:
¿Por qué
System.Array
implementaIList
eIEnumerable
mientras que la mayor parte de su implementación lanzaráNotSupportedException
para unaNotSupportedException
no 1-D?
Una cosa más interesante de mencionar. Técnicamente, ninguna de las matrices tiene un indexador de clase internamente en un significado clásico. El significado clásico de Indexer es una propiedad "Artículo" + obtener (+ establecer) método (s). Si profundiza en la reflexión, verá que typeof (string []) no tiene propiedad de indexador y solo tiene 2 métodos Get y Set : los métodos declarados en la clase string [] (no en la clase base, a diferencia de Array.SetValue, Array.GetValue) y se utilizan para la indexación en tiempo de compilación.