Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Gabriel Moroni
Queda prohibida la reproducción total o parcial de este libro, por cualquier medio o procedimiento sin
la expresa autorización del autor.
Gabriel Moroni (Argentina, 1988) es el fundador de la comunidad The Coder Cave y se dedica tiempo
completo al mundo del software desde hace más de 15 años. Es Software Architect y Scrum Master,
aunque se ha desempeñado en todos los roles del proceso de desarrollo.
A lo largo de su carrera ha construido una gran variedad de soluciones utilizando diversas
tecnologías y lenguajes de programación, pero se ha especializado en tecnologías .NET en los últimos
diez años.
Siempre ha tenido una gran pasión por enseñar y compartir conocimiento. Cree firmemente que en
la programación esta el futuro por lo que decidió crear The Coder Cave: un espacio para formar a
programadores de todos los niveles.
The Coder Cave tiene el objetivo de mejorar de manera tangible la vida de un millon de
programadores.
Si quieres ser parte de la comunidad, puedes seguirme en cualquiera de mis redes (Youtube,
Twitter y TikTok principalmente), entrar al Discord de la comunidad o ingresar a
www.thecodercave.com en donde encontrarás cursos completos, tutoriales y mucho material que te
ayudará a llevar tu carrera al siguiente nivel.
¡Te esperamos!
Contents
Title Page
Copyright
Sobre el autor
Aprende C# Desde Cero
Introducción
Variables
Constantes
Tipos de Datos Primitivos
Conversión De Tipo de Datos
Operadores
Comentarios
Clases
Tipos de Datos no Primitivos
Tipos de Variable
Estructuras de Control
Colecciones
Programación orientada a objetos
Conceptos Avanzados
Aprende C# Desde Cero
Introducción
¿Que es .NET?
Definir qué es .NET suele resultar confuso para los recién iniciados, así
que intentaré dar la definición más simple posible: .NET es una plataforma de
desarrollo. Ni más ni menos.
En otras palabras, .NET es un conjunto de lenguajes, librerías, herramientas y
otras plataformas, es decir, un ecosistema completo de desarrollo. Es gratuito
y de código abierto.
Con .NET podemos crear todo tipo de aplicaciones, desde aplicaciones
web hasta aplicaciones móviles pasando por Machine Learning, desarrollo de
videojuegos, aplicaciones de escritorio y aplicaciones en la nube.
Además .NET ofrece un marco de desarrollo común, lo cual permite al
desarrollador utilizar las mismas habilidades y conocimientos para escribir
una aplicación para una Raspberry Pi, una aplicación para un dispositivo
móvil o un sitio web para ser desplegado en la nube.
Esta última característica es una de las mayores ventajas de aprender
.NET: una vez que conoces los fundamentos de alguno de sus lenguajes y sus
librerías, no hay límites en cuanto a lo que puedes construir.
Visual Basic .NET es el más antiguo de los tres, tiene una larga tradición
y evoluciona del antiguo Visual Basic que seguro has escuchado alguna vez.
Microsoft lanzó Visual Basic .NET en 2002 justamente para convertirlo en
un lenguaje amigable, fuertemente tipado y orientado a objetos.
C# es, sin dudas, el más popular de los lenguajes del ecosistema .NET.
Es un lenguaje simple, moderno, completamente orientado a objetos
(aunque también soporta las características principales de la programación
funcional) y además es fuertemente tipado.
Es uno de los lenguajes más utilizados del mundo, especialmente en entornos
corporativos.
Pese a las grandes ventajas que .NET Framework tenía, uno de sus
mayores problemas era que no funcionaba en ningún otro sistema operativo
fuera de Windows. Esta era además una de las principales razones por las
cuales la adopción de .NET no tenía la escala que Microsoft buscaba. Es por
esto que en el año 2014 Microsoft anuncia .NET Core, una re-
implementación de .NET Framework pero multiplataforma.
string customer;
Esta es la forma más básica de declarar una variable: separar ese espacio
de memoria para agregarle el valor más adelante, siempre y cuando ese valor
sea del tipo de datos declarado. El tipo de datos string sólo permite almacenar
valores de tipo texto en la variable.
using System;
Este es el resultado:
Hello World!
using System;
John
using System;
JohnDoe
using System;
Esto nos daría como resultado una nueva cadena de texto: “Name:
JohnDoe”
Name: JohnDoe
Nada nos impide agregar un espacio entre las dos variables para hacer el
texto más legible.
using System;
Lo que vimos hasta aquí hace referencia a variables de tipo texto, en las
que el operador “+” tiene el efecto de concatenar cadenas de texto, pero si
utilizamos el mismo operador para variables de tipo numérico, lo que
obtendremos será una clásica suma.
using System;
int number1 = 5;
int number2 = 6;
Console.WriteLine(number1 + number2);
}
11
Es decir que tenemos dos variables de tipo int, inicializadas con valores 5
y 6, y lo que imprimimos en consola es la suma de esos dos valores.
int number1 = 5;
Console.Writeline(number1);
number1 = 20;
Console.Writeline(number1);
5
20
using System;
int a,
b = 10,
c = 50;
using System;
Console.WriteLine(b);
b = c;
Console.WriteLine(b);
}
Ejemplo:
using System;
3.141592653589793
Lo que obtenemos como resultado es el valor de la constante.
El valor de PI siempre será el mismo y el hecho de que sea una constante
nos garantiza que durante su uso nunca se verá alterada de ningún modo por
la aplicación y podemos usarla cuando queramos así sin más.
using System;
const string ConfigName = "This config will never change";
Console.WriteLine(ConfigName);
Al igual que las variables, las constantes pueden adoptar cualquiera de los
tipos de datos soportados por C#.
using System;
12
Tipos de Datos Primitivos
Introducción
Esta es la lista de los datos primitivos con los que cuenta C#:
int a = 100;
System.Int32 a = 100;
La palabra clave bool representa un valor booleano que puede ser true o
false (verdadero o falso) y se utiliza así:
bool ok = true;
bool ok = false;
Las variables de tipo bool son iguales a cualquier otra variable con la
diferencia de que, en este caso, sólo pueden almacenar estos dos valores: true
o false.
Enseguida lo veremos en el práctico pero por ahora tienes que saber que
el resultado de los operadores de comparación e igualdad siempre serán de
tipo bool.
El valor predeterminado del tipo bool si no se inicializa explícitamente es
false.
Char
Char representa un carácter unicode utf 16, por ejemplo, una letra:
char a = “m”;
Para verlo de una manera más simple y gráfica, puedes pensar en el tipo
string como en una secuencia de caracteres char. Es decir que char se puede
entender como una cadena de texto de un único carácter. El tipo char admite
operadores de comparación, igualdad, incremento y decremento.
Conversión De Tipo de Datos
C# es un lenguaje fuertemente tipado, lo que significa que después de
declarar una variable y definir su tipo de datos esa variable no se puede
volver a declarar ni se le puede asignar un valor de otro tipo de datos, a
menos que este sea convertible de forma implícita.
Por ejemplo: un valor de tipo string no se puede convertir implícitamente
a uno de tipo int de la misma forma en la que una variable declarada como int
no se le puede asignar una cadena de texto. Intentarlo daría un error de
compilación como este:
Hasta ahora hemos convertido números enteros que son del mismo grupo
de tipos de datos, pero qué pasa si volvemos al ejemplo del comienzo de la
lección en donde tenemos un string que nosotros sabemos que puede ser
convertido a un tipo int. Para convertir este valor de tipo texto a un valor
numérico podemos llamar al método parse o al método tryparse del tipo
numérico al cual queremos convertir la variable. Cada uno de los tipos
numéricos que hemos estudiado hasta aquí posee estos dos métodos parse y
tryparse, los cuales pueden usarse indistintamente con la única diferencia de
que el método tryparse devolverá un valor booleano indicando si la
conversión fue exitosa o no, además del resultado de la conversión.
using System;
Console.WriteLine(secondnumber);
100
using System;
Console.WriteLine(secondnumber);
using System;
Console.WriteLine(secondnumber);
using System;
232
using System;
1234
using System;
using System;
try
{
}
catch (Exception)
{
using System;
try
{
string txtnumber = "1234";
int num = Int32.Parse(txtnumber);
Console.WriteLine(num);
}
catch (Exception)
{
Console.WriteLine("something went wrong");
}
using System;
try
{
string txtnumber = "Mark";
int num = Int32.Parse(txtnumber);
Console.WriteLine(num);
}
catch (Exception)
{
Console.WriteLine("Something went wrong");
}
Al ejecutar esta pequeña aplicación de consola, la conversión falla y el
código del catch se ejecuta. Ahora sí vemos por consola el mensaje de que
algo salió mal.
Esto significa que el código que estaba dentro del try (que se supone era
el flujo principal de la aplicación) ha lanzado una excepción. Ni más ni
menos.
Es aquí donde aparece la ventaja de usar el método tryparse para realizar
este tipo de conversiones. Veámoslo con un ejemplo.
using System;
Console.WriteLine(number);
using System;
Console.WriteLine(number);
1234
using System;
Console.WriteLine(number);
using System;
True
Operadores
Los operadores son símbolos especiales que ejecutan ciertas acciones
sobre los operandos tal y como funcionaría en matemáticas. Por ejemplo, el
símbolo más (+) realiza la suma entre el número que está a la izquierda del
operador y el que está a la derecha.
C# ofrece varios operadores para distintos tipos de operaciones, algunos
de ellos incluso modifican levemente su funcionamiento según el tipo de dato
de los operandos:
int a = 10 + 10;
int b = a + 5;
string c = “Hola” + “Mundo”;
En este ejemplo, el mismo símbolo más (+) que primero suma los valores
de tipo entero, luego concatena las dos cadenas de texto en la línea que sigue,
es decir que con el mismo símbolo obtenemos resultados distintos según el
tipo de datos de los operandos.
Operadores Aritméticos
Operator Example
< x < y;
> x > y;
<= x <= y;
>= x >= y;
Operator Example
== x == y;
!= x != y;
Operadores Aritméticos
using System;
int a = 5;
int b = 5;
Console.WriteLine(a + b);
10
int a = 5;
int b = 5;
Console.WriteLine(a * b);
25
using System;
int a = 5;
int b = 5;
int c = a + b;
Console.WriteLine(c / b);
using System;
int a = 5;
int b = 5;
int c = a + b;
Console.WriteLine(a + b * c);
using System;
int a = 5;
int b = 5;
int c = a + b;
Console.WriteLine((a + b) * c);
Con los paréntesis hemos alterado el orden por lo que primero se sumarán
a y b, y luego el resultado será multiplicado por c.
El resultado final también cambia: ahora es 100.
100
using System;
int a = 5;
int b = 5;
int c = a + b;
False
using System;
int a = 5;
int b = 5;
int c = a + b;
Console.WriteLine(b == c);
Evidentemente no.
False
using System;
int a = 5;
int b = 5;
int c = a + b;
True
using System;
int a = 5;
int b = 5;
int c = a + b;
Las dos claves de sol (&) representan el operador lógico AND, y para que
éste operador lógico valide como verdadero, ambas expresiones, tanto la de la
izquierda como la de la derecha, deben ser verdaderas. El compilador
evaluará las dos expresiones y devolverá verdadero o falso según
corresponda.
False
Para obtener un resultado verdadero, tenemos que cambiar el comparador
porque sabemos que a es menor a c y que también b es menor a c.
using System;
int a = 5;
int b = 5;
int c = a + b;
True
using System;
int a = 5;
int b = 5;
int c = a + b;
using System;
int a = 5;
int b = 5;
int c = a + b;
True
Porque al operador OR sólo le basta con que una de las expresiones sea
verdadera para que el resultado sea verdadero. De hecho algo que es muy
importante saber es que si tenemos dos expresiones (como en este caso) y la
primera ya evalúa verdadero, el compilador no ejecutara la expresión que
sigue por que no lo necesita.
En otras palabras, lo que está a la derecha no será evaluado nunca si lo
que está a la izquierda es verdadero.
using System;
int a = 5;
int b = 5;
int c = a + b;
False
int a = 5;
int b = 5;
int c = a + b;
Console.WriteLine(c != b);
True
Cómo para darle una última vuelta de tuerca ya que sabemos que la
respuesta es verdadera, envolvemos la expresión entre paréntesis y la
negamos.
using System;
int a = 5;
int b = 5;
int c = a + b;
Console.WriteLine(!(c != b));
//int x = 10;
Eso significa que esta línea no será evaluada por el compilador en tiempo
de compilación por lo que puedes escribir lo que quieras ahí. Todo lo que
sigue a las dos barras no será evaluado por el compilador pero recuerda que
esto sólo aplica a esa única línea; en cuanto presiones enter y saltes de línea,
el código volverá a ser evaluado normalmente.
Al utilizar el comentario de una única línea, no necesariamente tienes que
comentar la línea completa, también puedes agregar un comentario a la mitad
de la línea, a continuación de tu código.
/*
int a = 10;
int b = 10;
*/
using System;
namespace ConsoleApp1
{
/// <summary>
///
/// </summary>
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World");
}
}
}
En este caso podemos ver que arriba de la clase aparecen varias líneas de
comentario y un tag XML que se llama Summary. Es decir que apareció un
comentario multilínea con un tag XML dentro del cual podemos agregar los
comentarios particulares que necesitemos.
using System;
namespace ConsoleApp1
{
/// <summary>
///
/// </summary>
class Program
{
/// <summary>
///
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
Console.WriteLine("Hello World");
}
}
}
class Person
{
private string name;
}
}
Console.WriteLine(person.ToString());
Fields/Campos
El elemento más simple que puede tener una clase es field o simplemente
campo en español.
Volvamos al ejemplo de la clase Person.
class Person
{
private string name;
}
En esta clase teníamos una variable llamada name de tipo string: esto es
lo que se conoce como field.
Para definirlo técnicamente diremos que en realidad un field es toda
variable que esté declarada dentro de una clase. Tan simple como eso.
Es decir que la diferencia principal entre una simple variable y un field es
el lugar en donde la variable está declarada y aunque a simple vista parezca
trivial, esta es una diferencia importante porque está relacionada con lo que
conocemos como el scope de una variable, que recién mencionamos casi al
pasar.
El scope de la variable establece desde donde puede ser accedida esa
variable, por lo que una variable local definida dentro de un método
cualquiera sólo podrá ser accedida y utilizada dentro de ese método
específico.
En cambio un field puede ser accedido desde cualquiera de los métodos
de la clase y hasta desde métodos de otras clases si la visibilidad lo permite,
cosa que en este caso no sucede porque tiene un modificador de acceso
privado (private). Si cambiamos el modificador de acceso de privado a
público (public) la variable sí que podría ser accedida desde cualquier lugar.
Sabiendo esto es importante aclarar que la forma recomendada de acceder
a los campos de una clase desde afuera de la clase es a través de propiedades
que veremos a continuación.
Por eso verán que, en general, los campos se declaran en las clases arriba
de todo y son de tipo privado.
En el ejemplo, la variable name no tiene valor inicial así que, para poder
usarla, habrá que asignarle un valor.
Además de usar propiedades, otra opción muy frecuente es la de utilizar
el constructor público de la clase para asignarle un valor al campo:
class Person
{
private string name;
class Person
{
private string name;
public string name
{
get {return name;}
set {name = value;}
}
}
Más allá del ejemplo clásico que vimos, los métodos de una propiedad
pueden ser tan complejos como se nos ocurra. Consideremos un caso en
donde el valor del campo no sea asignado y devuelto así sin más, sino que,
por el contrario, se han modificado justo antes de ser devuelto y justo antes
de ser asignado.
Las propiedades auto implementadas son tan sólo un atajo; una forma de
escribir menos código y obtener el mismo comportamiento.
Visibilidad
Como podemos ver, en la firma del método (que es la primera línea del
método) tenemos que definir una visibilidad, un tipo de datos de retorno (es
decir, qué va a devolver
ese método si es que devuelve algo), a continuación el nombre del
método y opcional, entre paréntesis y como argumentos una lista de
parámetros.
Luego entre las llaves va a estar el código del método en sí.
Aquí tenemos un método muy sencillo que recibe dos números enteros
por parámetros, los multiplica y devuelve el resultado. Si lo analizamos
palabra por palabra vamos a encontrar que el método tiene una visibilidad
pública, un tipo de retorno int (es decir que el método devolverá un valor
entero) y que también tiene un nombre que es multiplicar.
A continuación y entre paréntesis aparecen dos parámetros que son los
valores que el método recibirá, ambos de tipo int y separados por coma:
number1 y number2.
Por último y entre llaves está el código del método propiamente dicho
que en este caso no es más que la multiplicación de los valores recibidos por
parámetros.
La palabra clave return indica que esa es la última línea del método y que
justamente esa multiplicación será lo que el método devuelva.
Para invocar este método, simplemente usaremos su nombre y pasaremos por
parámetros los dos números que queremos multiplicar entre paréntesis:
Veamos un ejemplo:
public void DoSomething (int number1, int number2)
{
int number3 = number1 * number2;
Console.Writeline(number3);
}
multiplicar(2,5);
Multiplicar(2,5);
Multiplicar(2,5,8);
public person() {}
En una misma clase pueden convivir uno o más constructores y cada uno
de ellos hace referencia a distintas maneras de instanciar la misma clase.
Además, cada vez que se define una clase, esa clase contiene un
constructor sin parámetros, aún cuando éste NO se defina explícitamente.
public Person() {}
Siguiendo con este ejemplo, la clase Person con estos dos constructores
podría instanciarse así usando simplemente el constructor sin parámetros:
namespace MyOffice
{
class Person{}
class Employee{}
class Manager {}
}
using System.Web.Script.Serialization;
using System;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
using System;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
class Customer
{
class Customer
{
public string FirstName { get; set; }
}
Así queda nuestra clase Customer una vez que agregamos todas las
propiedades:
class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
class Customer
{
public string FirstName { get; set; }
public Customer() { }
}
De esta forma le permito a esta clase ser instanciada sin pasarle ningún
parámetro. Es decir que todas estas propiedades pueden estar vacías a la hora
de crear una instancia de la clase.
Igualmente vamos a agregar un constructor más y esta vez sí que tendrá
parámetros:
class Customer
{
public string FirstName { get; set; }
public Customer() { }
}
}
class Customer
{
public string FirstName { get; set; }
public Customer() { }
class Customer
{
public string FirstName { get; set; }
public Customer() { }
Hasta aquí tenemos una clase Customer con cuatro propiedades y tres
constructores, y hay tres maneras de instanciar esta clase.
class Customer
{
public string FirstName { get; set; }
public Customer() { }
Este método no recibe nada por parámetros pero sí que utiliza los valores
de las propiedades LastName y FirstName. Lo único que hace es
concatenarlos y agregar una coma en el medio de los dos.
Y sabemos que hay varias formas de instanciar la clase Customer así que
comencemos por la primera:
using System;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
Customer customer1 = new Customer();
customer1.FirstName = "Tony";
customer1.LastName = "Soprano";
}
}
}
using System;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
Customer customer1 = new Customer();
customer1.FirstName = "Tony";
customer1.LastName = "Soprano";
Console.WriteLine(customer1.GetFullName());
}
}
}
using System;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
Customer customer1 = new Customer();
customer1.FirstName = "Tony";
customer1.LastName = "Soprano";
//Console.WriteLine(customer1.GetFullName());
using System;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
Customer customer1 = new Customer();
customer1.FirstName = "Tony";
customer1.LastName = "Soprano";
//Console.WriteLine(customer1.GetFullName());
Todavía nos falta utilizar uno de los constructores así que vamos a crear
una nueva instancia de la clase Customer y para que las nuevas líneas de
código no se confundan con las anteriores, agregaremos un comentario
multilínea al código que teníamos hasta aquí.
Para este nuevo cliente usaremos la última de las opciones que nos
permite enviar dos cadenas de texto para nombre y apellido respectivamente.
using System;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
/*
Customer customer1 = new Customer();
customer1.FirstName = "Tony";
customer1.LastName = "Soprano";
Console.WriteLine(customer1.GetFullName());
Customer customer2 = new Customer("Malkovich");
customer2.FirstName = "John";
Console.WriteLine(customer2.GetFullName());
*/
}
}
}
Hasta aquí hemos aprendido a crear objetos de tres formas distintas (de
las infinitas que hay) y hemos obtenido siempre el mismo resultado.
Las clases son el elemento fundamental de la programación orientada a
objetos así que las veremos mucho más a lo largo de este libro.
Tipos de Datos no Primitivos
Static
Console.WriteLine(Calculations.GetSum(2,5));
Como puedes ver no es necesario instanciar la clase y simplemente
escribiendo el nombre de la clase (Calculations) seguido de un punto y el
nombre del método, ya podemos usarlo. Esta es la ventaja de los métodos y
las clases estáticas: no hace falta crear un objeto para poder usar sus métodos
y propiedades.
namespace Partials
{
partial class Car
{
public string SpeedUp()
{
return “Accelerate!”;
}
}
}
namespace Partials
{
partial class Car
{
public string SlowDown()
{
return “Not so Fast!”;
}
}
}
Esta es otra clase parcial con el mismo nombre, pero con un método
distinto.
Entonces tenemos dos clases diferentes con el mismo nombre y con la
palabra clave partial o, para decirlo de otra forma, tenemos dos partes
separadas de la misma clase en distintos archivos.
Usar la clase Car será exactamente igual a lo que estábamos
acostumbrados. De hecho, a la hora de instanciar la clase, para el compilador
sólo existe una clase y no hay ninguna diferencia visible entre clases parciales
y clases no parciales en tiempo de desarrollo:
namespace Partials
{
class Program
{
static void main(strings[] args)
{
Car car = new Car();
car.SpeedUp();
car.SlowDown();
}
}
}
enum Categories
{
Food,
Snacks,
Drinks,
};
Food
enum Categories
{
Food, //0
Snacks, //1
Drinks, //2
};
Para obtener este valor entero debemos hacer un cast del valor del
enumerador a un entero:
enum Categories
{
Food = 4,
Snacks = 7,
Drinks = 9
};
“C”
“Word”
“This is a phrase”
string c = “C”;
string w = “Word”;
El tamaño máximo que puede ocupar un string en memoria son 2GB, que
sería el equivalente a algo así como dos billones de caracteres, aunque en la
práctica dependerá de la capacidad técnica de la computadora que contiene el
proceso. En lecciones posteriores veremos cuál es la manera más eficiente de
trabajar con cadenas de texto tan grandes.
Hasta ahora dijimos que un string está delimitado por comillas dobles
pero ¿qué pasa si necesitamos que el valor de la cadena de texto también
incluya comillas dobles?
Como se puede ver, la segunda opción es más limpia y mucho más fácil
de leer.
Para poder aplicar esta técnica, hay que colocar justo delante de la cadena
de texto un carácter especial que es el símbolo del dinero ($). Seguido de este
carácter se coloca la cadena de texto entre comillas dobles.
De este modo las variables name y age están inyectadas dentro de la
cadena de texto, cada una de ellas encerrada entre llaves. Estas llaves le dicen
al compilador que lo que está adentro es una variable y que todo lo que está
en tres llaves debe ser reemplazado por el valor de la variable.
Pero el uso de estas llaves no se reduce al valor de una variable. A decir
verdad, las llaves pueden incluir expresiones mucho más complejas:
(10 + 5) * 2 = 30
Práctico
Casi desde el comienzo del libro estamos haciendo uso de strings sin
haberlos introducido propiamente, y es lógico porque un string no es más que
una variable que puede almacenar una cadena de texto. Lo interesante es
explorar algunas de las operaciones que podemos realizar con esas cadenas
de texto y algunos de los caracteres especiales de los que disponemos.
Console.WriteLine(address);
Así que agreguemos espacio entre las cadenas de texto para que la
dirección sea más legible:
Console.WriteLine(address);
Ahora esto ha concatenado cinco cadenas de texto: las tres variables más
los dos nuevos espacios.
Ahora que tenemos una variable de tipo string vacía, declaramos otra
variable pero esta vez de tipo bool y agregamos una condición que va a
evaluar si address es una cadena vacía o no:
if (address == string.Empty)
isEmpty = true;
else
isEmpty = false;
Console.WriteLine(isEmpty);
if (string.IsNullOrEmpty(address))
isEmpty = true;
else
isEmpty = false;
Console.WriteLine(isEmpty);
Console.WriteLine(string.IsNullOrEmpty(address));
El método IsNullOrEmpty además tiene la capacidad de evaluar si el
string es nulo:
Console.WriteLine(string.IsNullOrEmpty(address));
Console.WriteLine(string.IsNullOrWhiteSpace(address));
String Builder
Y puede ser inicializada sin pasar ningún valor por parámetros como en el
ejemplo, o también se puede usar uno de sus constructores predeterminados y
enviar el texto inicial de nuestra cadena:
Veamos un ejemplo:
sb.Append(“Hi there!”);
sb.AppendLine (“How are you doing?”);
Hi there!
How are you doing?
Hi John Wick
Hi
Hi John
DateTime
En C#, para trabajar con fechas y tiempos es necesario crear una instancia
de la clase DateTime utilizando la palabra clave new como haríamos con
cualquier otra clase.
El menor valor posible que una variable DateTime puede tener es la del
primero de enero del año 1 a la medianoche:
Es importante tener presentes estos valores por defecto porque serán muy
útiles a la hora de programar en C#.
DateTime, como muchas otras clases o estructuras, ofrece varios
constructores que podemos elegir según lo que necesitemos para inicializar
nuestra fecha.
En principio puede usarse con un constructor sin parámetros, en cuyo
caso tendrá por defecto el valor mínimo posible de los DateTime, pero
también podemos asignar año, mes y día a nuestra fecha para inicializarla
directamente en el día que necesitamos.
19/08/2020 1:20:55 am
TimeSpan ts = dt2.Subtract(dt1);
262.00:00:00
Algunas lecciones atrás dijimos que cada tipo de datos reescribe los
operadores a su manera, es decir que no es lo mismo aplicar el operador de
suma a dos valores enteros que a dos cadenas de texto.
De la misma manera, el tipo de datos DateTime sobreescribe a los
operadores para que funcionen de una manera que tenga sentido para fechas.
Veamos algunos ejemplos de esto.
Console.WriteLine(dt2+ts);
Obtendremos esto:
17/05/2021 10:26:10 am
Console.WriteLine(dt2-dt);
El resultado será:
262.05:10:20
También podemos igualar fechas o evaluar si dos fechas son iguales o no.
Console.WriteLine(dt2==dt);
Aquí el resultado será un valor booleano, en este caso falso porque las
fechas obviamente no son iguales.
Console.WriteLine(dt2!=dt);
Por si todo esto fuera poco, DateTime nos ofrece una serie de métodos
para convertir la fecha a una cadena de texto utilizando diversos formatos
según la información que necesitemos mostrar. Esto es muy útil para mostrar
la fecha por pantalla al usuario.
int i = 100;
var x = 100;
var y = i + 20;
La diferencia principal entre tipos de datos por valor y tipos de datos por
referencia aparece a la hora de pasar valores por parámetro. Cuando pasamos
una variable de tipo de datos por valor de un método a otro, el sistema crea
una copia de la variable en el segundo método. Esto significa que si el valor
de la variable cambia dentro de uno de los métodos esto no afecta el valor de
la variable en el otro método.
Veamos un ejemplo:
10
20
10
string s = “Hi”;
Alan
if (condition)
{
//code to run when condition == true
}
Consideremos dos variables enteras con dos valores por defecto y una
sentencia IF:
if (x < y)
{
Console.WriteLine(“x is lesser than y”);
}
En el ejemplo, la condición dice que x tiene que ser menor que y para que
el código del IF se ejecute. Evidentemente cuando la condición sea evaluada,
su resultado será verdadero y por lo tanto el código que está dentro del
bloque IF será ejecutado.
if (x < y)
Console.WriteLine(“x is lesser than y”)
if (x > y)
Console.WriteLine(“x is greater than y”)
int x = 100;
Ahora agregamos una sentencia if, aunque esta vez la condición será
levemente diferente:
if (x > 200)
Console.WriteLine(“x is greater than 200”);
if (x > 200)
Console.WriteLine(“x is greater than 200”);
else if (x > 100)
Console.WriteLine(“x is greater than 100”);
else
Console.WriteLine(“x is lesser than 100”);
Esto nos permite crear una cadena de condiciones para cuando los
resultados de las evaluaciones son sucesivamente falsos. No hay un límite en
cuanto a la cantidad de else if que podemos tener dentro de un condicional if,
aunque sí es importante decir que si tenemos muchos es probable que exista
otra estructura de control que se ajuste mejor a lo que necesitamos.
Operador Ternario
El operador ternario comienza con una condición booleana tal cual sería
en un IF. Si la evaluación de esta condición es verdadera, entonces se
ejecutará el primer statement que sigue al signo de preguntas.
Si en cambio la evaluación de la condición es falsa, entonces se ejecutará
el segundo statement que está después de los dos puntos.
int x = 10, y = 5;
string z = x > y ? “greater x” : “greater y”;
int x = 10, y = 5;
string z = x > y
? “x is greater than y”
: x < y ? “y is greater than x”
: x == y ? “x is equal to y”
: “How did you get here”;
Console.WriteLine(IsJohn);
Console.WriteLine(IsJohn);
True
if(IsJohn)
Console.WriteLine("IsJohn es verdadero");
else
Console.WriteLine("IsJohn es falso");
IsJohn es verdadero
if(!IsJohn)
Console.WriteLine("IsJohn es verdadero");
else
Console.WriteLine("IsJohn es falso");
IsJohn es verdadero
Vamos a olvidarnos de las cadenas de texto por ahora y vamos a crear dos
variables enteras. El bloque de código contenido en el if puede contener
tantas líneas como sea necesario, igual que el else en donde podríamos
incluso anidar estructuras if si quisiéramos:
int x = 15;
int y = 30;
if (x > y)
{
Console.WriteLine("x es mayor a y");
Console.WriteLine("ademas podemos tener mas de una linea");
Console.WriteLine("y mas de dos");
}
else
{
Console.WriteLine("x es menor a y");
Console.WriteLine("ademas podemos tener mas de una linea");
Console.WriteLine("y mas de dos");
}
int x = 15;
int y = 30;
Cuando tenemos que evaluar una variable con dos o más condiciones, en
lugar de usar un gran if con muchos else, podemos utilizar la sentencia
switch.
int x = 10;
switch (x)
{
case 0:
Console.WriteLine(“x is 0”);
break;
case 10:
Console.WriteLine(“x is 10”);
break;
default:
Console.WriteLine(“x is not 0 nor 10”);
break;
}
Esta sentencia comienza con la palabra clave switch y puede contener una
variable (como en este caso) o también una expresión, siempre que el tipo de
datos resultado de la expresión sea char, string, bool, int o enum.
int x = 10;
switch (x)
{
case 0:
case 1:
Console.WriteLine(“x is 0 or 1 “);
break;
case 10:
case 11:
Console.WriteLine(“x is 10 or 11 “);
break;
default:
Console.WriteLine(“x is not in 0,1,10,11“);
break;
}
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Do you like C# so far? (yes / no /maybe)");
string input = Console.ReadLine();
switch (input.ToLower())
{
case "yes":
Console.WriteLine("That is great");
break;
default:
break;
}
}
}
}
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Do you like C# so far? (yes / no /maybe)");
string input = Console.ReadLine();
switch (input.ToLower())
{
case "yes":
Console.WriteLine("That is great");
break;
case "maybe":
Console.WriteLine("It'll get better");
break;
case "no":
Console.WriteLine("That is not so good");
break;
default:
break;
}
}
}
}
Con este agregado he cubierto las tres alternativas que le estoy dando al
usuario como posibilidades para que responda. Ahora bien, si el usuario
responde cualquier cosa que no sea ninguna de estas tres opciones, entonces
se va a ejecutar el default.
Si bien el default es opcional, en este caso me interesa que exista así que
voy a agregar algo más significativo.
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Do you like C# so far? (yes / no /maybe)");
string input = Console.ReadLine();
switch (input.ToLower())
{
case "yes":
Console.WriteLine("That is great");
break;
case "maybe":
Console.WriteLine("It'll get better");
break;
case "no":
Console.WriteLine("That is not so good");
break;
default:
Console.WriteLine("Wasn't an option");
break;
}
}
}
}
Esta que vimos es la forma más clásica del switch. Ahora voy a retocar un
poco el código de arriba para usar el switch exclusivamente con enteros:
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
int x = 10;
switch (x)
{
case 5:
Console.WriteLine("Value of x Is 5");
break;
case 10:
Console.WriteLine("Value of x Is 10");
break;
case 15:
Console.WriteLine("Value of x Is 15");
break;
default:
Console.WriteLine("Unknown value");
break;
}
}
}
}
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
int x = 10;
switch (x + 5)
{
case 5:
Console.WriteLine("Value of x Is 5");
break;
case 10:
Console.WriteLine("Value of x Is 10");
break;
case 15:
Console.WriteLine("Value of x Is 15");
break;
default:
Console.WriteLine("Unknown value");
break;
}
}
}
}
using System;
namespace ConsoleApp9
{
class Program
{
static void Main(string[] args)
{
int x = 10;
switch (x + 5)
{
case 1:
case 2:
case 3:
case 4:
case 5:
Console.WriteLine("Value of x Is < 5");
break;
case 10:
Console.WriteLine("Value of x Is 10");
break;
case 15:
Console.WriteLine("Value of x Is 15");
break;
default:
Console.WriteLine("Unknown value");
break;
}
}
}
}
using System;
namespace ConsoleApp9
{
class Program
{
static void Main(string[] args)
{
int x = 10;
switch (x + 5)
{
case 1:
case 2:
case 3:
case 4:
case 5:
Console.WriteLine("Value of x Is < 5");
break;
case 6:
case 7:
case 8:
case 9:
case 10:
Console.WriteLine("Value of x Is <= 10, but > 5");
break;
case 11:
case 12:
case 13:
case 14:
case 15:
Console.WriteLine("Value of x Is <= 15, but > 10");
break;
default:
Console.WriteLine("Unknown value");
break;
}
}
}
}
Value of i is: 0
Value of i is: 1
Value of i is: 2
Value of i is: 3
Value of i is: 4
Value of i is: 5
Value of i is: 6
Value of i is: 7
Value of i is: 8
Value of i is: 9
Value of i is: 10
Value of i is: 11
Value of i is: 12
Value of i is: 13
Value of i is: 14
Value of i is: 15
Value of i is: 16
Value of i is: 17
Value of i is: 18
Value of i is: 19
Value of i is: 10
Value of i is: 9
Value of i is: 8
Value of i is: 7
Value of i is: 6
Value of i is: 5
Value of i is: 4
Value of i is: 3
Value of i is: 2
Value of i is: 1
i: 0, j: 0
i: 0, j: 1
i: 0, j: 2
i: 0, j: 3
i: 0, j: 4
i: 1, j: 1
i: 1, j: 2
i: 1, j: 3
i: 1, j: 4
i: 2, j: 2
i: 2, j: 3
i: 2, j: 4
i: 3, j: 3
i: 3, j: 4
i: 4, j: 4
Como puedes ver, analizar el resultado es más fácil que explicarlo: por
cada valor de la variable i se ejecuta un bucle que que recorre los valores de
la variable j y por cada una de estas iteraciones imprime el valor de ambas
variables por consola
Las buenas prácticas dicen que siempre que podamos evitar los loops
anidados debemos hacerlo; aunque también es cierto que hay circunstancias
en las que esto no es posible. Para estos casos la herramienta existe y
podemos utilizarla sin problemas.
Más de dos loops anidados seguramente significa que podemos encontrar
una solución mejor.
While
while (condition)
{
//code block
}
El loop while comienza con la palabra clave while y debe incluir una
expresión condicional booleana que devuelva verdadero o falso. El loop
ejecutará el bloque de código hasta que la expresión condicional sea falsa.
int i = 0;
while (true)
{
//Code to be executed
}
int i = 0; //initialization
int i = 0; //initialization
Aquí está claro que, una vez que el valor de la variable sea 8, el bucle
terminará ya que aparece la palabra clave break.
Con la estructura while hay que tener cuidado porque, a diferencia quizás
de otras estructuras, puede convertirse con cierta facilidad en un loop infinito,
es decir, en un loop que no se termina nunca. Esto obviamente es peligroso
en una aplicación porque puede tener un costo muy alto en cuanto a la
performance y en cuanto a que obviamente en algún momento se va a
consumir toda la memoria de la aplicación.
El ejemplo más conocido de esto es el famoso while true:
while (true)
{
Console.WriteLine($"Infinite loop");
}
Pero también puede ocurrir que un loop infinito como éste, suceda de
manera involuntaria:
int i = 0;
while (i >= 0)
{
Console.WriteLine($"i = {i}");
i++;
}
int i = 0;
int j = 1;
while (i < 5)
{
Console.WriteLine($"i = {i}");
i++;
while (j < 5)
{
Console.WriteLine($"j = {j}");
j++;
}
}
i=0
j=1
j=2
j=3
j=4
i=1
i=2
i=3
i=4
Los valores de i van de cero hasta a cuatro. Para el primer loop se ejecuta
el segundo while en donde j es menor a cinco, y luego, como j ya tiene el
valor 5, no se vuelve a ejecutar por lo que no aparecen más resultados y sólo
vemos impresiones de i.
do
{
//code block
}
while (condition)
int i = 0;
do
{
Console.WriteLine($”Value: {i}”);
i++;
}
while (i < 15);
Try / Catch
try
{
//Block of code to try
}
catch (condition)
{
//Block of code to handle errors
}
finally
{
//Block of code that always run
}
Aquí tenemos un string con una cadena de texto como cualquier otra y a
continuación una variable de tipo int y un intento por convertir ese string en
un int.
El ejemplo es bastante inocente porque evidentemente esto va a dar error:
no se puede convertir esa cadena de texto en un número entero.
La manera de capturar el posible error que el código devolverá es
envolverla con una estructura try catch:
try
{
string name = “Mark”;
int number = int.parse(name);
}
catch (Exception e)
{
Console.Writeline(“Cannot parse that”);
}
System.IO.IOEException
System.OutOfRangeException
System.NullReferenceException
System.DivideByZeroException
Console.WriteLine("Enter a number");
var input = int.Parse(Console.ReadLine());
Console.WriteLine($"The number is {input}, {input * 2}");
try
{
Console.WriteLine("Enter a number");
var input = int.Parse(Console.ReadLine());
Console.WriteLine($"The number is {input}, {input * 2}");
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
finally
{
Console.WriteLine("Please retry...");
}
Enter a number
asd
Error: Input string was not in a correct format.
Please retry...
La tercera línea del bloque de código que está en el cuerpo del try nunca
se ejecutará, ya que la segunda línea dispara el error y la secuencia del
programa salta directamente al cuerpo del catch. Luego de ejecutar el catch,
la ejecución se mueve hacia el bloque finally.
Como se puede ver, usamos el nombre del array seguido de los corchetes
y dentro de los corchetes escribimos el índice del elemento que vamos a
agregar, con esto indicamos en qué posición queremos agregar el elemento.
Recuerden que en C# el índice de todas las colecciones empiezan en cero, por
lo que el texto “Quentin Tarantino” estará en la primera ubicación del array.
Pero no es necesario seguir ningún orden a la hora de agregar elementos,
así que bien podríamos tener otra asignación como ésta, en un índice menor:
Este tipo de expresiones es muy común ya que uno de los procesos más
comunes es el de iterar colecciones.
Algo interesante a notar en este ejemplo es que dentro del for tenemos
una variable entera i que comienza en cero y que luego, en la condición del
for, se determina la longitud del array utilizando la propiedad length de la
clase array. Lenght en español significa longitud.
Sabiendo esto, la condición se lee como sigue: “mientras i sea menor a
length, entonces el bloque de código se ejecutará”.
El bloque de código luego ejemplifica cómo acceder a los valores de los
elementos del array que es igual a la forma en la cual se asignan los
elementos del array: utilizando el nombre del array y colocando entre
corchetes el índice que queremos conocer en cada uno de los pasos de la
iteración.
En el ejemplo, el valor de un elemento del array se imprimirá por consola.
Práctico
numbers[0] = 2;
numbers[1] = 4;
numbers[4] = 8;
Para ver los valores del array también podemos utilizar sus índices:
Console.WriteLine(numbers[1]);
Lo que importa saber aquí es que solamente podemos utilizar índices que
existen. Si yo quisiera acceder al valor del array en su índice 10, cuando
hemos declarado este array como una colección de 5 elementos, entonces
obtendré la excepción IndexOutOfRange que es otra forma de decir que el
índice al que estamos intentando acceder está fuera del rango definido por el
array que claramente va de cero a cuatro.
Esto es algo que hay que tener en cuenta cuando trabajamos con arrays,
porque es bastante fácil equivocarse dado que este tipo de colecciones suele
ser dinámica y no siempre sabemos que el elemento al que estamos
intentando ingresar es válido.
Como ya vimos, todos los arrays tienen una propiedad que devuelve la
cantidad de elementos que hay en la colección. Esta es la propiedad Length y
suele usarse en la condición de las estructuras del control para poder recorrer
todos los elementos de la colección.
De este modo, el for del ejemplo va a iterar hasta que i (que comienza en
cero y tiene un incremento de uno en cada una de las iteraciones), sea menor
a numbers.Length o la cantidad total de elementos del array.
En cada iteración se imprime el valor del array en esa posición utilizando
la variable i para acceder a la posición dinámica del array. Empezará en cero
y terminará en el último valor del array.
2
4
6
8
10
Así de fácil será también modificar valores dentro del array. Por ejemplo,
consideremos estos dos bucles:
12
14
16
18
20
El foreach suele ser bastante más sencillo que el for en su sintaxis y nos
permite obtener el mismo resultado con menos código: simplemente recorrerá
todos los elementos que encuentre en la colección.
Listas
List<T>
System.Collection.Generic;
Para agregar valores a una lista, podemos utilizar su método Add, que es
un método que heredan todos los tipos de colecciones genéricas. Hay que
recordar que sólo podemos agregar en la lista valores del tipo de datos que
definimos para la lista.
numbers.Add(1);
numbers.Add(2);
numbers.Add(10);
cities.Add("Lima");
cities.Add("Valencia");
cities.Add("Buenos Aires");
En el ejemplo hay tres listas distintas: una con valores de tipo entero y
dos con cadenas de texto, cada una inicializada de una manera diferente.
La primera es la que ya habíamos estudiado, pero la segunda demuestra
que podemos declarar e inicializar una lista de tipo implícito, en donde el
valor de la lista es inferido según el tipo de datos de sus valores, que están a
derecha del signo igual (=).
La última lista demuestra que otra forma de inicializar y agregar valores a
una lista es utilizando el inicializador de colecciones, en donde puedo escribir
los valores directamente entre llaves a la hora de instanciar la lista.
La lista también puede contener clases que pertenezcan a nuestro modelo
personalizado y no sólo contener tipos de datos primitivos:
using System;
using System.Collections.Generic;
namespace ConsoleApp8
{
class Program
{
static void Main(string[] args)
{
var customers = new List<Customer>();
customers.Add(new Customer() { ID = 1, Name = "John" });
customers.Add(new Customer() { ID = 2, Name = "Mark" });
}
Console.WriteLine(numbers[4]);
Dictionary<TKey, TValue>
numberNames.Add(1, "One");
numberNames.Add(2, "Two");
numberNames.Add(3, "three");
numberNames.Add(1, "One");
numberNames.Add(2, "Two");
numberNames.Add(3, "three");
Este tipo de datos nos ofrece las propiedades Key y Value para que
podamos obtener sin problema la clave y el valor del elemento de la
colección. Si no recuerdas el nombre del objeto tampoco importa mucho
porque recuerda que utilizando el tipo de dato implícito con var, el
compilador ya sabrá que el tipo de datos que necesitamos es KeyValuePair
porque será inferido a partir de la colección.
if (countries.ContainsKey("MEX"))
Console.WriteLine(countries["MEX"]);
countries["MEX"] = "Colombia";
if (countries.ContainsKey("MEX"))
Console.WriteLine(countries["MEX"]);
Colombia
countries.Remove("PER");
countries.clear();
char[] anyWord = { 'c', 'o', 'd', 'e', 'r', ' ', 'c', 'a', 'v', 'e' };
foreach (var item in anyWord)
{
Console.WriteLine(item);
}
En la definición del foreach podemos ver como nos permite usar sin
problemas un tipo implícito para la variable local, lo cual nos da gran
versatilidad porque no tenemos que especificar exactamente cuál es el tipo de
datos cuando a veces, en porciones de código muy complejas, quizás ni
siquiera lo sabemos en el primer vistazo.
c
o
d
e
r
c
a
v
e
Por cada uno de los elementos se ha ejecutado este bloque de código que
no hace otra cosa que imprimir en pantalla la cadena de texto.
Pero el foreach no sólo sirve para leer valores, sino que también podemos
modificar valores dentro de colecciones:
88
Lo que esperamos que suceda aquí es que por cada uno de estos números
la suma crezca y vaya acumulando los valores, y eso es exactamente lo que
sucede.
using System;
using System.Collections.Generic;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
List<Customer> customers = new List<Customer>();
customers.Add(new Customer { Name = "John Wick" });
customers.Add(new Customer { Name = "John Travolta" });
customers.Add(new Customer { Name = "Tony Soprano" });
class Person
{
private string name;
Pero no sólo eso, sino que además la clase B puede también contener sus
propios métodos, en este caso, una propiedad y un método.
using System;
namespace ConsoleApp10
{
class Program
{
static void Main(string[] args)
{
Animal animal = new Animal();
animal.Saludar();
Perro perro = new Perro();
perro.Saludar();
}
}
}
}
Hello I am an animal
Hello I am an animal
using System;
namespace ConsoleApp10
{
class Program
{
static void Main(string[] args)
{
Animal animal = new Animal();
animal.Saludar();
Perro perro = new Perro();
perro.Saludar();
Gato gato = new Gato();
gato.Saludar();
}
}
}
}
Hello I am an animal
Hello I am a dog
Hello I am an animal
using System;
namespace ConsoleApp10
{
class Program
{
static void Main(string[] args)
{
Animal animal = new Animal();
animal.Saludar();
Perro perro = new Perro();
perro.Saludar();
Gato gato = new Gato();
gato.Saludar();
}
}
public class Animal
{
public virtual void Saludar()
{
Console.WriteLine("Hello I am an animal");
}
}
public class Perro : Animal
{
public override void Saludar()
{
Console.WriteLine("Hello I am a dog");
}
}
public class Gato : Animal
{
public override void Saludar()
{
Console.WriteLine("Hello I am a cat");
}
}
}
Y el resultado será:
Hello I am an animal
Hello I am a dog
Hello I am a cat
using System;
namespace ConsoleApp10
{
class Program
{
static void Main(string[] args)
{
Animal animal = new Animal();
animal.Saludar();
Perro perro = new Perro();
perro.Saludar();
Gato gato = new Gato();
gato.Saludar();
}
}
public abstract class Animal
{
public virtual void Saludar()
{
Console.WriteLine("Hello I am an animal");
}
}
public class Perro : Animal
{
public override void Saludar()
{
Console.WriteLine("Hello I am a dog");
}
}
public class Gato : Animal
{
public override void Saludar()
{
Console.WriteLine("Hello I am a cat");
}
}
}
Supongamos que tenemos una clase abstracta Shape que tiene dos
propiedades: Height y Width y además un método CalculateArea().
A la clase es abstracta no le importa cómo este método es implementado
porque nunca habrá una instancia de Shape, pero sí importa que cualquier
clase que herede de Shape implemente de alguna manera el método
CalculateArea.
using System;
using System.Collections.Generic;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var shapes = new List<Shape>();
var rectangle = new Rectangle() { Width = 20, Height = 10 };
var triangle = new Triangle() { Width = 10, Height = 15 };
var square = new Square() { Width = 10 };
shapes.Add(rectangle);
shapes.Add(triangle);
shapes.Add(square);
}
}
}
public class Square : Shape
{
public override double CalculateArea()
{
return Math.Pow(Width, 2);
}
}
public class Rectangle : Shape
{
public override double CalculateArea()
{
return Width * Height;
}
}
public class Triangle : Shape
{
public override double CalculateArea()
{
return (Width * Height) / 2;
}
}
}
200
75
100
Interfaces
Algunas lecciones atrás aprendimos qué cosa eran las clases abstractas.
Lo interesante de esto es que las interfaces son en realidad bastante parecidas
a las clases abstractas, especialmente porque tampoco pueden ser
instanciadas.
Sin embargo, las interfaces son todavía un poco más conceptuales que las
clases abstractas, porque sólo aceptan la firma de los métodos pero no sus
implementaciones. Como no hay implementación de métodos, tampoco habrá
propiedades ni tendría mucho sentido agregar campos, por lo que se podría
decir que una interfaz es esencialmente una clase abstracta que sólo tiene
métodos abstractos.
En general, se dice que una interfaz es un contrato porque toda clase que
implemente la interfaz está obligada a implementar todos sus métodos.
Entonces las interfaces, al igual que las clases abstractas, no pueden ser
instanciadas, sus métodos no tienen cuerpo, y el cuerpo o la implementación
del método estarán siempre en la clase que implementa la interfaz. Cuando
una interfaz es implementada, la clase que la implementa debe sobreescribir y
agregar cuerpo a todos sus métodos, sin excepción.
Las interfaces pueden contener propiedades y métodos, pero no campos o
variables.
Además, por defecto, todos sus métodos son public y abstract, aun
cuando no lo especificamos explícitamente.
Práctico
Para ver este ejemplo de interfaces vamos a hacer algo muy, muy
parecido a lo que hicimos con herencia y con clases abstractas:
class Program
{
static void Main(string[] args)
{
int increment = 10;
int decrement = 5;
Bicycle bicycle = new Bicycle();
bicycle.applyBreaks(decrement);
bicycle.SpeedUp(increment);
bicycle.Print();
car.Print();
}
}
Analicemos el resultado:
Speed. 5
Speed: 25
Hasta ahora hemos aprendido que los objetos son instancias de una clase.
Una clase se declara con un cierto número de campos, propiedades y
métodos, y luego podremos crear una instancia de esa clase en la forma de
objeto. Sin embargo, con la introducción de tipos anónimos, ahora también
podemos crear un objeto sin tener la necesidad de declarar una clase.
Si bien esto suena a que las clases ya no son necesarias, en la práctica, no
hay nada más lejos de la verdad. Los tipos anónimos tienen muchas
limitaciones y están pensados para ser usados en situaciones en donde
necesitamos crear un objeto rápido y con pocos elementos.
Console.WriteLine(customer.Name + “: ” + customer.Age);
Aquí hay un array que se llama contacts y dentro de ese array tenemos
tres objetos de tipo anónimo.
Los tipos anónimos son útiles para muchas situaciones, pero en especial
para cuando se necesita devolver un conjunto de valores con una complejidad
mayor que la de simplemente un string o un número, rápidamente.
Los tipos anónimos también permiten crear propiedades dinámicamente
sin la necesidad de declarar una clase primero.
En todos los conceptos que hemos aprendido hasta aquí en este libro
siempre hemos dicho que el compilador de C#, en tiempo de compilación,
puede detectar determinados errores cuando trabajamos con tipos de datos
además de validar sintaxis, entre muchas otras cosas.
Los tipos dinámicos básicamente evitan que el compilador valide el tipo
de dato de las variables dinámicas en tiempo de compilación. En su lugar, la
validación ocurrirá sólo en tiempo de ejecución.
Esto significa que no importa qué cosa hagamos con la variable de tipo
dinámico, la aplicación siempre compilará, aunque más tarde en tiempo de
ejecución la aplicación arrojará un error si hay alguna inconsistencia.
dynamic x = 100;
Console.WriteLine(x.GetType());
El resultado sería:
System.int32
dynamic x = 100;
x = “Hi there”;
x = true;
x = DateTime.Today;
Veamos un ejemplo:
}
}
con.GetPhone();
if(car == null)
Por otro lado, cuando trabajamos con números, notaremos que los
números siempre tienen un valor por defecto. Para un int o un long este valor
por defecto es cero (0), que no debe ser confundido con null porque no es lo
mismo. Null representa la ausencia de valor, mientras que 0 es un valor tan
válido como cualquier otro. Entonces, si alguna vez necesitas tener un entero
con un valor nulo, no podrás hacerlo de manera directa por qué no se puede
asignar null a una variable de tipo int.
if(numberNullable == null)
if(! numberNullable.HasValue)
Por último, para devolver el valor de la variable (si lo hubiere), los tipos
de datos Nullables también heredan la propiedad Value que se usa justamente
para esto:
Console.WriteLine(numberNullable.Value);
Práctico
Nullable<int> x = null;
Nullable<int> x = null;
Console.WriteLine(x.GetValueOrDefault());
Console.WriteLine(x.GetValueOrDefault());
int? x = null;
x = 10;
Console.WriteLine(x.GetValueOrDefault());
10