Tutorial C#
Tutorial C#
En cada lenguaje de programacion uno de los comandos mas importantes es el if. Tener la
habilidad de establecer bloques condicionales de código en uno de los principios fundamentales
de desarrollar software. En C#, el uso del comando if es muy simple. Si tienes experiencia en otro
lenguaje de programacion, es probable que puedas utilizar el comando if de inmediato. En
cualquier otro caso, lee para entender como utilizarlo. El comando if requiere de un resultado del
tipo Boolean, es decir, true (Verdadero) o false (Falso). En algunos lenguajes de programacion,
muchos tipos de datos pueden ser transformados automaticamente en Booleans, sin embargo en
C# necesita hacerlo especificamente. De hecho, no se puede utilizar if(numero), debe comparer el
numero con algo para generar un resultado true (Verdadero) o false (Falso), tal como veremos mas
adelante.
En el capitulo anterior le dimos un vistazo a las variables, ahora queremos ver uno de los ejemplos
de como las condiciones lógicas pueden ser usadas
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int number;
Console.ReadLine();
}
}
}
Utilizaremos dos comandos if para validar que un numero ingresado este en el rango entre 0 y 10,
introduciremos la clausula else. La clausula else debe ser obvia para cualquiera que hable ingles.
Simplemente ofrece una alternativa para decidir que codigo debe ser ejecutado en caso el
condicion evaluada en el comando if no se cumpla.
Tal como se observa, no utilizamos las llaves { } para definir el bloque condicional del codigo. La
regla indica que si el bloque contiene una sola linea de codigo entonces las llaves { } no son
necesarias. Ahora bien, el ejemplo anterior parece que está usando demasiadas lineas de codigo
para comparar simplemente un número, ¿verdad? Definitivamente, se puede implementar con
menos líneas como lo siguiente:
Colocamos cada condición encerrada entre paréntesis, luego usamos el operador || , el cual
significa "o", para validar si el numero es mayor que 10 "O" menor que cero. Otro operador muy
frecuentemente utilizado es AND (y) representado por &&. podríamos haber utilizado el operador
AND ? Por supuesto que si, se necesitaría un simple ajuste en el código tal como lo siguiente:
El código de arriba nos presenta dos nuevos operadores: <= (menor que o igual que) y >= (mayor
que o igual que).
La declaración "switch" es como una serie de declaraciones "if". Es una lista de posibilidades, con
una acción para cada posibilidad, y una acción opcional por defecto, en caso de que nada se
evalúe como verdadero. Una simple declaración "switch" se ve de la siguiente forma:
int number = 1;
switch(number)
{
case 0:
Console.WriteLine("The number is zero!");
break;
case 1:
Console.WriteLine("The number is one!");
break;
}
El identificador por evaluar es colocado después de la palabra clave "switch", y luego está la lista
de los casos declarados, en donde evaluaremos el identificador contra los valores dados. Te darás
cuenta de que tenemos una declaración "break" al final de cada caso. C# simplemente requiere
que dejemos el bloque antes de que este termine. En caso de que estuvieses escribiendo Una
función, pudieses usar una declaración "return" en lugar de una declaración "break".
En este caso usamos un "integer", pero pudiese haberse usado también un "string", o cualquier
otro tipo. También puedes especificar la misma acción para múltiples casos. Lo haremos en el
próximo ejemplo también, donde tomaremos una entrada (input) del usuario y la usaremos en
nuestra declaración "switch":
En este ejemplo, le hacemos una pregunta al usuario y le sugerimos que introduzca un si, no, o
puede ser (yes/no/maybe). Entonces leemos la entrada del usuario, y creamos una declaración
"switch" para ella. Para ayudar al usuario, convertimos la entrada a minúsculas antes de compararla
con nuestras cadenas de texto (strings) en minusculas, de manera de que no haya diferencia entre
letras minúsculas y mayúsculas.
De todas maneras, el usuario puede cometer un error ortográfico o tratar de escribir algo
completamente diferente; y en ese caso, no se generaría ninguna salida (output) por esta
declaración "switch". Introduce la palabra clave "default"
Si ninguno de los casos da como resultado verdadero, entonces la declaración "default"; en caso
de haberla, será ejecutada. Esto es opcional, como pudimos ver en los ejemplos previos.
Loops
Otra técnica esencial al momento de escribir código es el Looping o bucle (la habilidad de repetir
un bloque de código X veces). En C#, estos vienen 4 variantes distintas, y veremos a cada una de
estas.
El ciclo while es probablemente uno de los más simples, así que empezaremos con este. Este ciclo
simplemente ejecuta un bloque de código durante el tiempo que la condición descrita se cumpla.
A continuación un pequeño ejemplo y después una explicación:
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int number = 0;
while(number < 5)
{
Console.WriteLine(number);
number = number + 1;
}
Console.ReadLine();
}
}
}
Intenta correr el código. Obtendrás una lista de números del 0 al 4. El número es definido primero
como 0 y cada vez que el código en el Loop es ejecutado este se incrementará por uno. Pero ¿Por
qué el número solo llega a 4 cuando en el código dice 5? Pues, para que la condición se cumpla y
regrese el valor, el número debe ser menor a 5. En este caso, una vez que el número es igual a 5, la
línea de código que regresa o entrega el valor del número nunca es ejecutada, debido a que la
condición del Loop While es evaluada antes de entrar a ejecutar el bloque de código.
El ciclo do
Lo contrario sucede con el ciclo do, que opera parecido al ciclo while en algunos aspectos. Este
ciclo evalúa la condición después de que el bloque de código ha sido ejecutado, lo que asegura
que el bloque sea ejecutado al menos una vez.
int number = 0;
do
{
Console.WriteLine(number);
number = number + 1;
} while(number < 5);
El resultado sería el mismo - una vez que el número sea mayor a 5, el ciclo es abandonado.
El ciclo for
El ciclo for es un poco diferente. Es preferido cuando tu sabes cuántas iteraciones quieres, porque
sabes la cantidad exacta de iteraciones, o porque tienes una variable conteniendo la cantidad. Aquí
hay un ejemplo de un ciclo for.
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int number = 5;
Console.ReadLine();
}
}
}
Esto produce el mismo resultado, pero como puedes ver, el ciclo for es un poco más compacto.
Consiste de 3 partes - inicializamos una varaible para contar, creamos una sentencia condicional
para probarlo, e incrementamos el contador (++ significa lo mismo que "variable = variable + 1").
La primera parte, en donde definimos la variable i y la iniciamos en 0, se ejecuta una sola vez, antes
de que el ciclo empiece. Las últimas dos partes son ejecutadas en cada iteración del ciclo. Cada
vez, i es comparada con nuestra variable 'number' - si i es más pequeña que 'number', el ciclo se
ejecuta una vez más. Después de eso, i es incrementada en 1.
Intenta correr el programa y después, intenta cambiando la variable 'number' a algo más grande o
más pequeño que 5. Vas a ver que el ciclo responde al cambio.
El ciclo foreach
El último ciclo que veremos, es el ciclo foreach. Opera en colecciones, como arreglos u otro tipo de
listas integradas. En nuestro ejemplo, vamos a usar una de las listas más simples, llamada ArrayList.
Funciona casi igual que un arreglo, pero no te preocupes, lo veremos en otro capítulo.
using System;
using System.Collections;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
ArrayList list = new ArrayList();
list.Add("John Doe");
list.Add("Jane Doe");
list.Add("Someone Else");
Console.ReadLine();
}
}
}
Bien, creamos una instancia de un ArrayList y luego le añadimos algunos elementos de tipo
cadena. Usamos el ciclo foreach para ir por cada elemento, asignando la variable 'name' al
elemento en el que estamos en cada ocasión. De esa manera, tenemos una variable nombrada
para dar salida. Como puedes ver, declaramos la variable 'name' de tipo string - siempre debes
decirle al ciclo foreach qué tipo de datos esperas obtener de la colección. En caso de que tengas
una lista de varios tipos, debes usar la clase 'object' en lugar de una clase específica, para sacar
cada elemento como un objeto.
Cuando trabajas con colecciones, es muy probable que uses el ciclo foreach la mayor parte del
tiempo, principalmente porque es más simple que cualquier otro ciclo para esta clase de
operaciones.
Introduction to C# classes
En muchos tutoriales de programación, la información sobre las clases es guardada para mucho
después. Sin embargo, ya que C# trata sobre la programación orientada a objetos y como
consecuente también las clases, ahora daremos un vistazo a una básica introducción hacia los
detalles más importantes.
Primero que todo, una clase es un grupo de métodos y variables relacionados. Una clase describe
estos conceptos, y en la mayoría de los casos, se crea una instancia de esta clase, esto se conoce
como objeto. En este objeto, se pueden usar los métodos y las variables definidas. Por supuesto,
se pueden crear todas las instancias que se quieran en la clase. Las Clases y la Programación
Orientada a Objetos en general es un tema extenso. Cubriremos algunos de estos temas en este
capítulo y en capítulos posteriores, pero no todo el tema.
En el capitulo "Hello World", vimos una clase por primera vez, ya que todo en C# esta construido
sobre clases. Expandamos nuestro Hola Mundo con una clase que nosotros construiremos:
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Car car;
Console.ReadLine();
}
}
class Car
{
private string color;
Bueno, hay muchas cosas nuevas aquí, pero la mayoría se basan en cosas que ya hemos utilizado
anteriormente en este tutorial. Como puede ver, hemos definido una nueva clase, llamada Car. Se
declaró en el mismo archivo que nuestra aplicación principal, para una descripcion mas fácil, sin
embargo, generalmente las nuevas clases se definen en sus propios archivos. Se define una única
variable, llamada color, que por supuesto se utiliza para determinar el color de nuestro car. La
declaramos como privada, lo que es una buena práctica - el acceso a las variables desde el exterior
se debe hacer utilizando una propiedad. La propiedad Color se define al final de la clase, dando
acceso a la variable de color.
Además de eso, nuestra clase Car define un constructor. Toma un parametro el cual nos permite
inicializar objetos tipo Car con un color. Debido a que solo hay un constructor, los objetos tipo Car
solo ser pueden instanciar con un color. El metodo Describe() nos permite obtener un nice
mensaje con una unica pieza de informacion that acerca de nuestro car. Simplemente devuelve
una cadena con la información que proporcionamos.
Ahora, en nuestra aplicación principal, declaramos una variable de tipo Car. Después de eso,
hemos creado una nueva instancia de ella, con el color "Rojo" como parámetro. De acuerdo al
código de nuestra clase, significa que el color rojo sera asignado como el color de Car. Para
verificar esto, nosotros llamamos al método Describe(), y para mostrar cuan fácilmente podemos
crear varias instancias de la misma clase, lo hacemos una vez mas, pero con otro color. Y así hemos
creado nuestra primera clase funcional y la hemos usado.
Clases: Fields
Uno de los bloques de construcción más básicos es un campo. Es como una variable, como
hablamos previamente, pero definido en el nivel de clase en lugar de en el nivel método. La
diferencia es bastante importante y tiene que ver con el concepto de alcances, que decide desde
dónde puede ser accedida una variable: Una variable local (definida dentro de un método) solo
puede ser accedida desde este método específico, mientras que un campo de clase puede ser
accedido desde todos los métodos de una clase e incluso desde métodos de otras clases si la
visibilidad lo permite.
En otras palabras, la diferencia entre una variable y un campo es más o menos dónde se declara.
Una variable a nivel de clase se conoce como un campo, mientras que una variable a nivel de
método generalmente se conoce como variable.
Los campos a menudo se declaran cerca de la parte superior de la clase y su visibilidad a menudo
se establecen en privado (discutiremos la visibilidad más adelante en este capítulo). Podría verse
así:
Ahora tenemos una variable de nivel de clase llamada "name", a la que se puede acceder desde
todos los métodos de esta clase. No se puede acceder desde fuera de la clase, pero solo porque lo
hemos marcado como privado(private). Usted es libre de declarar sus campos
como protegido(protected) si desea acceder a ellos desde clases derivadas o
incluso público(public) si desea acceder a ellos desde cualquier lugar, pero tenga en cuenta que
la forma recomendada de acceder a los campos de fuera de una clase es a través de propiedades,
que discutiremos en el siguiente artículo.
En nuestro ejemplo anterior, nuestra variable "name" no tiene un valor inicial, lo que significa que
tendrá que asignarle algo antes de poder usarlo. Si ya sabe con qué valor debe comenzar su
campo, puede asignarlo fácilmente al mismo tiempo que lo declare:
Como hemos hablado anteriormente, los miembros de una clase pueden ser accedidos con la
notación punto, como "class.member". Sin embargo, cuando accedemos a un miembro de la clase
actual, usamos la palabra clave "this", como "this.member". Aquí tenemos un ejemplo más claro
donde declaramos un par de campos y los usamos dentro de un método.
En el método Describe() [Describir()] (no te preocupes, hablaremos de los métodos en uno de los
siguientes artículos), vamos a declarar una variable local llamada "description" [descripción], con
un valor basado en dos campos declarados. La variable "description" [descripción] es un ejemplo
genial de como una variable debe ser siempre una variable y nunca un campo: es temporal y solo
es relevante para el método que la usa, donde el campo puede perfectamente ser relevante en
otros métodos de la clase.
Resumen
Los campos actúan parecido a una variable global, debido a que pueden usarse para almacenar
información y ser accedidos desde cualquier parte dentro de la clase. También pueden ser
accedidos desde fuera de la clase que las define, pero normalmente, son las propiedades las que
se usan para ese propósito. Hablaremos de las propiedades en el siguiente artículo.
Clases:
Properties
En el artículo anterior, vimos los campos. Son como las variables globales de una clase, y permiten
acceder a ellos desde todos los métodos. También vimos que, de hecho, los campos PUEDEN
accederse desde otras clases si se los define como públicos, pero eso, en general, no se
recomienda. Para las variables o campos que se quieran acceder desde fuera de la clase, en
cambio se deberían usar propiedades.
Cuando usted declara un campo como público, está dando acceso completo a el desde afuera -
otras clases pueden hacer lo que quieran con él, sin notificarlo la clase declarante. Las propiedades
regresan el control a la clase declarante, al especificar si un campo es de solo lectura o escritura y
aun permitiendo a la clase declarante verificar y manipular el valor antes de retornarlo o asignarlo
al campo.
Una propiedad se ve como una mezcla entre un campo y un método, ya que es declarado mas
como un campo con visibilidad, un tipo de dato y un nombre, pero tiene también un cuerpo como
un método, para controlar el comportamiento:
Note las palabras clave especiales get y set. Ellas son usadas exclusivamente para propiedades,
para controlar el comportamiento cuando el campo es leido (get) y escrito (set). Usted puede
declarar propiedades con sólo una implementación de get O de un set, por ejemplo, para crear
una propiedad la cual pueda ser leida desde cualquier parte (pública) pero sólo ser modificada
desde dentro de la clase declarante (privada).
También notará que me refiero a un campo llamado _name. Usted tendrá que declarar este en la
clase también, de tal forma que su propiedad pueda usarla. Un patrón de uso común para campos
y propiedades se verá como este:
Ahora puede ver como el campo y la propiedad trabajan juntos: El método get regresará el valor
del campo _name, mientras que el método set asignará el valor pasado al campo _name. En el
método set, usamos la palabra clave especial value la cual, en esta situación específica, referirá al
valor pasado a la propiedad.
Así, esto es prácticamente todo lo básico como puede ser y en este punto, no hicimos nada que
no pueda ser obtenido con un simple campo público. Pero en un punto posterior, usted pudiera
decidir que quiere tomar un mayor control de como otras clases puede trabajar con el nombre y
ya que usted ha implementado esto como un propiedad, es libre de modificar la implementación
sin disturbios para quien use su clase. Por ejemplo, la propiedad Name puede ser modificada para
verse como esto:
El método get ahora forza a que el valor retornado sea siempre en MAYÚSCULAS, no importa en
que tipo esté el campo de respaldo (_name). En el método set, hemos agregado un par de líneas
de código para verificar si el valor pasado contiene un espacio en blanco, porque hemos decidido
que el nombre siempre debe consistir de ambos un nombre y un apellido - si este no es el caso,
una excepción es lanzada. Esto es todo muy crudo y simplificado, pero debe ilustrar el nivel
completo de control que usted obtiene cuando usa propiedades.
La mayoría de las propiedades que verá en los ejemplos de este tutorial serán tanto de lectura
como de escritura, porque ese es el uso más común de las propiedades, pero no siempre es así. En
primer lugar, puede declarar una propiedad usando solo el método get, por ejemplo:
En este caso, ya no puede cambiar la propiedad "Name": solo puede leerlo y el compilador
arrojará un error si intenta asignarle un valor. Sin embargo, aún puede cambiar su valor desde
dentro de la clase, ya que simplemente puede asignar un nuevo valor al campo de respaldo
"_name". Sin embargo, hacerlo de esa manera niega una de las mayores ventajas de las
propiedades: la capacidad de controlar siempre si se puede aceptar un valor. Como ya
mencionamos, el método set es una excelente manera de realizar la validación del valor, excepto si
asigna un nuevo valor al campo _name desde varios lugares, debido a que la propiedad es de solo
lectura, no obtiene esta validación
Afortunadamente para nosotros, C# ofrece una solución a esto: puede definir un método set en la
propiedad, pero limitar su visibilidad, usando, por ejemplo la palabra clave private o protected.
Esto le dará lo mejor de ambos mundos, donde aún puede asignar un valor a la propiedad desde
dentro de la clase (o cualquier clase heredada si usa la palabra clave protected) y validarlo en
consecuencia. Aquí hay un ejemplo:
private string _name = "John Doe";
private set
{
if(IsValidName(value))
this._name = value;
}
}
La diferencia clave aquí es simplemente la palabra clave "privada" justo en frente de la palabra
clave "set" y, como se mencionó, puede reemplazarla con, por ejemplo, protected o internal, según
sus necesidades.
Propiedades Auto-implementadas
En algunos casos, no necesita todo el control sobre los campos y se puede sentir incómodo de
implementar ambos un campo y un propiedad con los métodos get y set sin hacer nada adicional
a lo que vimos en el primer ejemplo. Usted puede ser tentado a declarar simplemente sus
variables como un campo público y evitar toda esta molestia. Pero no lo haga! Afortunadamente
para todos nosotros, Microsoft decidió agregar las propiedades auto-implementadas en C#
versión 3, lo cual le ahorrará varias líneas de código. Sólo considere la diferencia:
Note que los métodos get y set están vacíos y que no se declara un campo de respaldo - en otras
palabras, ¡Ahora podemos obtener el mismo comportamiento exacto como en el primer ejemplo
pero con un sola línea de código! Tenga en mente que un campo de respaldo privado aún existirá
en el tiempo de ejecución - será auto-implementado por el compilador, como lo implica su
nombre. Más tarde podría decidir que usted necesita más control de esta propiedad específica,
usted puede simplemente cambiarla a una combinación regular de campo/propiedad con la
implementación deseada de los métodos get y set.
Note que aún tiene un mecanismo importante de control de las propiedades regulares cuando usa
propiedades auto-implementadas: Usted puede dejar fuera las palabra clave set para crear un
propiedad de sólo lectura, por ejemplo como este:
Las propiedades de sólo escritura no son permitidas cuando se usan propiedades auto-
implementadas.
Previo a C# versión 6, usted no podía definir un valor default (por omisión) de una propiedad
auto-implementada - para ello, usted necesitaría un campo de respaldo declarado, el cual le
permitiría inicializar la variable con un valor:
Pero en C# versión 6, Microsoft finalmente agregó la abilidad para inicializar una propiedad auto-
implementada con un valor default, como esta:
Expression-bodied properties
Otra característica relacionada con las propiedades que Microsoft implementó en C# 6.0 y 7.0 son
los miembros con cuerpo de expresión. Se permite simplemente escribir expresiones de una sola
línea para las propiedades y los métodos - en este caso, veamos cómo usarlo para tus métodos
get/set de manera que tome menos espacio y requiera escribir un poco menos.
Por supuesto esto también funciona si necesitas hacer algo antes de retornar el valor, como ahora:
Como puedes ver, esto te permite definir un metodo get, pero sin las palabras get y return,
mientras que anima a mantener todo en una sola línea en lugar de usar varias.
Resumen
Las propiedades le dan a su clase más control sobre como los campos puedes ser accedidos y
manipulados, y estas siempre deben ser usadas cuando usted quiere dar acceso a los campos
desde fuera de la clase declarante.
Clases:
Methods (functions)
Mientras que las propiedades y los campos pueden considerarse partes pasivas de una clase, los
métodos son activos. Realizarán una o varias acciones y, opcionalmente, devolverán un resultado.
En otros lenguajes de programación, a veces se les llama funciones o incluso "funcs", pero en C #,
donde pertenecen a una clase, se denominan métodos. Los métodos son muy útiles porque le
permiten encapsular una parte de la funcionalidad en un método que luego puede volver a llamar
desde varios lugares.
Este método muy básico adicionará dos números y regresa el resultado. Recorramos las diferentes
partes de él:
Para llamar a un método, simplemente escriba su nombre seguido de paréntesis. Dentro de los
paréntesis, deberías escribir los parámetros (si el método acepta alguno), como este:
AddNumbers(3, 39);
Ya que los métodos son definidos en una clase, quizá quiera llamar un método en otra clase que
en la que se encuentra. Si es así, usted debe prefijar la llamada al método con el nombre del
objeto, o en caso de un método estático (más de ésto adelante), el nombre de la clase. Aquí hay
un ejemplo donde llamamos al método AddNumber(), el cual ha sido ubicado en otra clase
llamada MathHelper:
Hablemos de los tipos (de datos) de retorno. En los ejemplos previos, definimos un método con un
entero como el tipo de retorno, pero usted es libre de retornar cualquier otra clase de tipo de
datos de C#. De hecho, puede incluso declarar un método el cual no retorne nada, como lo vimos
con nuestro método DoMath() arriba. Note que he sustituido int con la palabra clave void, lo cual
significa que este método se supone no regresa nada. En alunos lenguajes de programación, las
funciones sin tipo de retorno son referidas como procedures [procedimientos], pero en C#, estas
son siempre llamadas métodos.
Debe estar consciente de que cuando declara un tipo de retorno para un método, tiene que
retornar algo - de otro modo, el compilador se quejará inmediatemente:
Esto significa que usted necesita una (o varias) palabras reservadas return dentro de su método si
este tiene un tipo de retorno declarado. Usted puede necesitar más de un return para situaciones
donde tenga múltiples trayectorias posibles de código, como esto:
Resumen
Los métodos le permiten encapsular y reusar funcionalidad desde diversos lugares. Al proveer
diferentes parámetros a un método, usted puede obtener diferentes resultados. Hemos usado
algunos parámetros en los ejemplos anteriores, pero en el próximo artículo, cavaremos mucho
más profundo en el tema de los parámetros de métodos.
Clases:
Métodos con parámetros
Esto demuestra en gran medida lo ingenioso que es el concepto de los métodos, porque con los
métodos se puede encapsular la funcionalidad en el método, pero se puede modificar el resultado
cuando se llama a este método a través de los parámetros:
AddNumbers(2, 3);
Result: 5
AddNumbers(38, 4);
Result: 42
Este es el tipo básico de parámetros, pero hablemos más sobre los diversos modificadores y
opciones que se pueden usar para cambiar el comportamiento de los parámetros.
Tenga en cuenta que en este artículo, vamos a profundizar en todos los tipos de parámetros y en
cómo pueden ayudarle, pero si acaba de empezar con C# y sólo quiere ver algunos resultados, lo
siguiente podría ser un poco demasiado complejo y técnico por ahora. Siéntase libre de saltarse el
resto del artículo y volver más tarde cuando esté listo.
Parámetros opcionales
Por defecto, cuando se llama a un método con uno o varios parámetros, estás obligado a
suministrar valores para todos estos parámetros. Sin embargo, en algunas situaciones, es posible
que tengas que hacer que uno o varios parámetros sean opcionales. En algunos lenguajes de
programación, se le permitiría hacerlo simplemente marcando el parámetro como opcional, pero
en C#, un parámetro se hace opcional suministrando un valor por defecto para él en la declaración
del método. En realidad, ésta es una buena solución, porque te ahorra tener que escribir código
adicional para hacer frente a situaciones en las que el parámetro no es suministrado por quien
llama al método.
El último parámetro (number3) es ahora opcional, porque hemos proporcionado un valor por
defecto para él (0). Al llamarlo, ahora puede proporcionar dos o tres valores, de esta forma:
Puede hacer que más de un parámetro sea opcional - de hecho, su método puede consistir sólo
en parámetros opcionales si es necesario. Sólo recuerde que los parámetros opcionales deben
ser los últimos en la declaración del método - no los primeros o entre parámetros no
opcionales.
El modificador params
Como alternativa a una serie de parámetros opcionales, se puede utilizar el modificador de params
para permitir un número arbitrario de parámetros. Podría ser parecido a esto:
Otra ventaja de usar el enfoque con params, es que también se le permite pasar cero parámetros
al método. Los métodos con params también pueden aceptar parámetros normales, siempre y
cuando el parámetro con el modificador params sea el último. Por otra parte, sólo se puede utilizar
un parámetro con la palabra clave params por método. Éste es un ejemplo completo en el que
usamos el modificador params para imprimir un número variable de nombres con nuestro
método GreetPersons():
C#, al igual que otros lenguajes de programación, diferencia entre dos tipos de parámetros: "por
valor" y "por referencia". El valor por defecto en C# es "por valor", lo que básicamente significa
que cuando se pasa una variable en una llamada a un método, en realidad se está enviando una
copia del objeto, en lugar de una referencia a él. Esto también significa que puedes hacer cambios
en el parámetro desde dentro del método, sin afectar al objeto original que pasaste como
parámetro.
Tenemos un método muy básico llamado AddFive() el cual adicionará 5 al número que le pasemos.
Así en nuestro método Main(), creamos un variable llamada number con el valor de 20 y entonces
llamamos al método AddFive(). Ahora en la siguiente línea, cuando escribamos la variable number,
uno esperaría que el valor es ahora 25, pero en vez de eso, permanece como 20. ¿Porqué? Por que
por defecto, el parámetro fue pasado como una copia de objeto original (por valor), así cuando
nuestro método AddFive() trabajó en el parámetro, estaba trabajando en una copia y así nunca
afecto la variable original.
Actualmente hay dos formas de cambiar este comportamiento , así a nuestro método AddFive() le
es permitido modificar el valor original: Podemos usar el modificador ref or los modificadores
in/out
El modificador ref
Notará que he agregado la palabra clave "ref" en dos lugares: En la declaración del método y
cuando paso el parámetro en la llamada al método. Con este cambio, ahora obtenemos el
comportamiento que esperabamos originalmente - el resultado es ahora 25 en vez de 20, por que
el modificador ref permite al método trabajar en el valor pasado realmente del parámetro y no en
una copia.
El modificador out
Al igual que el modificador ref, el modificador out asegura que el parámetro es pasado como
referencia en vez de como valor, peroi hay una diferencia mayor: Cuando usa el modificador "ref",
usted pasa un valor inicializado el cual puede decidir modificar dentro del método, o dejarlo como
está. Por otro lado, cuando usa el modificador out, usted forza a inicializar el parámetro dentro
del método. Esto significa que puede pasar un valor no inicializado cuando usa el modificador
"out" - el compilador se quejará si trata de terminar un método con un parámetro out sin asignarle
un valor a él.
En C#, un método puede retornar solamente un valor, pero si usa el modificador out, es capaz de
rodear esto pasando varios parámetros con el modificador out - cuando el método ha sido
llamado, los parámetros out habrán sido asignados. Aquí está un ejemplo, donde pasamos dos
números y luego, usando modificadores out, retornamos ambos una suma y una resta usando
estos números.
public void DoMath(int number1, int number2, out int addedValue, out int subtractedValue)
{
addedValue = number1 + number2;
subtractedValue = number1 - number2;
}
Output:
15
5
Como puede var en el inicio del ejemplo, declaro las dos variables (addedValue y subtractedValue)
antes de pasarlas como parámetros out. Esto era un requerimiento en versiones previas del
lenguaje C#, pero desde la version 6 de C#, puede declararlas directamente en la llamada al
método, así:
Así como el modificador out, el modificador in asegura que el parámetro sea pasado como una
referencia en vez de como una copia del valor, pero a diferencia del modificador out, el
modificador in le prevendrá de hacer cualquier cambio a la variable dentro del método.
Su primer pensamiento puede ser: Si no puedo cambiar el valor del parámetro, entonces quizá
también puedo pasarlo como un parámetro regular, donde los cambios no afectarán a la variable
original. Tiene razón, el resultado aparecerá exactamente igual, pero aún habrá una razón muy
válida para usar el modificador "in": Al pasarlo como una referencia en vez de un valor, está
ahorrando recursos por que el ambiente no tiene que invertir tiempo creando una copia del objeto
cuando lo pasa al método como un parámetro regular.
En la mayoria de los casos, esto no hará mucha diferencia, por que la mayoría de los parámetros
con cadenas simples o enteros, pero en situaciones donde llame repetidamente el mismo método
muchas veces en un ciclo o donde use parámetros con valores largos, por ejemplo una cadena
muy larga o estructuras, esto puede darle una buena mejora del desempeño.
Como lo ha visto en todos los ejemplos de arriba, cada parámetro tiene un identificador único en
la declaración del método. Esto le permite referenciar al parámetro dentro del método. Sin
embargo, cuando llama al método, usted no usa estos nombres - usted solamente provee los
valores en el mismo orden en el que fueron declarados. Esto no es un problema para métodos
simples que toman 2 o 3 parámetros, pero algunos métodos son más complejos y requieren más
parámetros. En esas situaciones, puede ser difícil darse cuenta de a que se refieren los varios
valores en la llamada al método, como en este ejemplo:
No es muy claro que significan los varios parámetros en esta llamada al método, pero si mira en la
declaración del método, puede ver esto:
public void PrintUserDetails(int userId, string name, int age = -1, List<string> addressLines =
null)
{
// Print details...
}
Pero es molesto si tiene que buscar constantemente la declaración del método para entender lo
que hacen los parámetros, así para métodos más complejos, puede suministrar los nombres de los
parámetros directamente en la llamada del método. Esto le permite suministrar los nombres de los
parámetros en cualquier orden, en vez de ser forzado a usar el orden de la declaración. Aquí está
un ejemplo:
Como un bono extra, esto le permite suministrar un valor para cualquiera de sus parámetros
opcionales, sin tener que proveer valores para todos los parámetros opcionales previos en la
declaración. En otras palabras, si quiere proveer un valor para el parámetro addressLines en este
ejemplo también tendría que proveer un valor para el parámetro age, por que viene primero en la
declaración del método. Sin embargo, si usa parámetros nombrados, el orden ya deja de importar
y puede suministrar solamente los valores para los parámetros requeridos así como uno o varios
de los parámetros opcionales, por ejemplo:
PrintUserDetails(addressLines: new List<string>() { }, name: "Jane Doe", userId: 2);
public void PrintUserDetails(int userId, string name, int age = -1, List<string> addressLines =
null)
{
// Print details...
Console.WriteLine(name + " is " + age + " years old...");
}
Resumen
A como pudo ver en este articulo, los parámetros vienen en diferentes formas y tipos.
Afortunadamente, obtendrá el camino con parámetros regulares antiguos y sencillos, pero una vez
que comience a profundizar en el lenguaje C#, se beneficiará de conocer todos los tipos y
modificadores como se explica en este artículo.
Clases:
Constructores y destructores
Los constructores son métodos especiales, usados cuando instanciamos una clase. Un constructor
nunca puede devolver nada, por lo que no tiene que definir un tipo de retorno para el. Un método
normal se define así:
public Car()
En nuestro ejemplo para este capítulo, tenemos una clase Car, con un constructor que toma una
cadena como argumento. Por supuesto, un constructor también puede estar sobrecargado, lo que
significa que podemos tener varios constructores, con el mismo nombre, pero con diferentes
parámetros. Aquí hay un ejemplo:
public Car()
{
Un constructor puede llamar a otro constructor, lo cual puede ser útil en varias situaciones. Aquí
hay un ejemplo:
public Car()
{
Console.WriteLine("Constructor with no parameters called!");
}
Si ejecuta este código, verá que el constructor sin parámetros es llamado primero. Esto puede ser
usado para crear una instancia de varios objetos para la clase en el constructor predeterminado,
que se puede llamar desde otros constructores de la clase. Si el constructor al que desea llamar
toma parámetros, puede hacerlo también. Aquí hay un ejemplo sencillo:
Si llamas al constructor que toma 2 parámetros, el primer parámetro se usará para invocar al
constructor que toma un parámetro.
Destructores
Desde que C# es recolectador de basura, significa que el framework liberará los objetos que ya no
use, habrán ocasiones donde necesite hacer alguna limpieza manual. Un destructor, un método
invocado una vez que el objeto es puesto a disposición, puede ser usado para limpiar recursos
usados por el objeto. Los destructores no se ven tanto como otros métodos en C#. Aquí hay un
ejemplo de un destructor de nuestra clase Car:
~Car()
{
Console.WriteLine("Out..");
}
Una vez que el objeto es recogido por el recolector de basura, este método es invocado.
Clases:
Sobrecarga de Métodos
Muchos de los lenguajes de programación soportan una técnica llamada parámetros por defecto u
opcionales. Permite al programador hacer uno o mas parámetros opcionales, dándoles un valor
por defecto. Esto es especialmente práctico cuando se añade funcionalidad al código existente.
Por ejemplo, tal vez desees añadir funcionalidad a una función ya existente que ahora requiere que
se agreguen uno o más parámetros. Al agregarlos, podrías hacer que el código que llama a esta
función no funcione más puesto que no se estarían pasando los nuevos parámetros. La solución a
este problema sería que definieras los parámetros añadidos como opcionales y darles un valor por
default que hagan que la función asuma los valores en caso de que no se le pasaran los
parámetros requeridos.
Los parámetros por defecto fueron introducidos en la versión 4.0 de C#, pero hasta ese punto,
quienes escribían código en C# habían estado utilizando otra técnica que, básiciamente, hace lo
mismo llamada sobrecarga de métdoos. Esto permite al programador definir varios métodos con
el mismo nombre, siempre y cuando reciban un conjunto distinto de parámetros. Cauando utilizas
clases de .NET, puedes notar rápidamente que la sobrecarga de métodos está en todos lados. Un
buen ejemplo de esto es el método Substring de la clase String; está sobrecargado y se ve así:
Pues llamarla ya sea con uno o dos parámetros. Si solo la llamas con un parámetro, el parámetro
de longitud se llena con la longitud total de la cadena, ahorrándonos tiempo cuando simplemente
queremos obtenenr la última parte de la cadena.
Así que, al definir varias versiones de la misma función, ¿cómo evitamos tener el mismo código en
varios lugares? Fácil: dejamos que la versión simple del método haga que la versión complicada le
haga todo el trabajo. Mira el siguiente ejemplo:
class SillyMath
{
public static int Plus(int number1, int number2)
{
return Plus(number1, number2, 0);
}
Definimos al método Plus en dos versiones. La primera toma dos parámetros para sumarlos,
mientras que la segunda toma tres parámetros. La suma se realiza en la versión que requiere tres
parámetros, si solo queremos sumar dos, llamamos a la versión de tres parámetros y le pasamos
un cero, actuando como valor por defecto. Lo sé, lo sé, como lo indica el nombre de la clase, es un
ejemplo muy simple; pero debería darte una idea de cómo funciona esto de la sobrecarga.
Ahora, cuando te sientas con ganas de hacer matemáticas avanzadas y sumar cuatro números
(broma), es muy simple agregar una nueva sobrecarga al método:
class SillyMath
{
public static int Plus(int number1, int number2)
{
return Plus(number1, number2, 0);
}
Lo genial de esto es que todas tus llamadas al método Plus existentes seguirán funcionando como
si nada hubiera cambiado. Entre más C# utilices, más apreciarás la sobrecarga de métodos.
Clases:
Herencia
La Herencia puede ser un aspecto difícil de comprender, así que comencemos con unos ejemplos
simples:
Primero, definimos una clase llamada Animal, con un método llamado Greet el cual imprime un
saludo en la consola. Luego definimos una clase de perro, y con el operador dos puntos ':' le
decimos a C # que la clase de perro hereda de la clase de animal. Lo elegante de este método es
que tiene sentido en el mundo real: un perro ES un animal. Intentemos usar las clases:
Si corres este ejemplo, notarás que aunque no hayamos definido un método Greet() para la clase
Dog, todavía sabe cómo saludarnos, ya que hereda este método de la clase Animal. Sin embargo,
este saludo es un poco genérico, por lo que vamos a personalizarlo cuando definamos qué animal
es:
Aparte del método Greet() en la clase Dog, debes notar dos cosas: en la clase Animal he agregado
la palabra reservada "virtual" y en la clase Dog uso la palabra reservada "override"
En C#, no esta permitido sobreescribir un miembro de una clase a menos que este marcado como
virtual. Sí quieres, puedes aun así acceder al método heredado, incluso después de sobre
escribirlo, usando la palabra clave "base".
Sin embargo los métodos no son lo único que se hereda. De hecho, casi todos los miembros de la
clase serán heredados, incluidos los campos y las propiedades. Sólo recuerda las reglas de
visibilidad, como se discutió en un capítulo anterior.
Se puede tener toda una jerarquía de clases que hereden una de la otra. Por ejemplo, podríamos
crear una clase Puppy que hereda de nuestra clase Dog que a su vez hereda de la clase Animal. Lo
que no puede hacer en C # es que una clase herede de más de una clase al mismo tiempo. La
herencia múltiple, como es conocida, no es compatible con C#.
Collections / Colecciones:
Lists / Listas
C# tiene un amplio rango de clases para lidear con listas. Implementando la interfaz de iList y la
implementación mas popular es la lista generica, normalmente referida como List<T> La "T"
especifica el tipo de objeto contenido en la lista, el cual tiene el beneficio añadido de que el
compliador verificará y se asegurará que se agreguen unicamente objetos del tipo de la lista - en
otras palabras la lista tipo -List <Type> es segura para agregar elementos de un mismo tipo.
List es muy parecida a la clase ArrayList, que era la opción de ir a la lista antes de que C #
admitiera listas genéricas. Por lo tanto, también verá que List puede hacer muchas de las mismas
cosas que una Matriz (que por cierto también implementa la interfaz IList), pero en muchas
situaciones, List es más simple y fácil de trabajar. Por ejemplo, no tiene que crear una Lista con un
tamaño específico; en su lugar, puede crearla y .NET la expandirá automáticamente para ajustarse
a la cantidad de elementos a medida que los agregue.
Como mencioné, la T se refiere a tipo y se usa para especificar el tipo de objetos que quieres que
contenga la lista. En nuestro primer ejemplo, te mostraré como crear una lista que debería
contener strings:
Esto crea una lista vacía, pero agregarle algo luego es muy fácil con el método Add:
listOfStrings.Add("a string");
Sin embargo, si intenta agregar algo que no sea una cadena, el compilador se quejará de
inmediato:
listOfStrings.Add(2);
Error CS1503 Argument 1: cannot convert from 'int' to 'string'
Inicializando una lista con ítems
En el ejemplo anterior, acabamos de crear una lista y luego le agregamos un elemento. Sin
embargo, C # realmente le permite crear una lista Y agregarle elementos dentro de la misma
declaración, utilizando una técnica llamada inicializadores de colección. Veamos cómo se hace:
La sintaxis es bastante simple: antes del punto y coma final habitual, tenemos un conjunto de
llaves, que a su vez contiene una lista de los valores que queremos que estén presentes en la lista
desde el principio. Como se trata de una lista de strings, los objetos iniciales que proporcionamos
deben ser del tipo de string. Sin embargo, se puede lograr exactamente lo mismo para la lista de
otros tipos, incluso si estamos usando nuestras propias clases, como demostraré en el siguiente
ejemplo.
Hay varias formas de trabajar con los elementos de una lista genérica y mostrar algunos de ellos.
He creado un ejemplo más amplio:
using System;
using System.Collections.Generic;
namespace Lists
{
class Program
{
static void Main(string[] args)
{
List<User> listOfUsers = new List<User>()
{
new User() { Name = "John Doe", Age = 42 },
new User() { Name = "Jane Doe", Age = 34 },
new User() { Name = "Joe Doe", Age = 8 },
};
for(int i = 0; i < listOfUsers.Count; i++)
{
Console.WriteLine(listOfUsers[i].Name + " is " + listOfUsers[i].Age + " years old");
}
Console.ReadKey();
}
}
class User
{
public string Name { get; set; }
Comencemos desde abajo, donde definimos una clase simple para almacenar información sobre
un Usuario, solo un nombre y una edad. Volvemos a la parte superior del ejemplo, donde he
cambiado nuestra lista para usar esta clase de usuario en lugar de cadenas simples. Utilizo un
inicializador de colección para completar la lista con los usuarios: observe cómo la sintaxis es la
misma que antes, solo que es un poco más compleja porque se trata de un objeto más complejo
que una cadena.
Una vez que tenemos lista la lista, utilizo un bucle for para ejecutarla - para saber cuántas
iteraciones vamos a hacer, uso la propiedad Count de la lista. En cada iteración, accedo al usuario
en cuestión a través del indexador de la lista, utilizando la sintaxis de corchetes (por ejemplo,
listOfUsers [i]). Una vez que tengo el usuario, escribo el nombre y la edad.
Ya intentamos agregar un solo elemento a una lista, pero hay más opciones para hacerlo. En
primer lugar, puede insertar un elemento en lugar de agregarlo; la diferencia es que, si bien el
método Addsiempre agrega al final de la lista, el método Insertle permite insertar un elemento en
una posición específica. Aquí hay un ejemplo:
Comenzamos la lista con solo un elemento, pero luego insertamos dos elementos más, primero en
la parte superior de la lista y luego en el medio. El primer parámetro del método Insert es el índice
donde queremos insertar el elemento. Sin embargo, tenga cuidado: se lanzará una excepción si
intenta insertar un elemento en el índice 3, ¡si la lista tiene menos elementos!
Al igual que tenemos los métodos Agregar e Insertar para agregar un solo elemento, también hay
métodos correspondientes para agregar e insertar múltiples elementos. Se llaman AddRange()()
e InsertRange() () y acepta cualquier tipo de colección que implemente la
interfaz IEnumerable como parámetro; esto podría ser, p.e. una matriz de elementos u otra lista,
qué elementos desea agregar o insertar en la lista actual.
Como ejemplo de los métodos Range, hagamos algo divertido: combinamos el método AddRange
con un inicializador de colección para agregar varios nombres nuevos a una lista existente en una
sola declaración:
listOfNames.AddRange(new string[]
{
"Jenna Doe",
"Another Doe"
});
Simplemente creamos una serie de cadenas sobre la marcha e inmediatamente agregamos sus
elementos a nuestra lista de nombres del ejemplo anterior.
Borrando elementos.
Actualmente hay tres métodos a su disposición cuando desea eliminar uno o varios elementos de
una lista: Remove(), RemoveAt() y RemoveAll().
El método Remove() toma solo un parámetro: el elemento que desea eliminar. Esto es genial para,
p.e. una lista de cadenas o enteros, porque simplemente puede escribir el elemento que desea
eliminar. Por otro lado, si tienes una lista de objetos complejos, primero deberías encontrar ese
objeto, para tener una referencia que puedas pasar al método Remove (). "Vamos a tratar eso más
tarde. Aquí hay un ejemplo muy básico sobre cómo puedes eliminar un solo elemento con el
método Remove ():
listOfNames.Remove("Joe Doe");
El método Remove () simplemente recorre la lista en iteración hasta que encuentra la primera
instancia del objeto que especificó para su eliminación, y ellos la eliminan; solo elimina una
instancia, y si especifica un elemento en la lista que no existe, No se arroja ningún error. El método
devuelve true si pudo eliminar un elemento y false si no lo fue.
El método RemoveAt() aprovecha el hecho de que la lista genérica se basa en índices al permitirle
eliminar un elemento en función de su índice / posición en la lista. Por ejemplo, podría eliminar el
primer elemento de la lista así:
listOfNames.RemoveAll(0);
RemoveAll() es el más complejo de los métodos remove, pero definitivamente también el más
poderoso. Toma un delegado a un método como parámetro y este método decide si un elemento
debe eliminarse o no devolviendo verdadero o falso. Esto le permite aplicar su propia lógica al
eliminar elementos y también le permite eliminar más de un elemento a la vez. Los delegados
serán tratados en otra parte de este tutorial, porque es un tema grande y complejo, pero todavía
quiero que entiendan lo genial que es el método RemoveAll, así que aquí hay un ejemplo:
listOfNames.RemoveAll(name =>
{
if (name.StartsWith("J"))
return true;
else
return false;
});
En este ejemplo, utilizamos un método anónimo (nuevamente demasiado complejo para ser
explicado aquí, pero será tratado en otro capítulo) como un parámetro para el método RemoveAll.
Nuestro método anónimo es bastante simple: se llamará para cada elemento de la lista y tendrá
un parámetro llamado name, que es, por supuesto, el elemento actual en la iteración. Mira este
nombre y si comienza con "J", se devuelve true; de lo contrario, es false. El método RemoveAll ()
usa esta respuesta (verdadero o falso) para decidir si cada elemento debe eliminarse o no. Al final,
esto deja nuestra lista inicial con solo un miembro de Doe: Another Doe.
Hasta ahora, los elementos de la lista con los que hemos trabajado se han utilizado en el orden en
que se agregaron a la lista. Sin embargo, es posible que desee ordenar los elementos de una
manera específica, p. alfabéticamente en el caso de nuestra lista de nombres. La List<T> tiene un
método Sort () que podemos usar para esto:
Como verá en la salida, los elementos de la lista ahora se han ordenado alfabéticamente, y si lo
desea en orden descendente (de Z a A), simplemente llame al método Reverse()después de
realizar la clasificación:
listOfNames.Sort();
listOfNames.Reverse();
Entonces ordenar una lista fue bastante fácil, ¿verdad? Bueno, fue muy fácil porque tenemos una
lista de cadenas y .NET Framework sabe exactamente cómo comparar dos cadenas. Si tiene una
lista de números, .NET, por supuesto, también sabrá cómo ordenar eso. Por otro lado, es posible
que tenga una lista de objetos personalizados (ya que laList<T>puede contener cualquier objeto)
que .NET no tiene la posibilidad de saber cómo comparar. Hay varias soluciones a este problema,
p. implementando la interfaz IComparable o usando LINQ (lo veremos más adelante en este
tutorial), pero como solución rápida, también podemos proporcionar un método para que llame el
método Sort (), para aprender cómo se comparan dos elementos uno contra el otro, así:
using System;
using System.Collections.Generic;
namespace ListSort
{
class Program
{
static void Main(string[] args)
{
List<User> listOfUsers = new List<User>()
{
new User() { Name = "John Doe", Age = 42 },
new User() { Name = "Jane Doe", Age = 39 },
new User() { Name = "Joe Doe", Age = 13 },
};
listOfUsers.Sort(CompareUsers);
foreach (User user in listOfUsers)
Console.WriteLine(user.Name + ": " + user.Age + " years old");
}
Esto agregó bastante código a nuestro ejemplo, pero en realidad no es demasiado complicado. Si
comenzamos desde abajo, he creado una clase de usuario muy simple, que consta de un nombre y
una edad. En el medio, he declarado un método llamado CompareUsers(): toma dos usuarios como
parámetros y luego devuelve un número entero, que indicará si un elemento es "más pequeño",
"igual" o "más grande" (-1, 0 o 1). El método Sort () utilizará estos valores para mover los
elementos de modo que el orden de los elementos coincida con lo que queremos. En este caso,
simplemente uso la propiedad Age para comparar, esencialmente dejándonos con una lista de
usuarios ordenados por su edad.
Resumen
Este artículo es uno de los más largos en este tutorial, pero espero que haya aprendido mucho
sobre las listas, porque mientras más programación haga, más se dará cuenta de lo importantes
que son las listas y los diccionarios. Hablando de diccionarios, los discutiremos en el próximo
artículo.
C# 3.0:
Translate
Collection Initializers
Así como C# 3.0 ofrece una nueva manera de inicializar objetos, una nueva sintáxis para inicializar
una lista con un conjunto específico de elementos, ha sido incluida. Podemos usar la clase Car del
capítulo anterior:
class Car
{
public string Name { get; set; }
public Color Color { get; set; }
}
Si quisieramos crear una lista para contener un rango de vehículos, tendríamos que hacer algo
como esto en C# 2.0:
Car car;
List<Car> cars = new List<Car>();
Sin embargo, puede ser aún más simple, cuando lo combinamos con inicializadores de
colecciones:
List<Car> cars = new List<Car> { new Car { Name = "Corvette", Color = Color.Yellow }, new
Car { Name = "Golf", Color = Color.Blue} };
10 líneas de código han sido reducidas a una, aunque una línea un poco larga, gracias a los
inicializadores de objetos y colecciones.