sábado, 26 de febrero de 2022

Curso avanzado de C#.Constructores y métodos

A parte de la asignación de valores después de instanciar una clase, hay otra forma, de  asignar dichos valores a los miembros de un objeto. Esto se hace a través de un constructor.  Un constructor es un método que se llama siempre que se crea una instancia de un objeto. Podemos utilizar constructores en nuestros archivos de clase para establecer valores iniciales para algunos o todos los datos miembros en los objetos que creamos. Si no definimos un constructor C# crea su propio constructor. El constructor por defecto establece los valores de Cada variable miembro a su valor por defecto. 

Los constructores tienen una sintaxis específica, como se muestra aquí: 

  // sintaxis para un constructor
 public NombreClase()
 {
       //Instrucciones de inicialización opcionales;
 }
 // Definición de la clase estudiante
 class Estudiante
 {
   public static int CuentaEstudiante;
    public string Nombre;
    public string Apellido;
    public string Grado;
        // Constructor 1 de la clase Estudiante
        public Estudiante(string nombre, string apellido, string grado)
        {
            this.Nombre = nombre; this.Apellido = apellido; this.Grado = grado;
        }
        // Constructor n de la clase Estudiante
        public Estudiante()
        {
        }
}

En el ejemplo anterior hay dos constructores. Los constructores son públicos porque deben ser accesibles fuera de la clase.  Esto es necesario para permitir que el objeto se inicialice cuando se crea. El constructor toma el mismo nombre que la clase. Dentro de las llaves adjuntas, las declaraciones de inicialización son opcionales. Un constructor es un método pero no incluye ningún tipo de retorno, ni siquiera nulo. Incluir un tipo de retorno en un constructor es una sintaxis incorrecta y generará una advertencia del compilador.

El primer constructor es el no predeterminado y acepta tres valores de cadena y los utiliza para inicializar las variables miembro. El segundo constructor es predeterminado y no incluye declaraciones ni toma argumentos.

Este es el tipo de constructor que el compilador se genera si el desarrollador no crea otros constructores. Este constructor inicializa las variables miembro con sus valores por defecto. Cuando creamos un nuevo objeto, el compilador tiene la opción de usar cualquiera de los constructores disponibles declarados en la clase, o ninguno. En el ejemplo anterior podemos llamar al constructor no predeterminado, pasando los valores del nombre, apellido y grado. Si no proporcionamos ningún valor, se llamará al constructor por defecto. Además, no podemos llamar al  constructor no predeterminado anterior
si sólo rellenamos algunos valores. Es todo o nada. Los constructores predeterminados se utilizan solo cuando no se llama a ningún otro constructor.  

Definiendo métodos

Los métodos son los componentes de una aplicación que desglosar los requisitos informáticos de nuestra aplicación en piezas más pequeñas de funcionalidad.  Una buena práctica de programación dicta que los métodos deben realizar funciones discretas en nuestro código y que el método realiza solo lo que es  necesario para lograr el resultado deseado. Algunos argumentan que codificar de esta manera genera un código que ocupa más recursos  debido a que el sistema operativo debe mantener los indicadores de instrucciones y las referencias para todas las llamadas al método función,  pero hace que nuestro código sea más fácil de leer y mantener. Si nuestro programa está generando errores, es mucho más fácil localizarlos. 


Un método es un trozo de código definido por un nombre, una firma, un bloque de declaración  y una variable de retorno opcional. La sintaxis de un método siguiente: 

// sintaxis de un método

modificador return type nombre_metodo(optional argumentos)
{
   intrucciones;
}

En el ejemplo anterior, el modificador puede ser público, privado, etc. El tipo de retorno puede ser cualquier tipo de C# válido (por valor o referencia) pero también puede ser la palabra clave void, que indica que el método no devuelve ningún valor. El nombre se usa para identificar el método en el código y se utiliza para llamar al método. Los los paréntesis incluyen argumentos opcionales para el método. Un método puede tener ninguno o varios argumentos.

La llaves encierran la funcionalidad del método en forma de instrucciones. Estas instrucciones pueden ser cualquiera de C# y también puede incluir una declaración de devolución opcional return sólo si el método tiene declarado un tipo de retorno. 

Es ilegal incluir una declaración de devolución en un método declarado como void. A continuación crearemos dos métodos para la clase de Estudiante. Un método recuperará el nombre y apellido del estudiante, los concatena y devuelve el nombre. El método de llamada no devuelve valor, pero imprimirá el nombre en la ventana de la consola. 

