lunes, 4 de septiembre de 2017

La Clase Array en C# - Parte 1/3

Índice

1. Introducción
2. Palabras Clave
3. Generalidades
3.1 Jerarquía de herencia e implementación
3.2 Sintetización de pseudo-subtipos
3.3 Tipos por referencia y por valor
3.4 Comparación y copiado
4. Creación e Indexación
5. Enumeración
5.1 Ciclo for
5.2 Ciclo mejorado foreach
5.3 Método de extensión ForEach
6. Longitud y Rango
6.1 Longitud
6.2 Rango
7. Conclusiones
8. Literatura & Enlaces

1. Introducción

Esta serie de tres artículos comprende el estudio de la clase Array en lenguaje de programación C#. Esta clase, como ya veremos, es una implementación particular de IList; tanto su versión no-genéria como genérica, dado que el acceso a los elementos se realiza a través de un índice. Aún sí, sabremos que no todos los métodos de estas clases tendrán un codificación específica, sino que se lanzará la excepción NotSupportedException.


La primera parte es una introducción acerca de los fundamentales. La segunda se enfocará en búsqueda y ordenamiento de elementos. La última tratará acerca de copiado, conversión y redimensionamiento de arreglos.

2. Palabras Clave

  • Array
  • Arreglo
  • Coleccion
  • Copia deep
  • Copia shallow
  • Estructura de datos
  • Índice
  • List

3. Generalidades

3.1 Jerarquía de herencia e implementación

La clase Array es una de las clases base que implementa las interfaces estándar de colección; i.e., IList, ICollection y IEnumerable (Albahari, 2012). Se trata de un tipo abstracto que provee métodos estáticos y de instancia para realizar operaciones sobre arreglos: creación, manipulación, búsqueda y ordenamiento ("Array Class", 2017).


Se sabe que uno de los principios de .NET Framework es la unificación de tipos de datos, y en este caso la clase Array provee un conjunto de métodos comunes para todos los diferentes tipos de datos adyacentes a los elementos de un arreglo.


Con relación a lo anterior, los arreglos son considerados una estructura de datos o colección esencial en el lenguaje de programación C# y todos los demás lenguajes de programación compatibles con la CLR. En el artículo Arreglos en C# se expone la sintaxis declarativa y de manipulación de arreglos sobre C#.

3.2 Sintetización de pseudo-subtipos

Otra característica interna y esencial es la sintetización de pseudo-subtipos de arreglos tanto para el tamaño o dimensión como para el tipo de dato subyacente. Por ejemplo, si se crea un arreglo con el tipo de dato string, las interfaces génericas que implementa Array cambiarán su tipo paramétrico a este mismo: IList<string>.


Adicionalmente la máquina virtual CLR asignará al arreglo recién creado un espacio contiguo en la memoria de trabajo. Aunque esto resulte eficiente en el acceso basado en índices, no se permite el redimensionamiento en el ciclo de ejecución del programa. El método Resize<T> ("Array.Resize(T)", 2017) permite cambiar el tamaño de un arreglo especificado por uno nuevo; sin embargo, cualquier otra referencia al arreglo anterior continuará sin ser modificada. Para solucionar esta carencia, se opta por el uso de colecciones dinámicas como List<T>.

3.3 Tipos por referencia y por valor

El almacenamiento de los tipos por valor se computa a partir del tamaño del tipo dato; es decir, que si un arreglo de 5 elementos de tipo de dato int -4 bytes-, ocupará 20 bytes. Este no es el caso para los arreglos que localizan tipos por referencia: cada elemento del arreglo sólo ocupará el espacio dependiendo de la arquitectura del sistema: 4 bytes en un ambiente de 32 bits u 8 bytes en un ambiente de 64 bits.


Vale tomar el ejemplo expuesto por Albahari (2012) para la ilustración gráfica de este modelo de almacenamiento:
Modelo almacenamiento tipos por referencia y por valor
Figura 1. Modelo almacenamiento tipos por referencia y por valor (Albahari, 2012).

Esta ilustración puede traducirse al siguiente código fuente:

StringBuilder[] builders = new StringBuilder[5];
builders[0] = new StringBuilder("builder1");
builders[1] = new StringBuilder("builder2");
builders[2] = new StringBuilder("builder3");

long[] numbers = new long[3];
numbers[0] = 12345;
numbers[1] = 54321;

3.4 Comparación y copiado

Todos los arreglos en su defecto son tipos por referencia a razón que Array es una clase. Si se tienen dos variables, digamos arreglo1 y arreglo2, entonces al realizar la siguiente asignación arreglo1 = arreglo2, ambas apuntarán al mismo arreglo en memoria.


En cuanto a la comparación del contenido de los arreglos -i.e., comparación estructural- el operador == y el método Equals producen false como resultado: 


object[] a1 = {"Balzac", 1799, true};
object[] a2 = {"Balzac", 1799, true};

Console.WriteLine(a1 == a2);
Console.WriteLine(a1.Equals(a2));


Para realizar comparación estructural podemos usar un comparador de igualdad personalizado o por medio de la clase static StructuralComparisons (disponible a partir de la versión 4.0 de .NET Framework): 


