Mostrando las entradas para la consulta creación de tipos por referencia ordenadas por relevancia. Ordenar por fecha Mostrar todas las entradas
Mostrando las entradas para la consulta creación de tipos por referencia ordenadas por relevancia. Ordenar por fecha Mostrar todas las entradas

sábado, 19 de marzo de 2022

Curso avanzado de C#. Propiedades Indexadas, Tipos genéricos y Métodos genéricos

Las propiedades indexadas, o indexadores, se comportan de manera un poco diferente a las propiedades estándar. El propósito principal de las propiedades indexadas es permitir el "acceso a grupos de elementos de forma similar a como se accede a un array". En otras palabras, si tenemos clases que utilizan matrices u otros tipos de colecciones, debemos considerar el uso de indizadores para acceder a los valores de estas colecciones internas. Las propiedades estándard discutidas hasta ahora se utilizan para acceder a valores individuales en clases, mientras que una propiedad indexada Se utilizará para encapsular un conjunto de valores.

Para mostrar el funcionamiento de las propiedades indexadas, necesitamos un ejemplo simple que lo ilustre. Una aplicación que trate con direcciones de red en un escenario de subredes IP. 

Curso avanzado de C#. Propiedades Indexadas, Tipos genéricos y Métodos genéricos


sábado, 19 de febrero de 2022

Curso avanzado de C#.Tipos por referencia y modificadores

La Programación Orientada a Objetos OOP permite al desarrollador modelar objetos del mundo real en el código mediante el uso de clases. Si creamos una aplicación para un  cajero automático. Necesitará tratar con objetos tales como clientes, cuentas, depósitos, retiros, saldos, etc. Es mucho más fácil escribir código para modelar estos objetos creando una representación de dichos objetos en el código y luego asignar a estos objetos de código las mismas características y funcionalidad que la que tienen los objetos del mundo real. Una clase es un archivo que contiene el código necesario para modelar los objetos del mundo real. Una clase es como una plantilla de objetos. 

Curso avanzado de C#.Tipos por referencia y modificadores


La razón para hacer referencia a estos tipos de objeto como tipos por referencia es que la variable declarada para el tipo mantiene sólo una referencia al objeto real. 

En el código .NET, se utilizan dos secciones lógicas  distintas: de memoria y del ordenador. Se les llama pila (stack) y montón (heap). La pila es un área de memoria reservada por el sistema operativo para la ejecución. En la pila es donde .NET almacena tipos de datos simples en una cantidad relativamente pequeña de memoria utilizada para la ejecución del código. La mayoría de los tipos de datos simples serán creados y destruidos bastante rápido a medida que se ejecuta la aplicación, y por lo tanto la pila se  mantendrá limpia durante la ejecución del código. También es la razón por la que recibiremos excepciones de memoria insuficiente si ponemos un bucle infinito que se ejecuta y que almacena valores en la pila. 

El montón es un área de memoria mucho más grande que .NET Framework utiliza para almacenar los objetos que crea nuestro código basado en clases. Un objeto creado a partir de una clase puede requerir grandes cantidades de  memoria en función del tamaño de la clase. Las clases contienen tipos de datos simples para mantener los valores pertenecientes a las características de los objetos que está modelando. También contienen métodos que proporcionan la funcionalidad que muestra el objeto. 

.NET Framework utiliza la referencia hacia un objeto, que es su dirección de memoria. De este modo, si el código requiere copiar o asignar el objeto a otra variable, la memoria se conserva porque el compilador copia solo la dirección de memoria donde se encuentra almacenado y no el objeto en sí.  Las clases se crean con una sintaxis específica como se muestra aquí: 

class Miclase
 { 
// campos
 // propiedades 
// métodos 
// eventos 
// delegados 
// clases anidadas
 } 