class Estudiante
 {
   public static int CuentaEstudiante;
    public string Nombre;
    public string Apellido;
    public string grado;
   // sintaxis de un método
    public string concatenaNombre()
    {
      string NombreCompleto = this.Nombre + " " + this.Apellido;
        return NombreCompleto;
    }
    // sintaxis de un método
    public void MuestraNombre()
    {
      string nombre = concatenaNombre();
        Console.WriteLine(nombre);
        Console.ReadLine();
    }
}
class Programa
{
   static void Main(string[] args)
    {
      Estudiante primerEstudiante = new Estudiante();
        Estudiante.CuentaEstudiante++; Estudiante segundoEstudiante = new Estudiante();
        Estudiante.CuentaEstudiante++; primerEstudiante.Nombre = "Juan";
        primerEstudiante.Apellido = "González"; primerEstudiante.grado = "sexto";
        segundoEstudiante.Nombre = "Antonio"; segundoEstudiante.Apellido = "Fernández";
        segundoEstudiante.grado = "dos"; primerEstudiante.MuestraNombre();
    }
}

Análisis

Este ejemplo muestra el uso de métodos tanto dentro de la clase como en el método principal de la aplicación. Hemos añadido dos métodos. El primer método se llama concatenaNombre() y devuelve un valor de cadena. Lo hemos declarado público, y con un valor de retorno de cadena. El método no tiene parámetros simplemente declara una variable llamada NombreCompleto de tipo cadena. Luego usa la funcionalidad de concatenación de cadenas de C# y combina la variable Nombre con un espacio y la variable Apellido para crear el nombre completo para el alumno.

Asigna esto a la variable NombreCompleto y luego la envía a la función de llamada con la declaración de retorno Return. La función que llama a concatenaNombre()  es otro método simple que hemos creado, llamado MuestraNombre() utiliza el tipo de retorno void, lo que significa que no devuelve un valor y no tiene una declaración de retorno en el bloque de instrucción. Declara una variable de tipo cadena llamada nombre y utiliza el valor de retorno del método concatenaNombre() para Asignar a la variable nombre. Luego escribe el valor en la ventana de la consola.

En el método principal de la aplicación, agregamos una nueva declaración al final del método, primerEstudiante.MuestraNombre(); Esta declaración utiliza el objeto primer estudiante Objeto creado en el código y llama a su método público MuestraNombre(). La ejecución cambia a este método en el código del objeto. El método crea una variable, y luego en la instrucción de asignación, llama al método concatenaNombre() del mismo objeto. 

La ejecución ahora pasa a este método donde La variable NombreCompleto se crea y se utiliza en una instrucción que concatena el nombre y el apellido.
Debido a que el nombre de cadena de declaración concatenaNombre(); ha sido el responsable de llamar a este método, el compilador ha mantenido un seguimiento en la pila de memoria y sabe dónde está el valor de retorno que necesita la declaración de retorno de concatenaNombre(), cuando finaliza ese método, devuelve el valor al método que le llamó y asigna a la variable la concatenación generada. Ahora el método MuestraNombre () ya puede mostrar el nombre completo en la ventana de la consola. 


Los métodos  pueden aceptar varios valores entrantes. Esto es posible mediante el uso de parámetros y argumentos. La firma del método que acepta valores es de este tipo: 

// sintaxis de un método que toma valores

 modificador return tipo nombre(parametros)
 {
   instrucciones;
}

Desafortunadamente, el uso de los términos parámetros y argumentos suele ser mal utilizado. Cuando se trata de métodos, se utiliza el término parámetro para identificar los marcadores de posición en la firma del método, mientras que los argumentos son los valores reales que se pasan al método. 

// ejemplo de firma de métod que acepta valores
 public int suma(int num1, int num2)
 {
   return num1 + num2;
 }
 int ValorSuma = suma(2, 3);


En el ejemplo anterior, el método suma es público y devuelve un valor entero. Dentro de los paréntesis vemos int num1, int num2. Estos son los parámetros del método. Debemos indicar el tipo de datos que se espera de estos parámetros. Esto ayuda al compilador a capturar asignaciones inválidas cuando se llama al método es  llamado; num1 y num2 son los nombres de los parámetros. La última línea en el código de ejemplo llama al método de suma y pasa dos valores. Estos dos valores son los argumentos de la llamada al método. Podemos ver ahora de dónde viene la confusión  y por qué estos dos términos se usan indistintamente a veces.

Hay que pensar en los parámetros como en marcadores de posición en la firma del método, y los argumentos son los valores que se pasan en a estos marcadores de posición. Los métodos pueden recibir objetos como argumento. Otra distinción importante, es cuando se pasan argumentos por valor, se pasa una copia del valor al método, mientras que al pasar argumentos por referencia, se pasa una referencia (una dirección de memoria) y no se pasa todo el objeto.