IStructuralEquatable se1 = a1;
Console.WriteLine(se1.Equals(a2, StructuralComparisons.StructuralEqualityComparer);

Esta sentencia da como resultado true, a razón de que el contenido de ambos arreglos es igual.


Por su parte, la operación de clonación, la cual se lleva a cabo con el método Clone, como en 


arregloB = arregloA.Clone()


genera una copia shallow: lo que quiere decir que sólo se copia la memoria que representa al arreglo y no los valores de los elementos y sus datos localizados en la memoria. La Figura 2 ilustra mejor este concepto a partir del siguiente código fuente (Albahari, 2012):


StringBuilder[] builders2 = builders;
StringBuilder[] shalowClone = (StringBuilder[]) builders.Clone();

Copia shalow
Figura 2. Copia shalow.

Para crear una copia del contenido y las referencias de un arreglo, se debe efectuar una copia de tipo deep. Para lograrlo es necesario iterar el arreglo y realizar una clonación por cada elemento. Esto también aplica para los demás tipos de colecciones de .NET Framework.

4. Creación e Indexación

C# provee diferentes alternativas para la creación y acceso por medio de índices. Una formá básica comprende el uso de esta sintaxis:

int[] arregloEnteros = {2, 3, 5};
Console.WriteLine(arregloEnteros[0]); // Primer elemento
Console.WriteLine(arregloEnteros[arregloEnteros.Length - 1]); // Último elemento


Otra de las formas disponibles consiste en usar el método static CreateInstance:
Versiones sobrecargadas de CreateInstance
Tabla 1. Versiones sobrecargadas de CreateInstance ("Array.CreateInstance", 2017).

Entonces el arreglo definido anteriormente también se puede escribir así:

Array arregloEnteros = Array.CreateInstance(typeof(int), 3);
arregloEnteros.SetValue(2, 0);
arregloEnteros.SetValue(3, 1);
arregloEnteros.SetValue(5, 2);
Console.WriteLine(arregloEnteros.GetValue(0)); // Primer elemento
Console.WriteLine(arregloEnteros.GetValue(arregloEnteros.Length - 1)); // Último elemento


Nótese que el tipo de dato asociado al arreglo se define a través del operador typeof (El Método GetType y el Operador typeof en C#).



Como se advierte en Albahari (2012), independiente del mecanismo de inicialización de un arreglo que se use, cada uno de sus elementos se inicializan de manera automática.

5. Enumeración

Para recorrer un arreglo es posible utilizar diferentes mecanismos. Partimos de esta definición:

int[] arregloEnteros = {2, 3, 5};

5.1 Ciclo for

for(int i = 0; i < arregloEnteros.Length; ++i)
{
    Console.WriteLine(arregloEnteros[i]);
}

Aquí el recorrido se realiza a partir de un índice: la variable entera i. El arreglo itera desde el primer elemento -0- hasta que la condición de continuación de ciclo no se cumpla -i == arregloEnteros.Length-

5.2 El ciclo mejorado foreach

foreach(int valor in arregloEnteros)
{
    Console.WriteLine(valor)
}

Este mecanismo es más simple que el anterior, pero no permite la modificación de los datos adyacentes al arreglo.

5.3 El método de extensión ForEach

Con el método de extensión ForEach, se especifica el arreglo a ser recorrido y la acción a aplicar en cada elemento:

Array.ForEach(arregloEnteros, Console.WriteLine);

6. Longitud y Rango

6.1 Longitud

Para obtener la longitud o tamaño de una dimensión de un arreglo se usan los métodos de instancia Array.GetLength y Array.GetLongLength.

En lo que se refiere a las propiedades Length y LongLength, éstas obtienen la cantidad total de elementos en todas las dimensiones del arreglo.

Otros métodos interesantes son:
  • GetLowerBound: Obtiene el índice del primer elemento de la una dimensión especificada.
  • GetUpperBound: Obtiene el índice del último elemento de la una dimensión especificada.

6.2 Rango

El rango de un arreglo se obtiene a través de la propiedad Rank.

7. Conclusiones

En esta primera parte comprendimos los principios de la clase Array: una clase unificadora para definir arreglos básicos, ya sea de tipos por valor o tipos por referencia. Estudiamos las formas de declarar y acceder los elementos: básada en índices y métodos static y de instancia. Más adelante nos concentramos entender cómo se recorre o enumera los elementos: for, foreach y ForEach. Al final, se reconocieron los métodos para obtener el tamaño y el número de dimensiones de un arreglo.

8. Literatura & Enlaces

Albahari, J., Albahari, B. (2012). C# 5.0 in a Nutshell. United States: O'Reilly Media.
Array Class (System) (2017). Recuperado desde: https://msdn.microsoft.com/en-us/library/system.array%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
Array Class (System) (2017). Recuperado desde: https://msdn.microsoft.com/en-us/library/system.array(v=vs.110).aspx
Arreglos en C# (2017). Recuperado
desde: https://ortizol.blogspot.com/2013/09/arreglos-en-c.html
Array.CreateInstance Method (System) (2017). Recuperado desde: https://msdn.microsoft.com/en-us/library/system.array.createinstance(v=vs.110).aspx


O

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.