El ejemplo anterior no dicta el orden de los componentes de la clase, sino que simplemente enumera los elementos que puede contener una clase. Además, los artículos enumerados no son obligatorios.  Si estuviéramos modelando un coche, los campos podrían consistir del modelo, marca, color, año, número de puertas, etc. Los campos también se conocen comúnmente como Miembros, miembros de datos y campos de datos.
Las propiedades están directamente relacionadas con los campos. Las propiedades se utilizan para permitir el acceso controlado a los campos. en nuestra clase. 


Los métodos se utilizan para proporcionar funcionalidad a los objetos. Los objetos del mundo real tienen funcionalidad.  Siguiendo la analogía del coche, un coche encender o apagar el motor y puede acelerar, o frenar, detenerse, etc. Estos son ejemplos de métodos.

Los eventos también son funcionalidades en el código, pero de una manera diferente. Son cosas que suceden como resultado de alguna influencia exterior. Por ejemplo, si un sensor en un automóvil detecta un problema, se eleva un evento y el ordenador del  automóvil "escucha" el evento que se está activando con lo que genera una advertencia. Los eventos son un mecanismo por el cual los objetos notifican a otros objetos cuando sucede algo. El objeto que provocó el evento es el editor de eventos (event publisher) y el objeto que recibe el evento es el suscriptor del evento (event subscriber ).

Un delegado es "un tipo que hace referencia a un método". Si pensamos en un delegado en términos de un escenario político. Por ejemplo, un político delegado es alguien que ha sido elegido para representar a una o más personas. En C#, un delegado se puede asociar con cualquier método que tenga una firma similar (tipos de argumento). 

Modificadores 

Los modificadores se utilizan en la declaración de tipos y los datos miembro de nuestros tipos por referencia. En la siguiente tabla enumeraremos los modificadores disponibles en C# junto con una descripción de lo que hacen. 


Modificador
Descripción
public
Modificador de acceso que declara la accesibilidad del tipo al que está asignado. Este es el nivel más permisivo. Se permite el acceso fuera del cuerpo de la clase o estructura. Los tipos por referencia y valor pueden ser declarados públicos. Los métodos también pueden ser declarados públicos.
private
Declara la accesibilidad del tipo que se le asigna. Es el menos permisivo, permite el acceso solo dentro del cuerpo de la clase o estructura Los tipos de referencia y valor pueden ser declarados privados. Los métodos también pueden ser declarados privados.
internal
Modificador de acceso que declara la accesibilidad al tipo al que está asignado.  Permite el acceso solo dentro de archivos en el mismo ensamblaje .NET.
protected
Modificador de acceso para miembros. Los miembros declarados como  protected son accesibles solamente desde dentro de la clase y en las clases derivadas.
abstract
Utilizado en clases,  indica que la clase no puede ser instanciada pero que sirve como clase base para otras clases en una jerarquía de herencia.
async
Configura el método o la expresión lambda a la que se aplica como método asíncrono.

Permite a los métodos llamar a procesos de larga duración sin bloquear el código de llamada.
const
Aplicado a un campo, indica que el campo no puede modificarse. Las constantes deben inicializarse en el momento de su creación.
event
Se utiliza para declarar eventos en el código.
extern
Se utiliza para indicar que el método se ha declarado e implementado externamente.  Se  puede usar esto con archivos DLL importados o conjuntos externos.
new
Cuando se utiliza con los miembros de una clase, este modificador oculta los miembros heredados de los miembros de la clase base. Es necesario hacer esto si hemos heredado un miembro de una clase base pero nuestra clase derivada necesita utilizar nuestra propia versión de ese miembro.
override
Se utiliza cuando heredamos la funcionalidad de una clase base que queremos cambiar. 
partial
Los archivos de clase pueden existir en varios archivos en el mismo ensamblaje. Este modificador dice el compilador que la clase existe en otro archivo o archivos en el ensamblaje.
readonly
Los miembros de solo lectura pueden asignarse solo durante la  declaración o en un constructor de clase. No se permite ningún otro medio para cambiar o asignar un valor a ese miembro.
sealed
Se aplica a clases. Las clases selladas no se pueden heredar.
static
Cuando se aplica a un miembro de la clase, significa que pertenece a la clase  solo y no a objetos específicos creados a partir de la clase. .NET Framework tiene muchos ejemplos de esto, como la clase Math o la clase String.
unsafe
C# es código administrado, lo que significa que las operaciones de memoria se utilizan de forma protegida. El uso de la palabra unsafe  declara un contexto que no es seguro en términos de gestión de memoria, los punteros de C++ son ejemplos de operaciones de memoria no segura. Las operaciones para utilizar punteros en C#, se deben declarar en un contexto no seguro
virtual
Si creamos una clase y queremos permitir que el método se sobrescriba en una clase derivada, podemos utilizar la palabra  virtual.
volatil
Cuando se aplica a un campo, el campo puede ser modificado por componentes  aparte de nuestro código, como por ejemplo el sistema operativo