Podríamos ocupar bastante memoria si pasamos un objeto entero a un método. Cuando pasamos argumentos por valor, el método actúa sobre una copia local de la variable y no cambia el valor original. Al pasar un argumento por referencia si actuamos sobre el argumento, modificamos su valor. Esto se ve en el siguiente ejemplo. 

Pasando valores a métodos [value_type_passing] 

class Estudiante
 {
        public string Nombre;
        public string Apellido;
        public string grado;
 }
    class Programa
    {
        static void Main(string[] args)
        {
            int num1 = 2; int num2 = 3;
            int resultado;
            Estudiante primerEstudiante = new Estudiante();
            primerEstudiante.Nombre = "Juan";
            primerEstudiante.Apellido = "González";
            primerEstudiante.grado = "seis";
            resultado = suma(num1, num2);
            Console.Write("La suma es: ");
            Console.WriteLine(resultado);
            // devuelve 5
            Console.WriteLine();
            cambiaValores(num1, num2);
            Console.WriteLine();
            Console.WriteLine("Regreso a cambiaValores()");
            Console.WriteLine(num1);
            // devuelve 2
            Console.WriteLine(num2);
            // devuelve 3
            Console.WriteLine();
            Console.WriteLine("El nombre de primerEstudiante es " + primerEstudiante.Nombre);
            cambiaNombre(primerEstudiante);
            Console.WriteLine();
            Console.WriteLine(" El nombre de primerEstudiante es " + primerEstudiante.Nombre);
            Console.ReadLine();
        }

        static int suma(int valor1, int valor2)
        {
            Console.WriteLine("En el método suma()");
            return valor1 + valor2;
        }

        static void cambiaValores(int valor1, int valor2)
        {
            Console.WriteLine("En el método cambiaValores()");
            Console.WriteLine("valor1 es " + valor1);
            // devuelve 2 Console.WriteLine("valor2 is " + valor2);
            // devuelve 3 Console.WriteLine();
            Console.WriteLine("Cambiando valores");
            valor1--; valor2 += 5;
            Console.WriteLine();
            Console.WriteLine("valor1 es ahora" + valor1);
            // devuelve 1 Console.WriteLine("valor2 is now " + valor2);
            // devuelve 8
        }
        static void cambiaNombre(Estudiante refValue)
        {
            Console.WriteLine();
            Console.WriteLine("En cambiaNombre()");
            refValue.Nombre = "Jorge";
        }
}

Análisis

La clase Estudiante está simplificada para este ejemplo, solo tenemos tres campos miembro en el método principal del programa, declaramos cuatro variables, tres de tipo int y una de tipo estudiante. Asignamos valores a num1, num2 y a los miembros del objeto Estudiante llamado primerEstudiante. La variable de resultado se asignará más adelante. El primer método al que llamamos es el método que suma, pasamos a num1 y num2 como argumentos al método.

Dentro de suma(), escribimos un mensaje para indicar que estamos dentro de este método. A continuación, agregamos los dos valores y devolvemos el resultado al método que lo ha llamado. Después mostramos ese resultado en la ventana para ver como el método suma() ha sumado ambos valores, luego mostramos un ejemplo de cómo el método utiliza copias de los valores si estos son suministrados por valor. Llamamos al método cambiaValores() donde le pasamos de nuevo num1 y num2. En cuya salida mostramos los valores numéricos de los dos parámetros valor1 y valor2.

Con esto demostramos que pasamos los mismos valores para num1 y num2. Luego  indicamos que cambiaremos estos valores y decrementamos en 1 el valor1 e incrementaremos en 5 el valor2. Finalmente el método cambiaValores(), genera los nuevos valores para valor1 y valor2. 

De vuelta a la función main, mostramos los valores de num1 y num2 de nuevo para mostrar que estas variables no han sido cambiadas por el método cambiaValores() solo se han cambiado las copias locales, no los valores originales. 

Para ver cómo se ven afectados los tipos por referencia en las llamadas a métodos, ahora mostramos el primer nombre del El objeto primerEstudiante que creamos para mostrar que su valor es Juan, el valor que se le asignó al principio el código. Luego llamamos a otro método llamado cambiaNombre(), que toma una variable por referencia de tipo Estudiante, y pasa el primer estudiante como tipo por referencia al método. Dentro de este método, cambiamos el nombre del primer estudiante a Jorge. Después de volver del método, escribimos el primer nombre del primer estudiante y vemos que de hecho lo hemos cambiado. Esto muestra claramente que al pasar una variable como referencia a un método, obtenemos como resultado el cambio del valor original, muy diferente del tipo pasado por valor.  La Figura muestra la salida del código anterior. 



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.