Definiendo campos

Utilizamos campos para almacenar los datos que describen las características de las clases. Los campos se declaran como variables dentro de la clase y pueden ser de cualquier tipo y pasarse por valor o por referencia.

Los campos vienen en dos tipos básicos, como instancia y estáticos, y una clase puede contener un tipo o los dos. Un campo de instancia es el que utilizaremos más a menudo en nuestras clases. Los campos de instancia son aquellos contenidos dentro de cada objeto que se crea desde la definición de la clase. Cada campo de instancia contiene datos específicos para el objeto al que está asignado. Como ejemplo, crearemos una clase simple y luego crearemos dos instancias de la clase, estableciendo diferentes valores para los campos de la clase. 

Clase que muestra campos de instancia  [clase_estudiante] 

// crea una clase llamada estudiante
class Estudiante
    {
        public static int CuentaEstudiantes;
        public string Nombre;
        public string Apellido;
        public string Grado;
    }
    class Programa
    {
        static void Main(string[] args)
        {
            Estudiante primerEstudiante = new Estudiante();
            Estudiante.CuentaEstudiantes++;
            Estudiante segundoEstudiante = new Estudiante();
            Estudiante.CuentaEstudiantes++;
            primerEstudiante.Nombre = "Mariano";
            primerEstudiante.Apellido = "González";
            primerEstudiante.Grado = "primero";
            segundoEstudiante.Nombre = "Timoteo";
            segundoEstudiante.Apellido = "Pérez";
            segundoEstudiante.Grado = "segundo";
  Console.WriteLine(primerEstudiante.Nombre);        Console.WriteLine(segundoEstudiante.Nombre); Console.WriteLine(Estudiante.CuentaEstudiantes);
            Console.ReadLine();
        }
    }

Análisis

La primera parte del código crea una clase simple llamada Estudiante. En esta clase, creamos cuatro variables.

Una se declara como una variable estática de tipo int y se llama Cuentaestudiantes. Utilizamos esta variable para hacer un seguimiento de cuántos estudiantes hemos creado. Debido a que es estática, es una variable que se asigna a la clase, no a una instancia.  Cada una de las variables restantes son variables de instancia y se asignarán valores en cada objeto (instancia) de esta clase que hemos creado. 

Dentro del método principal, hemos creado dos instancias de la clase Estudiante: una llamada primerEstudiante y otra llamada segundo estudiante. 

Podemos hacerlo indicando primero el tipo de variable que deseamos utilizar. De la misma manera que creamos los tipos por valor, utilizaremos el nombre del tipo seguido del nombre de la variable.
En este caso, el nombre de la variable es en realidad el nombre de un objeto del tipo de la clase que creamos en el código. La palabra clave new dice al compilador que deseamos crear una nueva instancia del tipo de clase Estudiante. La nueva palabra clave es una instrucción para que el compilador vea la clase Estudiante, identificamos a los miembros y sus tipos de datos, luego reservamos suficiente memoria para almacenar el objeto y todos nuestros requisitos de datos. Después de creamos cada objeto, utilizamos la variable estática en la clase Estudiante y le agregamos uno. Esta La variable solo está disponible en la clase y no en los objetos de instancia, por lo que debemos utilizar el nombre de la Clase Estudiante para acceder a esta variable. Una vez que hemos creado las instancias,  les asignamos valores a los miembros. Utilizaremos el nombre de cada instancia para asignar valores a sus miembros. Y aquí es donde está la diferencia entre las variables estáticas y de instancia. Después de las asignaciones, se envían los valores a la consola. En este caso, solo se imprimen los nombres de cada instancia Estudiante para mostrar que los valores son realmente únicos para cada instancia. Podemos llevar un recuento de objetos de la clase Estudiante porque CuentaEstudiantes es una variable de clase estática y no una variable de instancia.


sábado, 29 de enero de 2022

Curso avanzado de C#: Tipos de datos


C # divide los tipos de valor en dos categorías distintas conocidas como estructuras y enumeraciones. Las estructuras se dividen a su vez en subcategorías llamadas tipos numéricos (tipos enteros, tipos decimales de coma flotante y decimales), tipos booleanos y estructuras definidas por el usuario. Las enumeraciones se forman como un conjunto de tipos declarados usando la palabra clave enum. 

Curso avanzado de C#: Tipos de datos


Tipos de valores predefinidos

Los tipos de valor identifican valores específicos de datos. C# incluye tipos de datos intrínsecos comunes a muchos otros lenguajes de programación y se usan para almacenar valores simples.  Los tipos de datos intrínsecos de C # tienen asignaciones directas a los tipos de .NET. Los nombres listados en la columna de tipos de la siguiente tabla son conocidos como alias para los tipos .NET. Todos los tipos de valores derivan de System.ValueType. 
La Tabla enumera los tipos de valores básicos que admite C#, incluido el rango de los valores de datos que estos soportan.

Tipo
Valor
Tamaño
Tipo .Net
bool
true/false
1 byte
System.Boolean
byte
Signed 32-bit
1 byte
System.Byte
char
0000–FFFF Unicode
16 bit
System.Char
decimal
±1 0 × 10−28  a ±7 9 × 1028
28-20 digitos significativos
System.Decimal
double
±5 0 × 10−324 a ±1 7 × 10308
15-16 digitos
System.Double
enum
Colección de constantes definidas por el usuario


float
±1 5 × 10−45 to ±3 4 × 1038
7 digitos
System.Single
int
–2,147,483,648  a 2,147,483,647
Signed 32-bit
System.Int32
long
9,223,372,036,854,775,808 a 9,223,372,036,854,775,807
Signed 64-bit
System.Int64
sbyte
–128 a 127
Signed 8-bit
System.SByte
short
–32,768 a 32,767
Signed 16-bit
System.Int16
struct
Incluye los tipos numéricos listados en esta tabla, incluyendo el boolean y  estructuras definidas por el usuario


uint
0 a 4,294,967,295
Unsigned 32-bit
System.UInt32
ulong
0 a 18,446,744,073,709,551,615
Unsigned 64-bit
System.UInt64
ushort
0 a 65,535
Unsigned 16-bit
System.Uint16

Para trabajar con los tipos de datos, declaramos una variable del tipo que deseamos. Después almacenamos en ella un valor del tipo declarado a través de una asignación. La asignación también se puede incluir como parte de la declaración. El siguiente código demuestra ambas opciones:

// declaramos una variable integer
int miEntero;
// Y le asignamos un valor
miEntero = 3;
// Declaramos y signamos en la misma instrucción
int miSegundoEntero = 50;
        
La palabra clave int se utiliza para indicar que la variable será de tipo entero, que es el alias para el tipo System.Int32. Como resultado, podrá contener cualquier valor desde 2,147,482,648 negativo a 2,147,482,647 positivo. 

Nota: No confundir los tipos de datos de C#  con nombres similares que se encuentran en los conceptos matemáticos. Por ejemplo, el tipo de datos int, que es la abreviatura de entero, no es lo mismo que el concepto de entero matemático. Los enteros en matemáticas pueden contener valores desde menos infinito hasta infinito positivo. Sin embargo, los tipos de datos en C # dependen de la cantidad de bits utilizados para contener el tipo de datos. En este caso, int tiene 32 bits ; 2 elevado a la potencia de 32  proporciona un máximo de 4,294,967,296. Quitamos 1 bit para usar para el signo, y tendremos los valores listados en la tabla anterior para int. 

Hay que tener en cuenta un par de restricciones con los tipos. No podemos derivar un nuevo tipo de un tipo de valor, y los tipos de valor no pueden contener un valor nulo. Ahora, aquí es donde el uso del alias para un tipo de valor y el tipo de sistema .NET difieren. Intentando usar un alias con una variable no asignada en código, resultará que Visual Studio generará un error sobre el uso de una variable no asignada. Cada tipo de valor tiene un tipo correspondiente de .NET en el espacio de nombres del sistema, y podemos utilizar una versión de este tipo sin firmar. Esto es posible porque los tipos de sistema son clases (tipos de referencia), se crean mediante el uso del nuevo operador y contienen un valor predeterminado. 

Al final de cada ejemplo pondremos la instrucción

Console.ReadLine(); 

Para que la pantalla se detenga y podamos leer el resultado. Saldremos de esta pantalla pulsando Enter.

Comparación de tipos de valor y sus alias [value_type_alias]


// crea una variable para mantener un tipo por valor
// utilizando el formulario de alias
 // pero no asigna la variable
 int miEntero;
 int miNuevoEntero = new int();
 // crea una variable para un tipo .NET 
 // Este tipo es la versión .NET alias de la forma int
 // el uso de new, crea un objeto de la clase System.Int32
int miEntero32 = new System.Int32();

// tendremos que comentar esta primera declaración de //Console.WriteLine pues .Net mostrará un error sobre el uso de una variable no asignada
// Esto se hace para evitar el uso de un valor que se //almacenó en la ubicación de la memoria antes de la //creación de esta variable

Console.WriteLine(miEntero);

// Imprime el valor por defecto asignado a la variable int
// que no tenía un valor asignado previamente //Console.WriteLine(miNuevoEntero);
// Esta instrucción imprimirá el valor por defecto de este //tipo de datos, por defecto el 0
 
Console.WriteLine(miEntero32);
Console.ReadLine(); 

En el ejemplo de código anterior, la variable miEntero32 se crea como un nuevo objeto del tipo System.Int32 .NET. No se proporciona un valor en la declaración System.Int32

int miEntero32 = new System.Int32(); 

Como resultado, Visual Studio llama al constructor predeterminado para este objeto y le asigna el valor predeterminado. Se crea una variable llamada miNuevoEntero usando la palabra clave new. .NET Framework reconoce que esta forma de declaración de variable es la misma que al usar el estilo de variable System.Int32. Aunque la declaración de

 int miEntero;

no permite generar el valor de esta variable si no se ha asignado, la declaración de

int miNuevoEntero = new int();

Permite generar la variable no asignada. Sin embargo, esta segunda versión no se usa a menudo cuando se trata de tipos simples, aunque nada impide usarla. 

.NET Framework proporciona valores predeterminados para todos los tipos de valores del sistema creados de esta manera. Los valores predeterminados para todos los tipos numéricos son equivalentes a cero. Cualquiera de los tipos de coma flotante como decimal, double o float será 0.0. El valor predeterminado para boolean es false, char es '\ 0',  enum es (E) 0 y las estructuras se establecen en nulo. Otro aspecto importante que debe entenderse sobre los tipos de valor es la forma en que se administran los valores. .NET Framework almacena los tipos de valor en la pila (stack) en lugar de en el montón (heap). El resultado es que si asignamos un tipo de valor a otro, se copiará el valor del primero al segundo. Los tipos por referencia copian una referencia (dirección de memoria) en oposición a los valores reales. El siguiente código de ejemplo muestra la creación de dos variables enteras. Se asigna un valor a una de las variables y luego se asigna una variable a otra. 

// asignando un tipo de valor a otro
int miEntero;
int segundoEntero;
//se asigna 2 a miEntero
miEntero = 2;
// segundoEntero contendrá el valor 2
segundoEntero = miEntero;
// Mostramos el valor de las variables
          
Console.WriteLine(miEntero);
Console.WriteLine(segundoEntero);
Console.WriteLine();

Aunque en los ejemplos anteriores sólo se ha mostrado el tipo de datos enteros, trabajar con los otros tipos de valores simples es similar. 

Usando value types [using_value_types]

// declaramos algunos tipos de dato numéricos
int miInt;
double miDouble;
byte miByte;
char miChar;
decimal miDecimal;
float miFloat;
long miLong;
short miShort;
bool miBool;
// asignamos  valores a estos tipos 
// los imprimimos en la consola
// utilizamos sizeOf para determinar
// el numero de bytes que ocupa cada tipo
miInt = 5000;
Console.WriteLine("Integer");
Console.WriteLine(miInt);
Console.WriteLine(miInt.GetType());
Console.WriteLine(sizeof(int));
Console.WriteLine();
miDouble = 5000.0;
Console.WriteLine("Double");
Console.WriteLine(miDouble);
Console.WriteLine(miDouble.GetType());
Console.WriteLine(sizeof(double));
Console.WriteLine();
miByte = 254; Console.WriteLine("Byte");
Console.WriteLine(miByte);
Console.WriteLine(miByte.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine();
miChar = 'r';
Console.WriteLine("Char");
Console.WriteLine(miChar);
Console.WriteLine(miChar.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine();
miDecimal = 20987.89756M;
Console.WriteLine("Decimal");
Console.WriteLine(miDecimal);
Console.WriteLine(miDecimal.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine();
miFloat = 254.09F;
Console.WriteLine("Float");
Console.WriteLine(miFloat);
Console.WriteLine(miFloat.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine();
miLong = 2544567538754;
Console.WriteLine("Long");
Console.WriteLine(miLong);
Console.WriteLine(miLong.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine();
miShort = 3276;
Console.WriteLine("Short");
Console.WriteLine(miShort);
Console.WriteLine(miShort.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine(); miBool = true;
Console.WriteLine("Boolean");
Console.WriteLine(miBool);
Console.WriteLine(miBool.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine();
Console.ReadLine();  //esta última línea es la más importante pues para la pantalla y nos permite leer el resultado

Análisis

Este código declara variables de varios tipos de valores que son intrínsecos a C #. Luego, cada variable se usa en un conjunto repetido de declaraciones de código es:

Asigna un valor a la variable 
Muestra una línea en la consola que indica el tipo de valor 
Muestra el valor asignado 
Muestra el tipo de sistema asociado al tipo de valor 
Muestra el tamaño del tipo de valor en bytes 

Para obtener una comprensión completa de estos tipos, cambiaremos los valores en las declaraciones de asignación a diferentes tipos o fuera del rango y veremos lo qué devuelve el compilador para los mensajes de error. Es necesario comprender estos tipos simples para representar los datos que sus aplicaciones utilizarán para representar problemas del mundo real. 

Mejores Prácticas: Eficiencia de código

Los desarrolladores que escriben código hoy pasan cada vez menos tiempo pensando en la eficiencia del código y los tipos de datos utilizados, principalmente debido a la potencia y la capacidad de almacenamiento de los ordenadores que se usan en la actualidad. En los primeros días de la computadora personal, la memoria era una prioridad, y todo el código escrito se hacía para conservar el uso de la memoria de la aplicación. Comprender el tamaño de los datos le ayuda a elegir el tipo de datos adecuado para nuestras necesidades de almacenamiento. Un tipo de datos demasiado grande puede desperdiciar recursos, mientras que un rango de tipos de datos demasiado pequeño puede causar problemas de desbordamiento y, a veces, problemas generales en los que al incrementar un valor int con signo (que puede ir de 32.767 a –32.767)  puede causar errores difíciles de detectar y localizar.