Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Manual de C#
Manual de C#
MANUAL DE C#
¿Qué es C#?
C# o C Sharp es un lenguaje de programación que está incluido en la Plataforma
.NET y corre en el Lenguaje Común en Tiempo de Ejecución (CLR, Common
Language Runtime). El primer lenguaje en importancia para el CLR es C#, mucho de
lo que soporta la Plataforma .NET está escrito en C#.
C# intenta ser el lenguaje base para escribir aplicaciones .NET
C# deriva de C y C++, es moderno, simple y enteramente orientado a objetos,
simplifica y moderniza a C++ en las áreas de clases, namespaces, sobrecarga de
métodos y manejo de excepciones. Se elimino la complejidad de C++ para hacerlo
más fácil de utilizar y menos propenso a errores.
Algunas características de C# son:
1
Los parámetros que son pasados son type-safe.
El soporte de versiones lo provee el CLR.
Permite acceder a diferentes APIs a través de .NET Common Language
Specification, el cual define el estádar de interoperabilidad entre
lenguajes que se adhieran a este estándar.
La Plataforma .NET provee un acceso transparente a COM.
Soporta OLE
Permite la interoperabilidad con APIs al estilo C y DLLs, esta
característica para acceder a APIs nativas es llamada Platform Invocation
Services (PInvoke)
Por default el código es safe mode, pero es posible declarar clases o sólo
métodos unsafe, esta declaración permite utilizar apuntadores,
estructuras y almacenamiento de arreglos estáticos.
C# depende del runtime que provee la Plataforma .NET, el runtime
administra la ejecución de código.
Encapsulación
Herencia
Polimorfismo
Objeto
Un Objeto es una instancia de un tipo de clase.
La instanciación es el acto de crear una instancia de un objeto, la instancia es un
objeto, la instanciación usa el operador new, después la instanciación es posible
comunicarnos con el objeto a través de sus miembros.
Un Objeto es una colección de información relacionada y funcional.
Un objeto se compone de:
Herencia
La Herencia es la habilidad para heredar datos y funcionalidad de un objeto padre, la
herencia es una característica fundamental de un sistema orientado a objetos.
A través de la herencia es posible crear o derivar una nueva clase basada en una
clase existente.
2
Una clase derivada es la nueva clase que esta siendo creada y la clase base es una
de las cuales la nueva clase es derivada. La nueva clase derivada hereda todos los
miembros de la clase base por consiguiente permite reusar el trabajo previo.
En C# se puede asumir que la clase derivada podría heredar todos los miembros de la
clase base.
La herencia es un ejemplo del diseño orientado a objetos conocido como una relación
"is-a" (es-un), por ejemplo:
"un empleado es una persona".
Al utilizar la herencia la clase base necesita ser diseñada teniendo en mente la
herencia, si los objetos no tienen la estructura apropiada la herencia no podría
funcionar correctamente.
Una clase derivada no debería requerir más ni prometer menos que su clase base
sobre cualquiera de sus interfaces heredadas.
Una interfaz de clase es un contrato entre esta y los programadores que usan la
clase.
upcasting, cuando un programador tiene una referencia a la clase derivada, el
programador siempre puede tratar a esa clase como si fuera la clase base.
En el lenguaje común en tiempo de ejecución .NET todos los objetos heredan de la
última clase base llamada object y existe sólo una herencia simple de objetos.
Un objeto puede derivar sólo de una clase base.
Clase
Una Clase es una plantilla para un objeto.
Una Clase define las operaciones que un objeto puede realizar y define un valor que
mantiene el estado del objeto, los componentes principales de una clase son:
métodos, eventos y propiedades.
Una instancia de una clase es un objeto, se accede a la funcionalidad de un objeto
invocando sus métodos y accediendo a sus propiedades, eventos y campos.
Una clase utiliza modificadores para especificar la accesibilidad de la clase y sus
componentes, los componentes de una clase son llamados miembros por lo que
existen diferentes tipos de miembros. Una referencia se refiere a una instancia, una
instancia es la creación de un objeto del tipo clase que se está declarando. Una clase
utiliza ninguno, uno o más constructores para ayudar a definir la instancia de una
clase. Existe una palabra reservada llamada this que sirve para hacer referencia a la
clase actual en el ámbito en el cual es utilizada. Cuando se hace referencia a una
variable de instancia que tiene el mismo nombre de un parámetro se debe utilizar
this.name.
Al crear y manipular objetos no es necesario administrar la memoria que estos ocupan
ya que existe un mecanismo que se encarga de esto llamado garbage collector
(recolector de basura), pero es una buena práctica no olvidar liberar los recursos.
Funciones Miembro
Una Función Miembro puede ser un constructor, es decir, una pieza de código que
es invocada en una instancia del objeto.
Campos Estáticos
Un Miembro Estático definine miembros de un objeto que no son asociados con una
instancia de clase específica.
Un Campo Estático es el tipo más simple de un miembro estático, para declarar un
campo estático se utiliza el modificador static.
Un campo estático puede accederse a través del nombre de la clase, en vez de la
instancia de la clase (objeto):
using System;
3
class MiContador{
//Campo Estático
public static int iContador = 0;
public MiContador(){
iContador++;
}
}
class App{
public static void Main(){
MiContador ContadorA = new MiContador();
Console.WriteLine(MiContador.iContador);
MiContador ContadorB = new MiContador();
Console.WriteLine(MiContador.iContador);
}
}
El ejemplo determina cuantas instancias del objeto han sido creadas.
Clase Base
Una Clase base es un objeto padre de donde se basa un nuevo trabajo.
Clase Derivada
Una Clase derivada es un objeto hijo.
Clase Abstracta
Una Clase Abstracta define las funciones que una clase derivada debe implementar.
Una Clase Abstracta define un contrato en donde las clases derivadas deben definir
las funciones que la clase padre marca utilizando la palabra reservada abstract,
además que la clase padre también se define como abstract.
using System;
abstract public class Persona{//Indica que la clase es abstracta
//Propiedades
public string sNombre;
public int iEdad;
//Constructor
public Persona(string sNombre, int iEdad){
this.sNombre = sNombre;
this.iEdad = iEdad;
}
//Métodos
abstract public string Tipo();//Método que la clase derivada debe
implementar
}
//Herencia Simple
public class Empleado : Persona{
public Empleado(string sNombre, int iEdad):base(sNombre, iEdad){}
override public string Tipo(){
return "Empleado";
}
}
class App{
//Aplicación
public static void Main(){
Console.WriteLine("--- Arreglo de Objetos ---");
4
aProgramadores[0] = new Empleado("Bill Gates", 50);
aProgramadores[1] = new Empleado("Eric S. Raymond", 60);
for(int i = 0; i < aProgramadores.Length; i++){
Console.WriteLine("aProgramadores["+i+"].sNombre : " +
aProgramadores[i].sNombre);
Console.WriteLine("aProgramadores[" + i + "].iEdad : " +
aProgramadores[i].iEdad);
Console.WriteLine("aProgramadores[" + i + "].Tipo : " +
aProgramadores[i].Tipo());
}
}
}
Clase Sealed
Una Clase sealed se utiliza para prevenir que una clase sea utilizada como una clase
base, su principal uso es para prevenir la derivación no planeada.
sealed class ClaseBase{
ClaseBase(){}
}
class ClaseDerivada : ClaseBase{
}
class Sellada{
public static void Main(){
ClaseDerivada CD = new ClaseDerivada();
}
}
Al compilar el código se muestra el siguiente mensaje:
5
//Propiedades
public string sNombre;
public int iEdad;
private double dSueldo;
//Constructor
public Persona(string sNombre, int iEdad){
this.sNombre = sNombre;
this.iEdad = iEdad;
}
//Métodos
public string Tipo(){
return "Persona";
}
public void AsignarSueldo(double dSueldo){
this.dSueldo = dSueldo;
}
public double ObtenerSueldo(){
return this.dSueldo;
}
}
//Herencia Simple
public class Empleado : Persona{
public Empleado(string sNombre, int iEdad):base(sNombre, iEdad){}
public new string Tipo(){
return "Empleado";
}
double dSueldo;
public new void AsignarSueldo(double dSueldo){
this.dSueldo = dSueldo * dSueldo;
}
public new double ObtenerSueldo(){
return this.dSueldo;
}
}
class App{
//Aplicación
public static void Main(){
Persona Mexicano = new Persona("Gerado Ángeles
Nava", 33);
Console.WriteLine("Mexicano.sNombre : " +
Mexicano.sNombre);
Console.WriteLine("Mexicano.iEdad : " + Mexicano.iEdad);
double dSueldo = 123.456;
Mexicano.AsignarSueldo(dSueldo);
Console.WriteLine("Mexicano.iSueldo : " +
Mexicano.ObtenerSueldo());
Console.WriteLine("Mexicano.Tipo : " + Mexicano.Tipo());
6
Console.WriteLine("Programador.iEdad : " +
Programador.iEdad);
Programador.AsignarSueldo(dSueldo);
Console.WriteLine("Programador.iSueldo : " +
Programador.ObtenerSueldo());
Console.WriteLine("Programador.Tipo : " +
Programador.Tipo());
}
}
Polimorfismo y Funciones Virtuales
El polimorfismo es la funcionalidad que permite a código antiguo invocar código
nuevo, también permite extender el sistema sin modificar el código existente, esto se
logra sobreescribiendo o redefiniendo el código, para lo cual se utilizan funciones
virtuales y la palabra clave override.
Las funciones abstractas son automaticamente funciones virtuales, las cuales
permiten al programador usar polimorfismo para hacer su código simple.
Virtual significa que cuando una invocación a funciones miembro, el compilador
debería buscar por el tipo real del objeto y no por el tipo de la referencia, e invocar en
base al tipo la función apropiada.
using System;
public class Persona{
//Propiedades
public string sNombre;
public int iEdad;
//Constructor
public Persona(string sNombre, int iEdad){
this.sNombre = sNombre;
this.iEdad = iEdad;
}
//Métodos
virtual public string Tipo(){
return "Persona";
}
}
//Herencia Simple
public class Empleado : Persona{
public Empleado(string sNombre, int iEdad):base(sNombre, iEdad){}
override public string Tipo(){
return "Empleado";
}
}
class App{
//Aplicación
public static void Main(){
Persona Mexicano = new Persona("Gerado Ángeles Nava", 33);
Console.WriteLine("Mexicano.sNombre : " + Mexicano.sNombre);
Console.WriteLine("Mexicano.iEdad : " + Mexicano.iEdad);
Console.WriteLine("Mexicano.Tipo : " + Mexicano.Tipo());
7
aProgramadores[1] = new Empleado("Eric S. Raymond", 60);
for(int i = 0; i < aProgramadores.Length; i++){
Console.WriteLine("aProgramadores["+i+"].sNombre : " +
aProgramadores[i].sNombre);
Console.WriteLine("aProgramadores[" + i + "].iEdad : " +
aProgramadores[i].iEdad);
Console.WriteLine("aProgramadores[" + i + "].Tipo : " +
aProgramadores[i].Tipo());
}
}
}
Cuando una función es declarada con la palabra reservada override significa que es la
misma función que fue declarada en la clase base, si la palabra reservada override se
omite el compilador podría asumir que la función no está relacionada a la función de la
clase base y no despacha la función virtual (el compilador podría sugerir omitir
override o agregar new) .
Cuando existe una función virtual el programador puede pasar una referencia a la
clase abstracta aunque la clase derivada y el compilador podrían escribir código para
invocar la versión apropiada de la función en tiempo de ejecución.
Por ejemplo, el objeto base object tiene una función virtual llamada ToString() que
convierte un objeto a string. Si se invoca la función ToString() en un objeto que que no
la tiene como versión propia, la versión de la función que es parte de la clase object
podría ser invocada.
Encapsulación y Visibilidad
Encapsulación (también llamada information hiding), habilidad de un objeto para
ocultar sus datos internos o parte interna de sus usuarios y provee una interface que
hace las partes importantes del objeto accesible programaticamente.
La encapsulación provee los límites entre una interfaz externa y los detalles de su
implementación interna.
Al diseñar objetos el programador decide que objetos son visibles al usuario y que es
privado dentro del objeto, los detalles que no son visibles al usuario son señalados
para ser encapsulados en la clase.
Razones para encapsular y ocultar:
Abstracción
Una Abstracción se refiere a como un problema dado es representado en el espacio
de programa.
8
Como desarrollador de clases es necesario pensar en terminos de hacer el mejor
diseño de abstracción para los clientes de clase y permitirles enfocarse a la tarea que
deben realizar y no escudriñar o indagar en los detalles de como funciona la clase,
también es necesario determinar cuales de los miembros de la clase deberían ser
accesibles publicamente.
Los beneficios de una buena abstracción, es diseñarla de manera tal que las
modificaciones son minimas, si se conoce bien el problema a resolver facilita
determinar que métodos necesitara el usuario, también será un sistema fácil de
entender y mantener.
La interfaz de clase es la implementación de la abstracción.
Plataforma .NET
La solución Microsoft .NET comprende cuatro componentes fundamentales:
9
Proveer un ambiente de ejecución de código que garantice la ejecución
de código seguro, incluyendo el código creado por un desconocido o un
tercero semiconfiable.
Proveer un ambiente de ejecución de código que elimine los problemas
de desempeño de ambientes de scripts o interpretes.
Hacer que la experiencia del desarrollador sea consistente a través de
una amplia variedad de aplicaciones, tal como aplicaciones basadas en
Windows y aplicaciones basadas en Web.
Construir toda la comunicación sobre estándares industriales para
asegurar que el código basado en la Plataforma .NET pueda integrarse
con cualquier otro código.
10
Características del CLR
EL CLR no sólo soporta el compilador de C#, también el de Visual Basic y C++, el
código que generan estos compiladores para ser soportado por CLR es llamado
managed code.
Algunos de los beneficios que las aplicaciones obtienen del CLR son:
El CLR provee los beneficios anteriores, el compilador debe emitir los metadatos en
el managed code. Los metadatos describen los tipos en el código y son
empaquetados en el código ejecutable.
El CLR administra la memoria, ejecución de hilos, ejecución de código, verificación de
código seguro, compilación y otros servicios. Estas características son intrínsecas a la
administración de código que corre sobre el CLR.
La seguridad y administración de componentes depende de un número de factores
que se incluyen en su origen como Internet red corporativa, computadora local, es
decir, quizá o quizá no están disponibles para desempeñar operaciones de acceso a
archivos, acceso a registros o funciones sensitivas, aún si comienzan a utilizarse en el
misma aplicación activa.
El runtime forza el acceso a código seguro, no es posible acceder a datos personales,
sistema de archivos o red.
El runtime también forza la robustez del código implementando una infraestrucutra
estricta de verificación de código llamado Common Type System (CTS), el cual
asegura que toda la administración de código se describe así misma. La variedad de
compiladores Microsoft y de terceros genera código administrado que conforma el
CTS, es decir, que la administración de código puede consumir otros tipos e
instancias administradas, mientras que se forza estrictamente la fidelidad de tipo y
seguridad de tipo.
La administración del ambiente del runtime elimina cuestiones de software comunes,
liberando por ejemplo recursos que ya no son utilizados.
El runtime también acelera la productividad del desarrollador, no importa el lenguaje
que un programador utilice, puede utilizar las ventajas del runtime, biblioteca de
clases, y componentes escritos por otros programadores, cualquier compilador que
utilice el runtime puede hacer lo mismo,
La interoperabilidad entre código administrado y no administrado permiten a los
desarrolladores continuar utilizando componentes COM y DLLs.
El runtime está diseñado para incrementar el desempeño, através del CLR que provee
muchos servicios estándar, el código administrado nunca es interpretado, una
característica llamada just-in-time (JIT) permite compilar todo el código administrado
para correr en el lenguaje nativo de la máquina del sistema o de cualquiera que se
este ejecutando. El administrador de memoria elimina las posibilidades de
fragmentación de memoria e incrementa la referencia de localidad de memoria para
impulsar el incremento del desempeño.
11
El runtime soporta aplicaciones del lado del servidor como Microsoft® SQL Server™ e
Internet Information Services (IIS), esta infraestructura permite utilizar codigo
administrado para escribir la lógica del negocio.
.NET Runtime Environment
El Lenguaje Común en Tiempo de Ejecución provee los servicios de ejecución básica.
Las clases base proveen tipos de datos básicos, clases colección y otras clases
generales. Las clases base son clases para tratar datos y XML. En la parte superior
de la arquitectura las clases exponen servicios web y tratramiento de la intefaz de
usuario. Una aplicación puede hacer invocaciones en cualquier nivel y utilizar clases
desde cualquier nivel.
Organización .NET Framework:
Servicios Web Interfaz de Usuario
Datos y XML
Clases Base
12
Seguridad, el entorno del runtime .NET está diseñado para ser un entorno
seguro. El runtime .NET es un entorno administrado o controlado, lo cual
significa que el runtime administra la memoria por el programador a
través del recolector de basura.
Ensamblaje
En el runtime .NET el mecanismo de empaquetado es el ensamble (assembly),
cuando el código es compilado por uno de los compiladores .NET, es convertido a una
forma intermedia conocida como IL.
El ensamble contiene todos los IL, metadatos y otros archivos requeridos para que un
paquete se ejecute en un paquete completo.
Cada ensamble contiene un manifiesto que enumera todos los archivos que están
contenidos en el ensamble, controla que tipos y recursos son expuestos fuera del
ensamble y relaciona las referencias de esos tipos y recursos a los archivos que
contienen los tipos y recursos.
El manifiesto también lista otros ensambles que dependen de un ensamble.
Los ensambles se contienen a sí mismo, existe suficiente información en el ensamble
para ser auto-descrito.
Cuando se define un ensamble, el ensamble puede ser contenido en un solo archivo o
puede ser dividido entre varios archivos. Utilizando varios archivos podría hacer
posible un escenario donde las secciones del ensamble sean descargadas sólo como
se necesiten.
Interoperabilidad de Lenguaje
Una de las metas del runtime .NET es ser un lenguaje agnóstico, permitiendo que el
código sea utilizado y escrito desde cualquier lenguaje, no sólo las clases pueden ser
escritas en algún lenguaje .NET como VB.NET y ser invocadas desde otro lenguaje
.NET como C#, una clase que fué escrita en VB.NET puede ser utilizada como una
13
clase base escrita en C# y esa clase podría ser utilizada desde una clase VC++ o
JScript, es decir, no importaria en que clase sea escrita una clase.
Para hacer lo anterior posible existen algunos obstaculos como las propias
características del lenguaje, ya que un lenguaje no podría soportar ciertas cosas que
otro si las soporte, por ejemplo la sobrecarga de operadores.
Para que una clase sea utilizada desde un lenguaje .NET, la clase debe adherir la
Especificación Común de Lenguaje (Common Language Specification - CLS) la cual
describe que características pueden ser visibles en la interfaz pública de la clase, por
ejemplo el CLS prohibe exponer tipos de datos sin signo, porque no todos los
lenguajes pueden utilizarlos.
Atributos
El runtime .NET soporta atributos personalizables, los cuales son en cierto sentido un
lugar para colocar información descriptiva en los metadatos junto con un objeto y
entonces recuper después los datos. Los atributos proveen un mecanismo general
para hacer esto y son utilizados en exceso en todo el tiempo de ejecución para
almacenar información que modifica como el runtime utiliza las clases.
Los atributos son extensibles y permite a los programadores definir atributos y
utilizarlos.
Los atributos se especifican encerrandolos entre corchetes:
[Version("14/09/2005", Comentario="1.0.1.0")]
Los atributos son anotaciones que se colocan en elementos de código fuente, como
clases, miembros, parámetros, etc.
Los atributos puede ser utilizados para: cambiar el comportamiento del runtime,
proveer información acerca de un objeto, llevar información organizacional al
diseñador.
El atributo información es almacenado con los metadatos del elemento y pueden ser
facilmente recuperados en tiempo de ejecución a través de un proceso conocido como
reflection.
C# utiliza un Atributo Condicional para controlar cuando las funciones miembro son
invocadas.
Por convención los atributos se agregan al final del nombre de una clase, con la
finalidad de conocer cuales son clases atributo y cuales son clases normales. Todos
los atributos derivan de System.Attribute.
Procure que el atributo para el elemento sea específico, utilizando los identificadores
siguientes:
Identificador Descripción
assembly ensamble
module módulo
type clase o estructura
method método
property porpiedad
event evento
field campo
param parámetro
return valor de regreso
14
Los atributos que son aplicados a ensambles o módulos deben colocarse después de
cualquier cláusula using y antes de cualquier código.
Biblioteca de Clases de la Plataforma .NET
La Biblioteca de Clases de la Plataforma .NET es una colección de tipos reutilizables
integradas en el CLR.
Los tipos de la Plataforma .NET permiten llevar a cabo tareas de programación
comunes como manipulación de strings, colecciones de datos, conectividad a bases
de datos y acceso a archivos.
Es posible utilizar la Plataforma .NET para desarrollar los siguientes tipos de
aplicaciones y servicios:
Aplicaciones de consola
Windows Forms
Aplicaciones ASP.NET
Servicios Web XML
Servicios Windows
Requerimientos de Software
Todo lo que se necesita para desarrollar en C# es el Kit de desarrollo (SDK), del cual
solo se utilizará el CLR y el compilador de C#.
Lenguaje Intermedio y Metadatos
Microsoft desarrollo un lenguaje parecido al lenguaje ensamblador llamado Microsoft
Intermediate Language (MSIL).
Para compilar aplicaciones .NET, los compiladores toman el código fuente como
entrada y producen MSIL como salida.
MSIL en sí es un lenguaje completo con el cual es posible escribir aplicaciones.
El managed code generado por el compilador C# no es código nativo porque es un
código de Lenguaje Intermedio (IL). Este código IL se convierte en la entrada para la
administración del proceso de ejecución del CLR. La ventaja final del código IL es que
el CPU es independiente, lo cual significa que se necesita un compilador en la
máquina destino para cambiar el código IL en el código nativo.
El IL es generado por el compilador, pero no es lo único que se provee para el
runtime, el compilador también genera metadatos acerca del código, los cuales dicen
más al runtime del código, por ejemplo la definición de cada tipo. Los metadatos son
bibliotecas de tipo, entrada de datos al registry, etc. Los metadatos son
empaquetados directamente con el código ejecutable y no en localidades separadas.
El IL y los metadatos son colocados en los archivos que extienden el formato PE
(Portable Executable) utilizado para archivos .exe y .dll, cuando se carga el archivo PE
el runtime coloca y extrae los metadatos y el IL de estos.
Cuando se compila una aplicación C# o cualquier aplicación escrita en un CLS, la
aplicación es compilada dentro del MSIL, además se compila dentro de las
instrucciones nativas de CPU cuando la aplicación es ejecutada por vez primera por el
CLR.
El proceso es el siguiente:
15
El código fuente es compilado usando el compilador de C# (csc.exe)
dentro de un EXE.
El compilador C# produce como salida el código MSIL y un manifiesto en
una parte de sólo lectura del EXE que tiene un encabezado estándar PE
(Win32-Portable Executable).
JITers
El managed code generado por C# es el código IL, aunque el código IL es
empaquetado en un formato de archivo PE válido, no es posible ejecutarlo sin
convertirlo a un managed code nativo.
Cuando un tipo es cargado, el laoder crea y agrega un stub (pieza pequeña) a cada
método del tipo, así cuando el método es invocado por vez primera, el stub pasa el
control al JIT.
El JIT compila el IL a código nativo y cambia el stub para que apunte al código nativo
que está en cache, así las subsecuentes invocaciones podrían ejecutar el código
nativo.
El CLR incluye tres diferentes JITers que pueden ser usados para convertir MSIL en
código nativo, dependiendo de las circunstancias:
16
La generación de código en tiempo de instalación compilará un ensamble
completo dentro de un código binario de CPU-especifico, tal como lo
hace el compilador C++. Un ensamble el código empaquetado que es
enviado al compilador. La compilación se hace en tiempo de instalación,
cuando el usuario final es menos probable para notificar que el ensamble
esta siendo compilado-JIT.
La ventaja de la generación de código en tiempo de instalación, es que
permite compilar el ensamble completo justo una vez antes de ser
ejecutado. Al ser compilado el ensamble entero no hay preocupación
referente al desempeño intermitente cada vez que un método en el
código es ejecutado por primera vez.
Al usar esta utilidad depende del tamaño del sistema y del ambiente de
distribución.
EconoJIT, realiza una conversión muy veloz del IL a managed code nativo
17
VOS Type System, provee un sistema de tipos que intenta soportar la
implementación completa de una rango amplio de lenguajes de
programación.
Metadata, describe y hace referencia a los tipos definidos por el VOS.
Common Language Specification - CLS, define el subconjunto de tipos
del VOS. Si una biblioteca de clases es soportada por las reglas del CLS,
garantiza que la biblioteca de clases pueda ser utilizada por los demás
lenguajes que implementen el CLS.
Virtual Execution System - VES, es responsable de la carga y ejecución
de los programas que fueron escritos por el CLR.
18
Existe un proceso llamado reflection donde el código en tiempo de ejecución puede
consultar los metadatos para encontrar que objetos están disponibles y que funciones
y campos están presentes en la clase. La reflection está disponible para usuarios
finales para determinar como son los objetos, búsqueda de atributos o ejecutar
métodos en los que los nombres no son conocidos hasta el tiempo de ejecución.
Los metadatos son utilizados para varias tareas:
19
escribe la DLL no adopta .NET será necesario invocar esa DLL desde una
aplicación .NET.
Código administrado usando componentes COM, es posible lograr esto
creando un wrapper .NET para el componente COM, así que el cliente
administrado trabaja con clases .NET
Código no administrado usando servicios .NET, cuando se desea acceder
a .NET desde código no administrado.
20
un mapa del lenguaje fuente de la construcción de direcciones en el flujo
de instrucciones y un mapa de las direcciones de las localidades en el
apilado de frames.
Administración de Hilos, el VES proprorciona este servicio al código
administrado.
Tipos de Datos
C# soporta el conjunto de tipos de datos usual, para cada tipo de dato que C#
soporta, existe una correspondencia tipo de lenguaje común en tiempo de
ejecución .NET subyacente.
Todos los tipos runtime pueden encontrarse en el namespace System del lenguaje
común en tiempo de ejecución .NET.
Byte Tipo
Tipo Descripción
s runtime
byte 1 Byte Unsigned byte
sbyte 1 SByte Signed byte
short 2 Int16 Signed short
ushort 2 UInt16 Unsigned short
int 4 Int32 Signed integer
uint 4 UInt32 Unsigned int
long 8 Int64 Signed big integer
ulong 8 UInt64 Unsigned big integer
float 4 Single Floating point number
double 8 double Double-precision floating point number
decimal 8 Decimal Fixed-precision number
string String Unicode string
char Char Unicode character
bool Boolean Boolean value
Los tipos de datos son separados en value types y reference types. Los value types
son asignados en estructuras de pilas o en línea. Los reference types son asignados
al aglomerado.
Las referencias y tipos de valores son derivados de la última clase base objet, de esta
manera en caso de que un tipo de valor necesite actuar como un object una envoltura
hace que el tipo de valor parezca una referencia asignandolo al aglomerado, y los
tipos de valores son copiados a estos. La envoltura es marcada por lo que el sistema
conoce que contiene por ejemplo int, a este proceso se le conoce como boxing y el
proceso de reversa se le conoce como unboxing
La palabra reservada class es empleada para declarar un tipo referencia (heap
allocated), y la palabra reservada struct es empleada para declarar un tipo valor, una
estructura es utilizada para objetos ligeros que necesitan actuar como tipos built-in, las
clases son utilizadas en cualquier otro caso.
21
Por ejemplo un tipo int es un valor tipo y un tipo string es un tipo referencias, esto
trabajaria así:
int i = 2005;
string s = "Septiembre";
i 2005
------------
s o----- Septiembre
>
Constantes y Campos Solo Lectura
En C# los valores pueden ser definidos como constantes y para que un valor sea
constante su valor debe ser algo que pueda ser escrito como una constante.
public const string sDominio = "informatique.com.mx";
La restricción de tipos constantes es que son conocibles en tiempo de compilación, en
vez de ello es posible utilizar el modificador readonly el cual está diseñado para
aquellas situaciones en donde las constantes tienen restricción.
Aplicando el modificador readonly un valor puede ser establecido en el constructor o
en una inicialización pero no puede ser modificado después.
Ejemplo Hello World!
El código C# puede ser escrito en cualquier editor, también puede escribirse con
Visual Studio 7.
El código C# debe almacenarse en un archivo con extensión .cs
Para compilar el código C# es necesario tener instalado la Plataforma .NET que
incluye el compilador C#, puede buscar el ejecutable en la ruta:
C:WINDOWSMicrosoft.NETFrameworkv1.1.4322csc.exe
Asegurese de tener esta ruta en el path para poder ejecutar el compilador desde
cualquier ubicación.
Para compilar su archivo .cs es necesario abrir la consola (DOS) y escribir el comando
cs seguido del nombre de su archivo por ejemplo:
cd helloworld.cs
La salida exitosa de la compilación podría ser así:
22
El método Main debe estar contenido en la clase y escrito con la primer letra en
mayúscula. El tipo de este método puede ser void o int. También este método puede
especificar argumentos:
public static void Main(string[] args)
System es el ámbito del namespace en el cual el objeto Console está contenido.
Es posible importar el namespace en las aplicaciones indicandolo al inicio del código
con la palabra reservada using que es una directiva para el namespace System.
Existen más namespaces en la Plataforma .NET
using System;
Ejemplo Args
Examinemos el siguiente ejemplo, el cual recibe los argumentos con los que el
componente fue invocado:
Ejemplo de Args con for:
using System;
class Args{
public static void Main(string[] args){
Console.WriteLine("Argumentos : {0}", args.Length);
for(int itera = 0; itera < args.Length; itera++)
Console.WriteLine("Argumento {0} : {1}", itera,
args[itera]);
}
}
Ejemplo de Args con foreach:
using System;
class App{
public static void Main(string[] args){
foreach(string input in args){
Console.WriteLine(input);
}
}
}
23
public static void Main(string[] args), La clase Args contiene una función
o método Main(), el cual sirve como punto de partida de la ejecución del
componente, este método puede o no ser declarado con argumentos, en
este caso es fundamental declarlos porque deseamos precisamente leer y
escribir estos argumentos proporcionados al invocar el componente.
Salida : Argumentos : 0
24
Argumento 2 : www
Argumento 3 : .
Argumento 4 : informatique
Argumento 5 : .
Argumento 6 : com
Argumento 7 : .
Argumento 8 : mx
Ejemplo Input/Output
Es posible leer datos de la consola utilizando el método ReadLine y es posible
mostrarlos utilizando el método Write o WriteLine del objeto Console:
using System;
class inOut{
public static void Main(){
Console.Write("Fecha de Nacimiento: ");
String strFecNac = Console.ReadLine();
Console.WriteLine("FecNac = " + strFecNac);
}
}
Note que importar la directiva System hace posible omitir escribir el namespace, de
esta forma sólo es necesario escribir el nombre del objeto seguido del nombre del
método.
Ejemplo String Format
Es posible dar formato a la salida de datos a un tipo string, utilizando la sintaxis
{número} donde número es reemplazado por la variable correspondiente:
using System;
class strFormat{
public static void Main(){
Console.Write("Nombre: ");
String strNombre = Console.ReadLine();
Console.Write("Edad: ");
String strEdad = Console.ReadLine();
Console.Write("Teléfono: ");
String strTel = Console.ReadLine();
Console.Write("Dirección: ");
String strDir = Console.ReadLine();
25
using System;
class App{
public static void Main(){
Console.WriteLine("Hello world!");
}
}
El ejemplo anterior define a la función Main como void lo cual indica que no regresa un
valor, pero es posible indicar que si regrese un valor escribiendo el tipo de la función
como int por ejemplo, que indica que regresa un valor de tipo entero:
using System;
class App{
public static int Main(){
Console.WriteLine("Hello world!");
return(1);
}
}
26
Preprocesamiento
Lo más importante en este punto es que en C# no existe el preprocesador, el motivo
por el cual no existe es para simplificar la estructura de compilación además de que
no hay necesidad de escribir un archivo de encabezados por separado y mantener en
sincronia la implementación, cuando los archivos fuente C# son compilados el orden
de la compilación de archivos individuales no es importante y es equivalente a un
archivo de gran tamaño.
Un identificador es el nombre que es usado para algún elemento de un programa
como una variable o función y deben tener una letra o guión bajo como primer
caracter.
C# soporta las siguientes directivas de preprocesamiento:
Tamaño Valor
Define un identificador, los identificadores también pueden
#define
ser definidos via la línea de comando
#undef Elimina la definición de un identificador
El código de la sección es compilado si la expresión es
#if
verdadera
Constructor Else-if, si la directiva anterior no se cumplio y si
#elif la expresión es verdadera el código de la sección es
compilado
Si la directiva anterior no se cumplio el código de la sección
#else
es compilado
#endif Marca el final de la sección
Los identificadores deben preceder a cualquier código real.
Es posible utilizar los siguientes operadores en expresiones preprocesador:
!
==
!=
&&
||
//, que se utiliza para comentar una línea, es decir, todo lo que sigue a //
es ignorado.
/* */, que se utiliza para comentar segmentos de código.
Value Types
Una variable contiene un valor de cierto tipo, C# forza a inicializar las variables antes
de utilizarlas en una operación.
27
Cuando se asigna un valor a un value type el valor actual es copiado a diferencia de
los reference types lo que se copia es la referencia actual no el valor.
C# agrupa los value types en:
Tipos Simples
Tipos Estructura
Tipos Enumeración
Tipos Simples
Los Tipos Simples de C# comparten características como las de alias con los tipos
de sistema de .NET, expresiones constantes consisten de Tipos Simples evaluados
solamente en tiempo de compilación no en tiempo de ejecución y los Tipos Simples
pueden ser inicializados con literales.
Los Tipos Simples de C# se agrupan en:
Integral
Bool
28
En C# el valor verdadero no es posible representarlo con algún valor
diferente de cero, no hay una conversión entre el tipo integral a bool que
force esta conversión.
Char
Floating Point
Nota: Si una expresión un valor es de tipo Floating Point todos los otros
valores son convertidos a tipos Floating Point antes de realizar el cálculo.
Decimal
29
comprenden los rangos 1.0x10-28 a 7.9x1028 con una precisión de 28 a 29
dígitos.
No hay conversiones implicitas entre decimales y dobles, se podría
generar un overflow o perder precisión, por lo que es necesario una
conversión explícita con un cast.
Cuando se define una variable y se le asigna un valor se utiliza el sufijo m
para denotar que es un valor decimal:
decimal decDecimal = 1.0m
Si se omite la letra m la variable podría ser tratada como double por el
compilador antes de ser asignado.
Tipos Estructura
Un tipo struct puede declarar constructores, constantes, campos, métodos,
propiedades, índices, operadores y tipos anidados. Las estructuras actuan de manera
similar a una clase y con mayores restricciones, por ejemplo no pueden heredar de
cualquier otro tipo, ni tampoco otra clase puede heredar de una estructura.
Las estructuras deberían ser utilizadas sólo para tipos que son realmente una pieza
de datos.
La diferencia entre struct y class en C# es que struct es un value type y class es una
reference type.
La principal idea de utilizar struct es para crear objetos ligeros como Point, FileInfo,
etc., de esta manera se conserva memoria porque no hay referencias adicionales que
son creadas como se necesiten por objetos clase.
using System;
struct IP{
public byte b1,b2,b3,b4;
}
class ip{
public static void Main(){
IP miIP;
miIP.b1 = 192;
miIP.b2 = 168;
miIP.b3 = 1;
miIP.b4 = 101;
Console.Write("{0}.{1}.", miIP.b1,miIP.b2);
Console.Write("{0}.{1}", miIP.b3,miIP.b4);
}
}
Tipos Enumeración
Es posible establecer un conjunto de constantes relacionadas, por default los
elementos de una enumeración son de tipo int donde el primer elemento tiene el valor
0 y cada elemento subsecuente se incrementa en 1. Es posible establecer el valor del
primer elemento simplemente asignando a este el valor deseado, así como es posible
especificar el tipo de dato de los valores contenidos especificandolo después del
nombre de la enumeración aunque están restringidos a los tipos: long, int, short y
byte.
Sintaxis:
enum NombreEnumeraciones{
constante1,
constante2,
30
constante3,
.
.
constanteN
}
Ejemplo:
using System;
public class Enumeracion {
enum enumDias {Sabado, Domingo, Lunes, Martes, Miércoles,
Jueves, Viernes };
enum enumMeses
{Enero,Febrero,Marzo,Abril,Mayo,Junio,Julio,Agosto,Septiembre,_
Octubre,Noviembre,Diciembre};
enum enumFecha {Dia = 21, Mes = 9, Año = 1971};
Console.WriteLine();
Console.WriteLine("Los meses del año, y su valor correspondiente en la
enumeración es:");
31
Los reference types que C# utiliza son:
Tipo Objeto
Tipo Clase
Interfaces
Delegados
Tipo string
Arreglos
Tipo Objeto
El Tipo Objeto es la Clase Base de todos los tipos, al ser la clase base de todos los
tipos es posible asignarle valores de cualquier tipo.
El Tipo Objeto es utilizado cuando el value type esta boxed, es decir, que está
disponible como un objeto.
Tipo Clase
El Tipo Clase contiene datos miembro, funciones miembro y tipos anidados. Los
datos miembro son constantes, campos y eventos. Las funciones miembro incluyen
métodos, propiedades, índices, operadores, constructores y destructores.
Interfaces
Una interface declara un tipo referencia que tiene sólo miembros abstractos. Sólo
existe la firma pero no tiene implementado todo el código, por lo que no es posible
instanciar una interface, sólo un objeto que deriva de la interface. Para crear una
interface se emplea la palabra reservada interface:
using System;
interface Iuno{
void AccionUno();
}
class App{
public static void Main(){
Implementa I = new Implementa();
I.AccionUno();
}
}
Es posible definir métodos, propiedades e índices en una interface, cuando se define
una Clase es posible derivar de múltiples interfaces, mientras que al definir una
interface sólo es posible derivar de sólo una clase.
Las interfaces están estrechamente relacionadas a clases abstractas, se parecen a
una clase abstracta que tiene todos sus miembros abstractos.
Cuando un objeto implementa una interface, una referencia a la interface puede ser
obtenida por un cast de la interface.
Una clase puede implementar más de una interface.
32
class NombreClase : InterfaceA, InterfaceB{}
Existe una técnica llamada Implementación de la Interface Explícita y se utiliza para
resolver colisiones con nombres de métodos iguales entre interfaces:
using System;
interface Iuno{
void AccionUno();
}
interface Idos{
void AccionUno();
}
class App{
public static void Main(){
Implementa I = new Implementa();
Iuno uno = (Iuno) I;
uno.AccionUno();
Idos dos = (Idos) I;
dos.AccionUno();
}
}
Es posible ocultar al usuario de la clase la implementación de una interfaz, así como
también es posible crear interfaces basadas de otras interfaces.
Delegados
Los delegados son similares a las interfaces, especifican un contratado entre un caller
y un implementer (implementador).
Un delegado especifica la forma de una función en vez de especificar toda una
interface.
Las interfaces se crean en tiempo de compilación y los delegados son creados en
tiempo de ejecución.
Un delegado encapsula un método con cierta firma, básicamente un delegado es un
type-safe y secure version. Un delegado es una implementación de function pointers
orientada a objetos y son utilizados en muchas situaciones donde un componente
necesita volver a invocar el componente que lo esta usando.
Es posible encapsular métodos estáticos e instancias en una instancia delegado.
El principal uso de los delegados es con los eventos no con las clases.
La especificación del delegado determina la forma de la función y crea una instancia
del delegado, se usa la función que coincide con la forma.
Los delegados al ser de naturaleza dinámica se utilizan cuando el usuario desea
cambiar el comportamiento, por ejemplo si deseamos que una clase Ordenamiento
soporte diferentes métodos de ordenación, la ordenación podría ser controlada en
base a un delegado que defina la función de comparación.
33
Nota los delegados siempre son creados aún si no son usados, pero los delegados
podrían ser creados al vuelo si se reemplazan las funciones estáticas por
propiedades, entonces unicamente se crea el delegado solo si se utiliza la propiedad.
Tipo string
El Tipo string se utiliza para manipular datos string. La clase string deriva
directamente de object y no es posible derivarla.
Todos los strings en C# son instancias del tipo System.String en el CLR.
string es un alias para la clase predefinida System.String y su uso es muy sencillo:
string sWebSite = "http://www.informatique.com.mx";
Para acceder a un caracter, simplemente acceda a su índice:
sWebSite[11];
Es posible hacer un barrido de los caracteres que componen el string utilizando la
propiedad Length que poseen los arreglos y porque es posible acceder a estos
tratando al string como un arreglo:
using System;
class App{
public static void Main(){
string sWebSite = "http://www.informatique.com.mx";
Console.WriteLine("sWebSite contiene : " + sWebSite.Length + "
caracteres");
for(int iElemento = 0; iElemento < sWebSite.Length; iElemento++){
Console.WriteLine("Elemento " + iElemento + " : " +
sWebSite[iElemento]);
}
}
}
Es posible concatenar strings utilizando el operador +.
Si requiere comparar strings por igualdad utilice el operador de comparación ==
Aunque string es un reference type la comparación se realiza comparando los valores
no las referencias.
La clase String es un ejemplo de tipo inmutable, es decir, que los caracteres
contenidos en el string no puede ser modificados por los usuarios del string, todas las
operaciones que son realizadas por la clase String regresan una versión modificada
del string en vez de modificar la instancia en la cual se invoco el método.
La clase String soporta los sisguientes métodos de comparación y búsqueda:
Método Descripción
Compare() Compara dos strings.
Compara dos regiones de strings utilizando una
CompareOrdinal()
comparación ordinal
CompareTo() Compara la instancia actual con otra instancia.
EndsWith() Determina cuando un substring existe al final de un string
Determina cuando un substring existe al principio de un
StartsWith()
string.
IndexOf() Regresa la posición de la primer ocurrencia de un substring
LastIndexOf() Regresa la posición de la última ocurrencia de un substring
Concat() Concatena dos o más strings u objetos, si se pasan objetos
34
la función ToString es invocada
Copia un número específico de caracteres de una ubicación
CopyTo()
del string dentro del arreglo
Regresa un nuevo string con un substring insertado en la
Insert()
ubicación específica
Une un arreglo de strings junto con un separador entre cada
Join()
elemento del arreglo
PadLeft() Alinea a la izquierda un string
PadRight() Alinea a la derecha un string
Remove() Elimina caracteres de un string
Reemplaza todas las instancias de un caracter con
Replace()
caracteres diferentes
Crea un arreglo de strings dividiendo un string en cualquier
Split()
ocurrencia de uno o más caracteres
Substring() Extrae un substring de un string
ToLower() regresa una versión de un string en minúsculas
ToUpper() regresa una versión de un string en mayúsculas
Trim() Elimina espacios en blanco de un string
TrimEnd() Elimina un string de caracteres al final de un string
TrimStart() Elimina un string de caracteres al inicio de un string
object.ToString(), convierte un objeto a una representación string. String.Format()
puede ser utilizada para crear un string basado en los valores de otro string.
La clase StringBuilder soporta las siguientes propiedades y métodos:
Propiedad Descripción
Recupera o establece el número de caracteres que
Capacity
StringBuilder puede contener
Índice StringBuilder utilizado para obtener o establecer un
[]
caracter en la posición específica
Length Recupera o establece la longitud
MaxCapacity Recupera la capacidad máxima del StringBuilder
Método Descripción
Append() Agrega la representación string de un objeto
Agrega la representación string de un objeto, utilizando un
AppendFormat()
formato específico para el objeto
Asegura que StringBuilder tiene suficiente espacio para un
EnsureCapacity()
número de caracteres específico
Inserta la representación string de un objeto específico en
Insert()
una posición específica
35
Remove() Elimina los caracteres específicos
Reemplaza todas las instancias de un caractes con un
Replace()
nuevo caracter
Arreglos
Un arreglo contiene variables a las cuales se accede a través de índices, todas las
variables contenidas en el arreglo son referidos como elementos los cuales deben ser
del mismo tipo, por lo que el tipo del arreglo.
Los arreglos en C# son referencias a objetos. Un arreglo value type no contiene
instancias boxed.
El valor inicial de un arreglo es null, un arreglo de objetos es creado utilizando new.
Cuando un arreglo es creado inicialmente contiene los valores por default para los
tipos que este contendrá.
Sintaxis:
tipo[] identificador;
Note que para definir un arreglo se utilizan los corchetes [] después del tipo del
arreglo.
Ejemplo:
string[] aPersonas;
Es posible inicializar un arreglo al momento de crearlo:
string[] asPersonas = new string[] {"Tim Berners-Lee","Brendan Eich","Dennis
Ritchie","James Gosling"};
Durante la inicialización es posible omitir new tipo[x] y el compilador podría determinar
el tamaño de almacenamiento para el arreglo del número de items en la lista de
inicialización:
string[] asPersonas = {"Tim Berners-Lee","Brendan Eich","Dennis Ritchie","James
Gosling"};
Cada elemento de un arreglo de ints es un int con el valor 0:
int[] aiNumeros = new int[5];
Cada elemento de un arreglo de strings es un string con el valor null:
string[] asNombres = new string[5];
La dimensión del arreglo puede ser simple o multidimensional, donde cada dimensión
comienza con el índice 0, si requiere hacer un barrido de todos los elementos del
arreglo, comience a partir del índice 0 hasta la longitud del arreglo menos uno
(nombreArreglo.Length - 1 o nIndice < nombreArreglo.Length);
using System;
class Arreglo{
static public void Main(){
string[] aNombres = {"Hugo","Paco","Luis"};
Console.WriteLine(aNombres[0]);
Console.WriteLine(aNombres[1]);
Console.WriteLine(aNombres[2]);
}
}
Otra alternativa al ejemplo anterior es:
int[] aiNumeros = new int[3];
aiNumeros[0] = 4;
aiNumeros[1] = 33;
aiNumeros[2] = 43;
36
Al declarar el arreglo especifique solamente el número de elementos que este
contendrá. utilice la palabre reservada new seguido del tipo y entre corchetes el
número de elementos que contendrá.
Es posible ordernar y buscar los elementos de un arreglo gracias a que los arreglos en
C# están basados en el tipo System.Array del runtime NET. El método Sort() podría
ordenar los elementos de un arreglo, los métodos IndexOf() y LastIndexOf() y
BinarySearch podrían buscar elementos en un arreglo. El método Reverse podría
invertir el orden de los elementos de un arreglo.
Arreglos Multidimensionales
Los Arreglos Multidimensionales son aquellos que tienen más de una dimensión.
Sintaxis:
tipo[,] identificador;
Ejemplo:
string[,] asBidimensional = new string[2, 4];
Para definir un arreglo multidimensional, simplemente defina arreglos como elementos
del arreglo:
string[,] asMulti = {{"a","1"},{"b","2"},{"c","3"}};
Ejemplo:
using System;
class App{
public static void Main(){
asBidimensional[0,0] = "00";
asBidimensional[0,1] = "01";
asBidimensional[1,0] = "10";
asBidimensional[1,1] = "11";
asBidimensional[2,0] = "20";
asBidimensional[2,1] = "21";
asBidimensional[3,0] = "30";
asBidimensional[3,1] = "31";
37
for(int i = 0; i < iRenglon; i++){
for(int j = 0; j < iColumna; j++){
Console.WriteLine("Dimensión " + i + " elemento " + j + " : " +
aiBidimensional[i,j]);
}
}
Console.WriteLine("Longitud del arreglo aiBidimensional : " +
aiBidimensional.Length);
}
}
Arreglo de Arreglos
Un Arreglo de Arreglos es también conocido como jagged array porque no tiene que
ser rígido.
Por ejemplo:
int[][] aiIDs = new int[3][];
Este ejemplo define un arreglo de arreglo de tipo int donde su dimensión es 3
elementos, donde estos elementos son arreglos.
Arreglos de Objetos
Un arreglo de objetos es creado utilizando new.
Es posible declarar y manipular arreglos de objetos de la siguiente manera:
using System;
public class Persona{
//Propiedades
public string sNombre;
public int iEdad;
//Constructor
public Persona(string sNombre, int iEdad){
this.sNombre = sNombre;
this.iEdad = iEdad;
}
//Métodos
public string Tipo(){
return "Persona";
}
}
//Herencia Simple
public class Empleado : Persona{
public Empleado(string sNombre, int iEdad):base(sNombre, iEdad){}
public new string Tipo(){
return "Empleado";
}
}
class App{
//Aplicación
public static void Main(){
Persona Mexicano = new Persona("Gerado Ángeles Nava", 33);
Console.WriteLine("Mexicano.sNombre : " + Mexicano.sNombre);
Console.WriteLine("Mexicano.iEdad : " + Mexicano.iEdad);
Console.WriteLine("Mexicano.Tipo : " + Mexicano.Tipo());
38
Console.WriteLine("--- Arreglo de Objetos ---");
39
class App{
public static void Main(){
double[] aNumeros = {8.7, 6.9, -6.5, 4.2, -102.09, 1.9, 0.01, -0.002,
99.87};
Array.Sort(aNumeros);
for(int elemento = 0; elemento < aNumeros.Length; elemento++)
Console.WriteLine("Elemento [" + elemento + "] = " +
aNumeros[elemento]);
}
}
Salida:
Elemento [0] = -102.09
Elemento [1] = -6.5
Elemento [2] = -0.002
Elemento [3] = 0.01
Elemento [4] = 1.9
Elemento [5] = 4.2
Elemento [6] = 6.9
Elemento [7] = 8.7
Elemento [8] = 99.87
Interface IComparable
La función sort no trabaja con clases o estructuras porque no conoce su orden, pero si
desea ordenarlas utilice la interface IComparable, por ejemplo una ordenación
utilizando una propiedad numérica:
using System;
class App{
public static void Main(){
Lenguaje[] aLenguaje = new Lenguaje[5];
aLenguaje[0] = new Lenguaje("C",3);
aLenguaje[1] = new Lenguaje("ActionScript",5);
aLenguaje[2] = new Lenguaje("JavaScript",2);
aLenguaje[3] = new Lenguaje("Java",8);
40
aLenguaje[4] = new Lenguaje("PHP",1);
Array.Sort(aLenguaje);
foreach(Lenguaje len in aLenguaje)
Console.WriteLine(len);
}
}
Salida:
PHP 1
JavaScript 2
C3
ActionScript 5
Java 8
Interface IComparer
Es posible definir múltiples tipos de ordenamientos gracias a que el diseño del
Framework provee esta capacidad.
Cada clase sólo puede implementar una interface a la vez, por lo que solamente se
podría permitir un tipo de ordenamiento, entonces se requiere una clase separada
para cada tipo de ordenamiento que implementen IComparer y podría también
implementar la función Comapare():
using System;
using System.Collections;
public class Lenguaje : IComparable{
string nombre;
int id;
public Lenguaje(string nombre, int id){
this.nombre = nombre;
this.id = id;
}
int IComparable.CompareTo(object o){
Lenguaje lenguajeB = (Lenguaje)o;
if(this.id > lenguajeB.id){return 1;}
if(this.id < lenguajeB.id){
return -1;
}else{
return 0;
}
}
public override string ToString(){
return nombre + " " + id;
}
public class OrdenaNombres : IComparer{
public int Compare(object oA, object oB){
Lenguaje lenA = (Lenguaje)oA;
Lenguaje lenB = (Lenguaje)oB;
return
String.Compare(lenA.nombre,lenB.nombre);
}
}
}
class App{
public static void Main(){
Lenguaje[] aLenguaje = new Lenguaje[5];
41
aLenguaje[0] = new Lenguaje("C",3);
aLenguaje[1] = new Lenguaje("ActionScript",5);
aLenguaje[2] = new Lenguaje("JavaScript",2);
aLenguaje[3] = new Lenguaje("Java",8);
aLenguaje[4] = new Lenguaje("PHP",1);
42
public int Compare(object oA, object oB){
Lenguaje lenA = (Lenguaje)oA;
Lenguaje lenB = (Lenguaje)oB;
return
String.Compare(lenA.nombre,lenB.nombre);
}
}
}
class App{
public static void Main(){
Lenguaje[] aLenguaje = new Lenguaje[5];
aLenguaje[0] = new Lenguaje("C",3);
aLenguaje[1] = new Lenguaje("ActionScript",5);
aLenguaje[2] = new Lenguaje("JavaScript",2);
aLenguaje[3] = new Lenguaje("Java",8);
aLenguaje[4] = new Lenguaje("PHP",1);
Array.Sort(aLenguaje, Lenguaje.Ordena);
ActionScript 5
C3
Java 8
JavaScript 2
PHP 1
Expresiones Regulares
Las Expresiones Regulares proveen un método muy poderoso para hacer funciones
de busquedas y reemplazamiento.
Operador as
El Operador as checa el tipo del operador izquierdo y si puede ser convertido
explicitamente a el operador derecho, se obtiene como resultado el objeto convertido
a el operador derecho, si no puede ser convertido la operación falla y regresa null.
Este operador sólo puede se utilizado con clases.
Secuencias de Escape
Secuencia de Escape Descripción
' Comilla simple
" Comilla doble
\ Diagonal invertida
Nulo
a Alert
b Retroceso
f Form Feed
43
n Nueva línea
r Retorno de carro
t Tabulador
v Tabulador vertical
Boxing
Boxing es un mecanismo que crea una liga entre los tipos de valores y las tipos de
referencia permitiendo a un tipo de valor ser convertido a un tipo objeto y viceversa.
using System;
class App{
public static void Main(){
int iEdad = 33;
object oNumero = iEdad; //Box
int iNumero = (int)oNumero; //Unbox
//cast necesario porque oNumero podría contener
cualquier tipo de objeto
}
}
Nota, durante la conversión unboxing el tipo debe coincidir exactamente, un valor de
tipo boxed no puede ser unboxed (convertido) a un tipo compatible. Si requiere
obtener otro tipo de valor diferente al que contiene el boxed, en ese caso primero
obtenga el valor correcto y después realice un cast al tipo que requiera:
(valorRequerido)
valorRequerido vr = (valorRequerido)(valorBoxed)objeto;
Otra forma de definir el concepto boxing es que este mecanismo permite que los
value types parezcan o tengan la apariencia de reference types.
Conversiones Boxing
Boxing un valor se refiere a la conversión implícita de cualquier tipo de valor al tipo
objeto. Cuando un tipo de valor es boxed se asigna espacio a una instancia de objeto
y el valor del value type es copiado al nuevo objeto.
Observe las siguientes líneas:
int iNumero = 2012;
object oNumero = iNumero; //invocación implicita a una
operación boxing
oNumero = 2005;
Console.WriteLine(iNumero);
Console.WriteLine(oNumero);
}
44
}
Al ejecutar el código notará que el valor de oNumero es 2005 y el valor de iNumero no
cambio permanece en 2012.
Conversiones Unboxing
Al contrario que Boxing, Unboxing es un mecanismo de una operación explícita, por
lo que es necesario indicar al compilador que tipo de valor deseamos extraer de un
objeto, al realizar la operación Unboxing C# checa que el value type que se requiere
este almacenado en la instancia del objeto, si la verificación es exitosa el valor es
Unboxing.
Suponga que tiene una variable de tipo int y asigna el valor de esta variable int a un
objeto, después declara una variable de tipo double y aplica un cast (double) al objeto
para asignar su valor a la variable double, el objeto contiene sólo un valor int y no
puede ser asignado a la variable double porque el CLR dispara una excepción
(System.InvalidCastException):
int iNumero = 2012;
object oNumero = iNumero; //invocación implicita a una operación
boxing
double dNumero = (double)oNumero; //invocacion explícita (cast)
//CLR dispara la excepción System.InvalidCastException
Constructores y Destructores
Antes de acceder a los métodos o propiedades de una clase, primero se ejecuta el
constructor de la clase el cual contiene código de inicialización, si no se escribe un
constructor para la clase el compilador provee automáticamente un constructor
default.
En el runtime .NET el programador no puede controlar la destrucción de objetos.
Un constructor puede invocar un constructor del tipo base utilizando la sintaxis base.
Los constructores son invocados invocados automaticamente sólo cuando una
instancia de un objeto es creada con new.
class NombreClase{
public NombreClase() : base(){} //Constructor que provee el
compilador
}
Las características de un constructor son:
45
class Vehiculo{
//Propiedades:
private int iRueda;
private int iPuerta;
private int iVentana;
private int iHelice;
private int iMotor;
private int iAsiento;
private string sTipo;//Aereo, anfibio, terrestre, espacial
//Constructor:
public Vehiculo(int Rueda, int Puerta, int Ventana, int Helice, _
int Motor, int Asiento, string Tipo){
iRueda = Rueda;
iPuerta = Puerta;
iVentana = Ventana;
iHelice = Helice;
iMotor = Motor;
iAsiento = Asiento;
sTipo = Tipo;
}
//Lectura/escritura de propiedades:
public int Ruedas{
get{return iRueda;}
set{iRueda = value;}
}
46
get{return sTipo;}
set{sTipo = value;}
}
//Aplicación:
class AplicConstructor{
public static void Main(){
Vehiculo MiAvion = new
Vehiculo(2,1,100,0,3,200,"Aereo");
Console.WriteLine("Ruedas : " + MiAvion.Ruedas);
Console.WriteLine("Puertas : " + MiAvion.Puertas);
Console.WriteLine("Ventanas : " + MiAvion.Ventanas);
Console.WriteLine("Helices : " + MiAvion.Helices);
Console.WriteLine("Motores : " + MiAvion.Motores);
Console.WriteLine("Asientos : " + MiAvion.Asientos);
Console.WriteLine("Tipo : " + MiAvion.Tipo);
}
}
En un sentido estricto en C# no se tienen destructores, pero el termino destructor se
refiere a la liberación de recursos.
Es posible escribir un método que libere recursos después de ser utilizados, pero
porque escribir un método para liberar recursos si existe un destructor:
public ~NombreClase(){
//liberar recursos
}
La razón por la cual se debería escribir un método adicional es por el recolector de
basura, el cual no es invocado inmediatamente después que las variables quedan
fuera de ámbito, sólo se invoca el recolector de basura en ciertos intervalos o
condiciones de memoria.
Lo que podría suceder es que los recursos se agoten antes de ser utilizados,
entonces es buena idea proveer un método explícito Release, el cual también puede
ser invocado por el destructor:
public void Release(){
//Liberar recursos
}
public ~NombreClase(){
Release();
}
La invocación del método Release en el destructor no es obligatoria, la colección
garbage de cualquier forma realiza la liberación de cualquier objeto, pero es una
buena práctica no olvidar liberar los recursos.
Constructor Estático
Un Constructor Estático podría ser invocado antes de ser creada la primer instancia
de un objeto, y es útil para configurar el trabajo que necesita hacerse una vez.
En el runtime .NET el usuario no tiene control sobre cuando el constructor estático es
invocado, ya que el runtime sólo garantiza que algo es invocado después del inicio del
programa y antes de ser creada la primer instancia de un objeto, lo que significa que
no puede ser determinada la instancia que es creada en el constructor estático.
Para declarar un constructor estático se utiliza el modificador static:
class NombreClase{
47
static NombreClase(){
.
.
}
}
Métodos
La mayor parte de la funcionalidad es implementada en los métodos, los métodos son
parte del Tipo (class), pero los métodos no son parte de la instancia (object).
Parámetros
De algún modo se deben pasar valores a un método y también se debe regresar el
resultado de un método, los valores son manipulados en:
Valores en Parámetros in
Se utilizan valores en parámetros para pasar una variable por valor a un método, la
variable del método es inicializada con una copia del valor del caller (quien realizó la
invocación).
using System;
public class Fecha{
public string Mayor(int iDiaA,int iMesA,int iAñoA,int iDiaB,int
iMesB,int iAñoB){
int iA = (iDiaA * 10000) + (iMesA + 100) + (iAñoA);
int iB = (iDiaB * 10000) + (iMesB + 100) + (iAñoB);
Console.WriteLine(iA + " > " + iB); //Test Line (Delete)
if(iA > iB){
return iDiaA + "/" + iMesA + "/" + iAñoA;
}else{
return iDiaB + "/" + iMesB + "/" + iAñoB;
}
}
public string Menor(int iDiaA,int iMesA,int iAñoA,int iDiaB,int
iMesB,int iAñoB){
int iA = (iDiaA * 10000) + (iMesA + 100) + (iAñoA);
int iB = (iDiaB * 10000) + (iMesB + 100) + (iAñoB);
Console.WriteLine(iA + " < " + iB); //Test Line (Delete)
if(iA < iB){
return iDiaA + "/" + iMesA + "/" + iAñoA;
}else{
return iDiaB + "/" + iMesB + "/" + iAñoB;
}
}
}
class AplicFecha{
public static void Main(){
Fecha MiFecha = new Fecha();
Console.WriteLine("La fecha mayor es : " +
MiFecha.Mayor(21,9,1971, 21,10,2000));
Console.WriteLine("La fecha menor es : " +
MiFecha.Menor(21,9,1971, 21,10,2000));
48
Console.WriteLine("La fecha menor es : " +
MiFecha.Menor(21,10,2000, 21,9,1971));
using System;
class Parametros{
public static void Main(){
Param MiParam = new Param();
49
using System;
class MiClase{
private int MiPropiedad;
public void AsignarValor(ref int MiRefParam){
this.MiPropiedad = MiRefParam;
}
public static void Main(){
MiClase MiObjeto = new MiClase();
int MiEdad = 33;//Se inicializa la variable al declararla
MiObjeto.AsignarValor(ref MiEdad);
Console.WriteLine("MiEdad : " + MiEdad);
Console.WriteLine("MiObjeto.MiPropiedad : " +
MiObjeto.MiPropiedad);
}
}
Los parámetros out son exactamente como los parámetros ref excepto que una
variable sin inicializar puede ser pasada como parámetro y el caller define un
parámetro out en vez de ref.
Valores en Parámetros out
50
Un parámetro out puede ser utilizado sólo para contener el resultado de un método,
es necesario especificar el modificador out para indicar el tipo de parámetro, a
diferencia de los parámetros ref el caller no necesita inicializar la variable antes de
invocar el método:
using System;
class Parametros{
public static void Main(){
Param MiParam = new Param();
int iValorOut; //No se requiere inicilizar el valor
MiParam.ParametroOut(out iValorOut); //Se invoca el método con
un parámetro out
Console.WriteLine("out : " + iValorOut);//Resultado de la invocación
del método
}
}
Ejemplo de Parámetros In, Ref y Out
using System;
class Parametros{
public static void Main(){
Param MiParam = new Param();
51
MiParam.ParametroRef(ref iValorRef);//Se invoca el método
pasando la referencia del valor
Console.WriteLine("ref : " + iValorRef);
class RedefinirMetodos{
public static void Main(){
ClaseBase ClsBase = new ClaseBase();
Console.WriteLine("Clase base : " +
ClsBase.Calculo(5,3));
//Se crea una instancia de la clase derivada:
ClaseDerivada ClsDer = new ClaseDerivada();
//Se invoca el método redefinido en la clase derivada:
52
Console.WriteLine("Clase derivada : " +
ClsDer.Calculo(5,3));
}
}
Ocultamiento de Métodos (Hiding)
Es posible ocultar métodos de la clase base, esto se logra haciendo uso de una
característica especial de la redefinición de métodos llamada ocultamiento de
métodos y al derivar de la clase base:
using System;
class ClaseBase{
//Sin código
}
class Hiding{
public static void Main(){
ClaseDerivada MiClaseDerivada = new ClaseDerivada();
MiClaseDerivada.MetodoOculto();
}
}
El código anterior demuestra que es posible derivar una clase que implementa un
método que la clase base no contiene.
Por otro lado si la clase base contiene el método y se trata de derivar una clase que
trata de implemetar un método que si contiene la clase, se produce un error:
using System;
class ClaseBase{
public void MetodoOculto(){
Console.WriteLine("Hiding Methods");
}
}
class Hiding{
public static void Main(){
ClaseDerivada MiClaseDerivada = new ClaseDerivada();
MiClaseDerivada.MetodoOculto();
}
}
El compilador indicará un mensaje similar al siguiente:
Hiding.cs(10,14): warning CS0108: The keyword new is required on
'ClaseDerivada.MetodoOculto()'
53
because it hides inherited member 'ClaseBase.MetodoOculto()'
Hiding.cs(4,14): (Location of symbol related to previous warning)
El error principal es que no se hace uso del modificador new, ya que si es posible
ocultar un método contenido en la clase base:
using System;
class ClaseBase{
public void MetodoOculto(){//Método Oculto
Console.WriteLine("Hiding Methods");
}
}
class HidingClassMet{
public static void Main(){
ClaseDerivada MiClaseDerivada = new ClaseDerivada();
MiClaseDerivada.MetodoOculto();
}
}
Al hacer uso del modificador new, se le indica al compilador que se está redefiniendo
el método de la clase base y que debería ocultar este método.
Se puede asegurar de invocar el método que redefine la clase derivada utilizando la
siguiente sintaxis:
((ClaseBase)MiClaseDerivada).MetodoOculto();
Propiedades
Las propiedades son convenientes para separar la interfaz de un objeto de su
implementación, en vez de permitir a un usuario acceder directamente a un campo o
arreglo, una propiedad permite especificar a un conjunto de sentencias realizar el
acceso mientras se permita utilizar el campo o arreglo.
class NombreClase{
using System;
class Propiedades{
54
}
Ciudadano.Edad = 33;
Console.WriteLine("Edad Ciudadano : " +
Ciudadano.Edad);
Ciudadano.Mexicano = true;
Console.WriteLine("Mexicano Ciudadano : " +
Ciudadano.Mexicano);
}
}
Existen dos maneras de exponer el nombre de los atributos:
Campos (fields)
Propiedades (properties)
Los atributos son implementados como variables miembro con acceso público via
accessors (get o set).
Los accessors (get o set) especifican las sentencias que son ejecutadas cuando se
requiere leer o escribir el valor de una propiedad.
Los accessors para la lectura del valor de una propiedad son marcados con la palabra
reservada get y los accessors para modificar el valor de una propiedad son marcados
con la palabra reservada set.
El siguiente ejemplo muestra como se implentan los accessors para las propiedades:
using System;
class Persona{
private int iSueldo;
public int Sueldo{
get{return iSueldo;}
set{iSueldo = value;}
}
}
55
class AplicPersona{
public static void Main(){
Persona Empleado = new Persona();
Empleado.Sueldo = 33;
Console.WriteLine("Edad : " + Empleado.Sueldo);
}
}
Note, que se utiliza el parámetro value, ya que el valor actual es almacenado en este
que es accesible dentro de la clase.
Si en vez de utilizar propiedades desea utilizar campos deberá dejar fuera los
accessors y redefinir la variable como:
public int Sueldo;
Accessors
Es posible ocultar los detalles de la estructura de almacenamiento de la clase
reordenando los accessors, en este caso el accessors set es pasado en el nuevo
valor para la propiedad en el parámetro value.
Las operaciones que pueden realizarse con los atributos son:
Propiedades Estáticas
Propiedades estáticas no pueden ser declaradas con los modificadores virtual,
abstract u override.
Las propiedades estáticas pueden ser inicializadas hasta que sea necesario hacerlo,
el valor puede ser fabricado cuando se necesite sin almacenarlo.
using System;
class Persona{
int iPiernas;
int iBrazos;
int iOjos;
public Persona(int piernas, int brazos, int ojos){
this.iPiernas = piernas;
this.iBrazos = brazos;
this.iOjos = ojos;
}
public static Persona Piernas{
get{
return(new Persona(2,0,0));
}
}
public static Persona Brazos{
get{
return(new Persona(0,4,0));
}
}
public static Persona Ojos{
get{
56
return(new Persona(0,0,8));
}
}
}
class App{
public static void Main(){
Persona ET = Persona.Piernas;
Console.WriteLine(ET);
}
}
Índices
Es posible incluir una forma de acceso indexado a la clase tal como si la clase se
tratará de un arreglo, para ello se utiliza la característica de C# indexer, sintaxis:
atributos modificadores declarador{instrucciones}
Los índices o indexers regresan o establecen un string en un índice dado, los indexers
no tienen atributos por lo que utilizan el modificador public.
La parte del declarador consiste del tipo string y la palabra reservada this para denotar
el indexer de la clase:
public string this[int iIndex]{
get{intrucciones}
set{intrucciones}
}
Las reglas de implementación para get y set son las mismas reglas de las
propiedades, la única diferencia es que la lista de parámetros se define libremente
entre los corchetes, también existen restricciones como que es necesario especificar
al menos un parámetro y los modificadores ref y out no están permitidos.
Ejemplo:
using System;
using System.Net;//Directiva namespace para la clase DNS
class ResolverDNS{
IPAddress[] aIPs;
public void Resolver(string strHost){
IPHostEntry IPHE = Dns.GetHostByName(strHost);
aIPs = IPHE.AddressList;
}
public IPAddress this[int iIndex]{
get{return aIPs[iIndex];}
}
public int Contador{
get{return aIPs.Length;}
}
}
class AplicResolverDNS{
public static void Main(){
ResolverDNS MiDNS = new ResolverDNS();
MiDNS.Resolver("www.informatique.com.mx");
57
for(int i = 0; i < iContador; i++){
Console.WriteLine(MiDNS[i]);
}
}
}
Si el namespace para la clase DNS no está contenida en la biblioteca central, al
compilar incluya la referencia a la biblioteca que la contiene:
csc /r:System.Net.dll /out:resolver.exe AplicResolverDNS.cs
Los índices pueden tener más que un parámetro para simular un arreglo virtual
multidimensional.
Eventos
Una clase puede usar un evento para notificar a otra clase o clases que algo ocurrio,
los eventos usan el idioma "publish-subscribe", ya que una clase publica el evento que
puede lanzar y las clases que están interesadas en un evento específico pueden
subscribir al evento.
La rutina o tarea que un evento podría invocar es definida por un delegado.
Para tratar de manera fácil con eventos, la convención de diseño para eventos es
emplear dos parámetros, donde el primer parámetro es el objeto que lanza el evento y
el segundo parámetro es un objeto que contiene la información acerca del evento el
cual siempre deriva de la clase EventArgs.
Los eventos pueden ser declarados como campos o propiedades de clase, ambos
accesos comparten la comodidad de tipo que el evento debe tener delegate.
Cada evento puede ser utilizado por cero o más clientes y un cliente puede utilizar un
evento en cualquier momento.
Los delegados pueden ser implementados como métodos o instancias estáticas.
Modificadores
Los modificadores pueden clasificarse en:
Modificadores de Clase
Modificadores Miembro
Modificadores de Acceso
Modificadores de Clase
Existen dos tipos de modificadores de clase:
abstract
58
Utilice este modificador para prevenir herencia accidental
Ejemplo:
using System;
abstract
59
Un campo declarado con el modificador readonly puede ser
cambiado sólo en su declaración o en el constructor de la clase
contenedora
static
Miembros que son declarados static pertenecen a la clase y no a
una instancia de la clase
Pueden utilizarse modificadores static con campos, métodos,
propiedades, operadores y constructores.
virtual
Indican que un método o accessor pueden ser redefinidos por las
clases que reciben la herencia.
Modificadores de Acceso
Los modificadores de acceso definen el nivel de acceso que cierto código tiene en los
miembros de la clase como métodos y propiedades. Es necesario aplicar el
modificador de acceso deseado a cada miembro, de otra forma el tipo de acceso por
default es implícito.
Los modificadores de acceso son:
60
Sentencias de Selección
Sentencias de Iteración
Sentencias de Selección
Las Sentencias de selección son aquellas que se utilizan para realizar operaciones
basadas en el valor de una expresión.
Las Sentencias de selección son aquellas que se utilizan para escribir diferentes
flujos de acción en base a una condición dada, existen dos tipos de sentencias de
selección:
Sentencia if
Sentencia switch
Sentencia if
Al escribir uno o varios flujos de acción el código contenido en estos se ejecutará
siempre y cuando la evaluación de la expresión en la sentencia if se evalue como
verdadera (tenga cuidado en C# if(0){} o if(1){} no es válido).
if(expresión-booleana){la expresión se evaluo verdadera}
Es posible indicar código alterno en caso de que la expresión booleana se evalue
falsa:
if(expresión-booleana){
la expresión se evaluo verdadera
}else{
la expresión se evaluo falsa
}
Nota C# no puede convertir valores numéricos a booleanos, solo puede hacer
comparaciones entre ellos para evaluar el resultado de la expresión el cual es un valor
booleano.
using System;
class SeleccionIf{
public static void Main(){
if(1 == 1){
Console.WriteLine("se evaluo verdadero");
}
/* No es soportado por C#
if(0){
Console.WriteLine("?");
}
*/
}
}
Nota el operador de igualdad en C# es ==, si está habituado a otra forma, sera cosa
tiempo acostumbrarse a escribirlo correctamente, en la siguiente tabla se muestran los
operadores válidos en C#:
Operador Evalua
== Verdadero, si ambos valores son los mismos
!= Verdadero, si los valores son diferentes
61
<, <=, >, >= Verdadero, si el valor cumple con la condición
Los operadores de la tabla son implementados via la sobrecarga de operadores y la
implementación es especifica para el tipo de dato, si se comparan dos variables de
diferente tipo se realiza una conversión implícita que debe existir para que el
compilador cree el código necesario automáticamente. Recuerde que siempre podrá
realizar un cast explícito.
Ejemplo
using System;
class Caracteres{
public static void Main(){
string sNombre = "Gerardo Angeles Nava";
if(Char.IsDigit(chLetra)){
Console.WriteLine(chLetra + " es un dígito");
}else{
EsMayuscula(chLetra);
EsMinuscula(chLetra);
}
if(Char.IsDigit(chLetra)){
Console.WriteLine(chLetra + " es un dígito");
}else{
EsMayuscula(chLetra);
EsMinuscula(chLetra);
}
sNombre = "123";
chLetra = sNombre[2];//Extrae el tercer caracter del string
if(Char.IsDigit(chLetra)){
Console.WriteLine(chLetra + " es un dígito");
}else{
EsMayuscula(chLetra);
EsMinuscula(chLetra);
}
}
public static void EsMayuscula(char chCaracter){
if(chCaracter >= 'A' && chCaracter <= 'Z'){
Console.WriteLine(chCaracter + " mayúscula");
}
}
public static void EsMinuscula(char chCaracter){
if(chCaracter >= 'a' && chCaracter <= 'z'){
Console.WriteLine(chCaracter + " minúscula");
}
}
62
}
En el ejemplo anterior se muestra la aplicación de la sentencia de selección if y el uso
del método IsDigit de la clase Char, también se muestra como determinar si un
caracter correponde a las letras mayúsculas o minúsculas.
Good Practice: nunca asigne valores a variables dentro de una condición que utiliza
operadores lógicos (&&,||,!), porque puede que nunca se le asigne el valor
correspondiente a la variable en caso de que una expresión anterior se evalue
verdadera:
if(a == b || (c == (iValor = d))){}
En el ejemplo anterior, si la expresión a == b se evalua verdadera entonces la variable
iValor nunca contendrá el valor d.
Sentencia switch
La sentencia de selección switch tiene una expresión de control y los flujos de código
alternos son ejecutados dependiendo del valor constante asociado con esta
expresión.
switch(expresion-de-control){
case expresion-contante:
sentencias;
break;
case expresion-contante:
goto case 2;
case expresion-contante:
goto default;
default:
sentencias;
}
Los tipos de datos permitidos para la expresión de control son sbyte, byte, short,
ushort, uint, long, ulong, char, string o un tipo enumeración (enumeration).
¿Cómo funciona la sentencia de selección switch?
Ejemplo:
using System;
class SentenciaSwitch{
public static void Main(){
for(int i = 0; i <= 12; i++){
Mes(i);
}
63
}
public static void Mes(int iMes){
switch(iMes){
case 1:
Console.WriteLine("Enero");
break;
case 2:
Console.WriteLine("Febrero");
break;
case 3:
Console.WriteLine("Marzo");
break;
case 4:
Console.WriteLine("Abril");
break;
case 5:
Console.WriteLine("Mayo");
break;
case 6:
Console.WriteLine("Junio");
break;
case 7:
Console.WriteLine("Julio");
break;
case 8:
Console.WriteLine("Agosto");
break;
case 9:
Console.WriteLine("Septiembre");
break;
case 10:
Console.WriteLine("Octubre");
break;
case 11:
Console.WriteLine("Noviembre");
break;
case 12:
Console.WriteLine("Diciembre");
break;
default:
Console.WriteLine("Mes no válido");
break;
}
}
}
Es posible utilizar sentencias goto dentro del switch de la siguiente manera:
Ejemplo:
using System;
64
class SentenciaSwitch{
public static void Main(){
int iOpcion = 4;
Opcion(iOpcion);
iOpcion = 2;
Opcion(iOpcion);
iOpcion = 8;
Opcion(iOpcion);
iOpcion = 10;
Opcion(iOpcion);
}
public static void Opcion(int iValor){
switch(iValor){
case 2:
goto case 6;
case 4:
Console.WriteLine(" cuatro");
break;
case 6:
Console.WriteLine(" seis");
break;
case 8:
goto default;
case 10:
Console.WriteLine(" diez");
break;
default:
Console.WriteLine(" por defecto");
break;
}
}
}
Sentencias de Iteración (repetición)
Las Sentencias de Iteración (también conocidas como looping statements) son
aquellas que nos permiten ejecutar un bloque de código repetidamente mientras una
condicíon específica sea verdadera:
for
foreach
while
do
Sentencia for
La Sentencia for se utiliza cuando se conoce previamente cuantas veces ha de
repetirse un bloque de código. Este bloque se repetira mientras la condición evalue
una expresión booleana verdadera, no será posible evaluar otro tipo de expresión.
Sintaxis:
for(inicializador; condición; iterador)
Los componentes de la sentencia for: inicializador, condición, iterador, no son
obligatorios.
Es posible salir de un ciclo for a través de las instrucciones:
65
break
goto
Ejemplo:
using System;
class Factorial{
public static void Main(string[] aArgs){
if(aArgs.Length == 0){
Console.WriteLine("Debe proporcionar un argumento,
Ejemplo: Factorial 5");
return;
}
long lFactorial = 1;
long lCalcular = Int64.Parse(aArgs[0]);
long lAux = 1;
for(lAux = 1; lAux <= lCalcular; lAux++){
lFactorial *= lAux;
//Test Line Console.WriteLine("{0}! * {1}", lAux, lFactorial);
}
Console.WriteLine("{0}! es {1}", lCalcular, lFactorial);
}
}
Sentencia foreach
La Sentencia foreach es un comando para enumerar los elementos de una
colección.
foreach(Tipo indentificador in expresión){}
La variable de iteración es declarada por el Tipo, indentificador y expresión
correspondiente a la colección.
La variable de iteración representa el elemento de la colección para cada iteración.
El siguiente ejemplo muestra el uso de for:
using System;
class App{
public static void Main(string[] aArgs){
for(int i = 0; i < aArgs.Length; i++){
Console.WriteLine("Elemento " + i + " = " +
aArgs[i]);
}
}
}
El ejemplo anterior implementado con foreach:
using System;
class App{
public static void Main(string[] aArgs){
foreach(String s in aArgs){
Console.WriteLine(s);
}
}
}
66
No es posible asignar un nuevo valor a la variable de iteración. No se puede pasar la
variable de iteración como un parámetro ref o out.
Para que una clase soporte la sentencia foreach, la clase debe soportar un método
con la firma GetEnumerator() y la estructura, clase o interface que regresa debe tener
un método público MoveNext y una propiedad pública Current.
En el siguiente ejemplo el método GetEnvironmentVariables() regresa una interfaz de
tipo IDictionary. Es posible acceder a las colecciones Keys y Values de la interfaz
IDictionary:
using System;
using System.Collections;
class SentenciaForEach{
public static void Main(){
IDictionary VarsAmb = Environment.GetEnvironmentVariables();
Console.WriteLine("Existen {0} variables de ambiente declaradas",
VarsAmb.Keys.Count);
foreach(String strIterador in VarsAmb.Keys){
Console.WriteLine("{0} = {1}", strIterador,
VarsAmb[strIterador].ToString());
}
}
}
Nota, es necesario tener una precaución extra al decidir el tipo de variable de
iteración, porque un tipo equivocado no puede ser detectado por el compilador, pero si
detectado en tiempo de ejecución y causar una excepción.
Sentencia while
La Sentencia while se utiliza cuando no se conoce previamente cuantas veces ha de
repetirse un bloque de código, por lo que puede ejecutarse 0 o más veces. Este
bloque se repetira mientras la condición evalue una expresión booleana verdadera, no
será posible evaluar otro tipo de expresión.
while(condicional){}
Ejemplo:
using System;
using System.IO;
class SentenciaWhile{
public static void Main(){
if(!File.Exists("test.html")){
Console.WriteLine("El archivo test.html no
existe");
return;
}
StreamReader SR = File.OpenText("test.html");
String strLinea = null;
while(null != (strLinea = SR.ReadLine())){
Console.WriteLine(strLinea);
}
SR.Close();
}
}
Es posible utilizar la sentencia break para salir del ciclo o continue para saltar una
iteración.
Sentencia do
67
La diferencia entre la sentencia while y do es que do se evalua después de su primer
iteración, por lo que al menos siempre se ejecuta una vez:
do{
sentencias;
}while(condición);
Es posible salir de un ciclo do a través de la sentencia break y es posible saltar una
iteración utilizando la sentencia continue
El siguiente ejemplo le la entrada de la consola toma el primer caracter leido, lo
convierte en un Tipo double y suma su valor mientras la entrada sea 's' o hasta que la
entrada sea 'n'.
using System;
class Consola{
public static void Main(){
Consola LeerDatos = new Consola();
LeerDatos.Run();
}
public void Run(){
char chContinuar = 's';
string strDatos;
double dSuma = 0;
do{
Console.Write("Proporcione un número: ");
strDatos = Console.ReadLine();
dSuma += Double.Parse(strDatos);
Console.Write("¿Continuar s/n?");
strDatos = Console.ReadLine();
chContinuar = strDatos[0];
if(chContinuar == 'n') break;
}while(chContinuar == 's');
Console.WriteLine("La suma de los números es: " +
dSuma);
}
}
Sentencias de Salto
Las Sentencias de Salto como break, continue, goto y return sirven para ir de una
sentencia a otra
break
La Sentencia break es utilizada para salir de la iteración en curso o sentencia switch
y continuar con la ejecución después de esa sentencia.
continue
La Sentencia continue salta todas las sentencias siguientes en la iteración en curso y
entonces continua la ejecución en la sentencia de iteración (siguiente iteración).
goto
La Sentencia goto puede ser utilizada para saltar directamente a una etiqueta. Una
sentencia goto no puede ser utilizada para saltar adentro de un bloque de sentencias.
Su uso podría ser empleado en sentencias switch o para transferir el control fuera de
un loop anidado.
Nota, como buena práctica no se recomienda el uso de goto.
return
68
La Sentencia return regresa a la función invocadora y opcionalmente retorna un
valor.
Asignación Definitiva
Las reglas de Asignación definitiva previenen la observación del valor de una
variable no asignada, ya que C# no permite utilizar variables que no han sido
inicializadas, así como también no pueden realizarse operaciones con variables de
clase que no han sido inicializadas.
Puede accederse al elemento de un arreglo aún si no ha sido inicializado, ya que el
compilador no puede rastrear la asignación definitiva en todas las situcaciones.
Precedencia de Operadores
Cuando una expresión contiene múltiples operadores, la precedencia de operadores
controla el orden en el cual los elementos de la expresión son evaluados.
Categoría Operador
Primary (x), x.y, f(x), a[x], x++, x--, new, typeof, sizeof, checked, unchecked
Unary +, -, !, ~, ++x, --x, (T)x
Multiplicative *, /, %
Additive +, -
Shift <<, >>
Relational <, >, <=, >=, is
Equality ==, !=
Logical AND &
Logical XOR ^
Logical OR |
Conditional AND &&
Conditional OR ||
Conditional ?:
Assignment =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=
typeof
El operador typeof regresa el tipo del objeto, el cual es una instancia de la clase
System.Type
Una instancia ya existente puede obtener el tipo de objeto con el método de la
instancia GetType().
is
El operador is es utilizado para determinar cuando una referencia a un objeto puede
ser converitda a un tipo específico o interface.
El operador as es muy similar al operador is, pero en vez de determinar cuando un
objeto es un tipo específico o interface, as también realiza la conversión explicita a
ese tipo o interface y si no lo puede convertir el operador regresa null.
Utilizar as es más eficiente que utilizar is, porque as sólo necesita checar el tipo del
objeto una vez e is checa el tipo cuando el operador es utilizado y lo checa otra vez
cuando la conversión se realiza.
69
Utilizando is
if(UnObjeto is UnTipo){
UnTipo ut = (UnTipo) UnObjeto;
}
Utilizando as
UnTipo ut = UnObjeto as UnTipo;
if(ut != null){
sentencias;
}
Conversiones
En C# las conversiones se dividen en conversiones explícitas y conversiones
implícitas que son aquellas que podrían siempre ocurrir:
//conversiones implícitas
sbyte a = 55;
short b = a;
int c = b;
long d = c;
//conversiones explícitas
c = (int) d;
b = (short) c;
a = (sbyte) b;
A continuación se presenta la jerarquía de conversión en C##
Manejo de Excepciones
Las excepciones son el método fundamental de manejo de condiciones de error.
Ejemplo:
using System;
class DivisionCero{
public static void Main(){
int iA = 33;
int iB = 0;
try{
//Sentencia que puede lanzar una excepción
Console.WriteLine("{0}/{1} = {2}", iA ,iB, iA/iB);
}catch(Exception e){
//Manejo de la excepción
Console.WriteLine("La operación {0}/{1} genero la excepcion :
{2}", iA, iB, e);
}
Console.WriteLine("Continua la ejecución del código...");
}
}
El ejemplo encierra el bloque de código que podría lanzar una excepción con try. En
caso de generarse una excepción el runtime .NET detiene la ejecución del código y
busca el bloque try en el cual la excepción tuvo lugar, entonces busca si este bloque
tiene relacionado un bloque catch, puede ser que encuentre más de un bloque catch
relacionado al bloque try que genero la excepción, por lo que se determina que bloque
catch es el que mejor y ejecuta el código que contiene.
El compilador de C# puede manejar silenciosamente situaciones que podrían producir
un error sin notificarnos explicitamente de ello, por ejemplo una situación como un
70
overflow que es cuando el cálculo de una operación excede el rango válido de
resultados posibles para el tipo de dato.
El caso del código para calcular un factorial, el compilador no prodruce una
advertencia, pero si trata de obtener el factorial de 2000 dara por resultado 0, el
compilador actuo en modo silencioso porque por default el compilador tiene
deshabilitada la opción de chequeo de overflow.
Es posible cambiar el comportamiento de chequeo de overflow utilizando un switch al
compilar.
Jerarquía de Excepciones
Todas las excepciones derivan de la clase Exception, la cual es parte del lenguaje
común en tiempo de ejecución (CLR), donde la propiedad catch determina por
coincidencia el tipo de excepción a el nombre de la excepción generada. Un bloque
catch con una coincidencia especifica hacen más general la excepción:
using System;
class ExceptionDivision0{
public static void Main(){
int iA = 33;
int iB = 0;
try{
Console.WriteLine("{0}/{1} = {2}", iA ,iB, iA/iB);
}catch(DivideByZeroException){
Console.WriteLine("Se genero la excepcion :
DivideByZeroException");
}
Console.WriteLine("Continua la ejecución del código...");
}
}
En este ejemplo el bloque catch que atrapa la excepción DivideByZeroException es
una coincidencia más específica, por lo que es la única que será ejecutada, pero si
además de escribir el catch para DivideByZeroException escribe el catch para
Exception, el compilador le notificara que existe una excepción que atrapa todas las
excepciones y esto es porque Exception ocupa la cima de la jerarquía de todas las
excepciones.
Trabajar con Excepciones
Existen tres formas básicas de trabajar con excepciones:
Caller Beware
La primer forma es no atrapar la excepción, lo cual provocará dejar al objeto en un
estado incorrecto, y causará daños cuando el caller trate de utilizarla de nuevo.
Caller Confuse
La segunda forma es atrapar la excepción y tratar de hacer acciones que dejen la
operación como estaba hasta antes de generarse la excepción y entonces relanzar la
excepción, esto usualmente es lo menos que se esperaria del manejo de excepciones
ya que un objeto debería siempre mantener un estado válido después de generarse
una excepción.
Se llama Caller Confuse, porque después de generase una excepción, el caller con
frecuencia tiene poca información respecto al entendimiento de los detalles de la
excepción o como podría ser solucionada.
Caller Inform
Las tercer forma Caller Inform agrega información que es devuelta al usuario, la
excepción atrapada es envuelta en una excepción que tiene información adicional:
using System;
71
class ExcDivZeroInf{
public static void Main(){
int iA = 33;
int iB = 0;
try{
Console.WriteLine("{0}/{1} = {2}", iA ,iB, iA/iB);
}catch(DivideByZeroException e){
Console.WriteLine("Se genero la excepcion :
DivideByZeroException");
throw(new DivideByZeroException("Información
adicional...", e));
}
Console.WriteLine("Continua la ejecución del código...");
}
}
Chequeo de Overflow
Si requerimos controlar el chequeo de overflow para la aplicación completa, el
compilador de C# debe establecerse como checked. Por default el compilador tiene
deshabilitada la opción de chequeo.
Para indicar explicitamente que el compilador cheque el overflow escriba:
csc Factorial.cs /checked+
Una vez que se compilo de con la opción de chequeo de overflow habilitado, al
intentar obtener el factorial de 2000 de presenta la ventana Just-In-Time-debbuging
notificandonos que ocurrio una excepción en Factorial.exe:
System.OverflowException
Este tipo de situaciones es posible atraparlas y manejarlas a través de las
excepciones que se producen.
Chequeo programático de Overflow
Existe otra opción si es que no deseamos activar el Chequeo de Overflow para la
aplicación completa y habilitar sólamente partes especificas de código, para ello se
utiliza la sentencia checked:
using System;
class FactorialChecked{
public static void Main(string[] aArgs){
if(aArgs.Length == 0){
Console.WriteLine("Debe proporcionar un argumento,
Ejemplo: Factorial 5");
return;
}
long lFactorial = 1;
long lCalcular = Int64.Parse(aArgs[0]);
long lAux = 1;
for(lAux = 1; lAux <= lCalcular; lAux++){
checked{lFactorial *= lAux;} //Habilitar chequeo de
overflow
//Test Line Console.WriteLine("{0}! * {1}", lAux, lFactorial);
}
Console.WriteLine("{0}! es {1}", lCalcular, lFactorial);
}
}
72
También es posible hacer el caso contrario, es decir, indicar que no se realice el
chequeo de overflow para partes especificas de código, para ello se utiliza la
sentencia:
unchecked{sentencias;}
Sentencias para el Manejo de Excepciones
Es posible atrapar, manejar y limpiar las excepciones que se producen utilizando las
sentencias siguientes:
try - catch
try - finally
try - catch - finally
try - catch
Para evitar que se muestre el mensaje que indica que una excepción ocurrio, es
necesario atrapar la excepción y lo mejor de todo es que continue la ejecución del
programa, para ello se utiliza try y catch.
try contiene el código que quizá pueda lanzar una excepción y catch maneja la
excepción si esta ocurre:
try{
//sentencias que pueden lanzar una excepción
}catch(nombreExcepción){
//manejo de la excepción
}
El siguiente ejemplo maneja la excepción FileNotFoundException que se produce
cuando se intenta manipular un archivo que no existe, si esto ocurre se presenta un
mensaje que muestra el nombre del archivo que se intento manipular y no se encontro
a través de una propiedad pública de la excepción llamada FileName.
using System;
using System.IO;
class SentenciaWhile{
public static void Main(){
try{
StreamReader SR = File.OpenText("test.html");
String strLinea = null;
while(null != (strLinea = SR.ReadLine())){
Console.WriteLine(strLinea);
}
SR.Close();
}catch(FileNotFoundException e){//En caso de que el
archivo no exista
Console.WriteLine("No se encontro el archivo : "
+ e.FileName);
return;
}
}
}
try - finally
Es posible limpiar el manejo de errores utilizando try y el constructor finally, sin
eliminar el mensaje de error, pero el código contenido en el bloque finally es ejecutado
aún después de ocurrir una excepción.
73
El siguiente código maneja una variable booleana que indica si se produjo un error,
simplemente poniendola dentro del bloque try, si el código contenido fué ejecutado la
variable booleana es false lo cual indica que no ocurrieron excepciones, si el bloque
no se ejecuto la variable booleana mantiene su valor inicial lo cual significa que si
ocurrieron excepciones y entonces se ejecuta el bloque Finally el cual evalua el valor
de la variable booleana y presenta la indicación correspondiente.
using System;
using System.IO;
class SentenciaTryFinally{
public static void Main(){
bool bExcepcion = true;
try{
StreamReader SR = File.OpenText("test.html");
String strLinea = null;
while(null != (strLinea = SR.ReadLine())){
Console.WriteLine(strLinea);
}
SR.Close();
bExcepcion = false;
}
finally{
if(bExcepcion){
Console.WriteLine(">>> No se encontro
el archivo");
}else{
Console.WriteLine(">>> No ocurrieron
excepciones");
}
}
}
}
Note que en caso de no existir el archivo se produce una excepción y se presenta el
mensaje que indica que ha ocurrido una excepción pero también fué ejecutado el
bloque finally, el código que contiene el bloque finally siempre es ejecutado ocurra o
no una excepción.
Puede emplear la sentencia finally para reestablecer los valores previos a la
generación de la excepción.
try - catch - finally
Combinar try para controlar el código que puede lanzar excepciones, atrapar la
excepción con catch y llevar acabo instrucciones necesarias con finally hacen una
mejor solución cuando ocurren las excepciones.
Es posible utilizar una sentencia catch por cualquier excepción que pudiera ocurrir, es
decir, tener más de un bloque catch, pero es necesario conocer la jerarquía de las
excepciones porque puede ocurrir que un bloque previo catch sea más general y
contenga todas las excepciones lo cual produciria un error.
using System;
using System.IO;
class SentenciaTryCatchFinally{
public static void Main(){
bool bExcepcion = true;
bool bModificacion = false;
74
try{
bModificacion = true;
StreamReader SR = File.OpenText("test.htmlX");
String strLinea = null;
while(null != (strLinea = SR.ReadLine())){
Console.WriteLine(strLinea);
}
SR.Close();
bExcepcion = false;
}catch(FileNotFoundException e){//En caso de que el archivo no
exista
Console.WriteLine("No se encontro el archivo : " +
e.FileName);
return;
}
finally{
if(bExcepcion){
bModificacion = false;//Valor antes de generarse
la excepción
if(!bModificacion){
Console.WriteLine("Entro en modo
modificación, _
pero las modificaciones no se
realizaron");
}
Console.WriteLine("Causa : No se encontro el
archivo");
}else{
Console.WriteLine("No ocurrieron excepciones");
}
}
}
}
Lanzamiento de Excepciones
Para atrapar una excepción con la sentencia catch primero debe generarse la
excepción, pero es posible que a través de codigo se lanze o invoque una excepción:
throw new NombreExcepcion(excepcion);
El poder lanzar o invocar una excepción es util cuando no se ha contemplado cierto
escenario o para nuevos escenarios, al crear una clase podrian crearse también
excepciones propias de esta clase.
A continuación se presenta una tabla que contiene las excepciones estándar que
provee el runtime:
Tipo Descripción
Exception Clase base para todas los objetos exception
Clase base para todos los errores
SystemException
generados en tiempo de ejecución
Lanzada en tiempo de ejecución cuando el
IndexOutRangeException
índice de un arreglo está fuera de rango
Disparada en tiempo de ejecución cuando
NullreferenceException
un objeto null es referenciado
75
Lanzada por ciertos métodos cuando
InvalidOperationException invocan a métodos que son inválidos para
el estado de los objetos actuales
Clase base de todos los argumentos de las
ArgumentException
excepciones
Lanzada por un método, en caso de que un
ArgumentNullException argumento sea null cuando no sea
permitido
Lanzada por un método cuando un
ArgumentOutOfRangeException
argumento no está en el rango permitido
Clase base para excepciones que son
InteropException originadas u ocurren en ambientes fuera del
CLR
Excepción que contiene información
ComException
HRESULT COM
Excepción que encapsula información del
SEHException manejo de excepciones destructurada
Win32
Relanzamiento de Excepciones
El siguiente código muestra como es posible atrapar una excepción, manejarla y se
volverla a invocar:
using System;
class FactorialCheckedReThrow{
public static void Main(string[] aArgs){
if(aArgs.Length == 0){
Console.WriteLine("Debe proporcionar un argumento,
Ejemplo: Factorial 5");
return;
}
long lFactorial = 1;
long lCalcular = Int64.Parse(aArgs[0]);
long lAux = 1;
try{
checked{ //Habilitar chequeo de overflow
for(lAux = 1; lAux <= lCalcular; lAux++){
lFactorial *= lAux;
//Test Line Console.WriteLine("{0}! * {1}", lAux,
lFactorial);
}
}
}catch(OverflowException){
Console.WriteLine("El factorial {0}! causo una excepción",
lCalcular);
throw;
}
Console.WriteLine("{0}! es {1}", lCalcular, lFactorial);
76
}
}
Componentes
Así como es posible escribir clases y hacer uso de estas en un mismo archivo,
también es posible escribir en un archivo (ejecutable) unicamente la clase lo cual es
77
conocido como componente y en otro archivo (también ejecutable) el uso de la clase
lo cual es conocido como cliente.
Creación de Componentes
Para crear un componente unicamente es necesario escribir la Clase con todos sus
miembros (Constructores, Propiedades, Métodos), almacenarla en un archivo y
compilar el componente.
using System;
class AplicClsPersona{
public static void Main(){
ClsPersona Empleado = new ClsPersona();
Empleado.Sueldo = 33;
Console.WriteLine("Edad : " + Empleado.Sueldo);
78
Empleado.AsignarNombre("Gerardo Ángeles Nava");
Console.WriteLine("Nombre : " +
Empleado.ObtenerNombre());
}
}
Compilación de Clientes
Es necesario indicarle al compilador una referencia a la nueva biblioteca de
componentes DLL:
csc /r:NombreComponente.dll NombreCliente.cs
Nota para hacer uso de una clase es necesario que tenga el modificador de acceso
public.
Namespaces
Los nombres de espacio namespace en el runtime .NET son utilizados para organizar
las clases y otros tipos en una estructura jerarquica. El propósito del uso de
namespace hacen las clases fáciles de usar y prevenir colisiones con las clases
escritas por otros programadores.
Los namespace en C# se utilizan para organizar programas y la jerarquía natural de la
organización facilita presentar los elementos de un programa a otros programas. Los
namespace son utiles para la organización interna de aplicaciones.
Un namespace contiene tipos que pueden ser utilizados en la construcción de
programas: clases, estructuras, enumeraciones, delegados e interfaces, por ejemplo
para poder escribir a la consola se utiliza el namespace System.
No es obligatorio jerarquizar los namespace pero es una buena práctica organizar los
namespace creados de manera jerarquica para dar claridad a la aplicación.
Los nombres de espacio son definidos utilizando la sentencia:
namespace
Para múltiples niveles de organización los namespaces pueden ser anidados:
namespace NombreNamespaceA{
namespace NombreNamespaceB{
class NombreClase{
public static void Function(){}
}
}
}
El código anterior puede ser simplificado de la siguiente manera:
namespace NombreNamespaceA.NombreNamespaceB{
class NombreClase{
public static void Function(){}
}
}
Las colisiones entre tipos o nombres de espacio que tienen el mismo nombre se
pueden resolver utilizando una variante de la cláusula using que permite definir un
alias para la clase:
using Alias = System.Console;
class NombreClase{
public static void Main(){
Alias.WriteLine("Alias de una clase");
}
}
Envolver Clases en Namespace
79
Es posible envolver (wrapping) las clases en un namespace, para ello sólo es
necesario utilizar la palabra reservada namespace seguida de un nombre que lo
identifique y encerrar entre llaves el código que deseamos pertenezca a este.
using NombreOtrosEspacios;
namespace NombreEspacio{
public class NombreClase{
//propiedades
//métodos
}
}
Ejemplo:
using System;
namespace informatique.com.mx{
public class iPersona{
private int iSueldo;
public int Sueldo{
get{return iSueldo;}
set{iSueldo = value;}
}
class iAplicClsPersona{
public static void Main(){
iPersona Empleado = new iPersona();
Empleado.Sueldo = 33;
Console.WriteLine("Edad : " + Empleado.Sueldo);
80
}
Compilar:
csc /r:iPersona.dll iAplicPersona.cs
Especificar de manera absoluta el namespace:
using System;
class iAplicClsPersonaAbs{
public static void Main(){
informatique.com.mx.iPersona Empleado = new
informatique.com.mx.iPersona();
Empleado.Sueldo = 33;
Console.WriteLine("Edad : " + Empleado.Sueldo);
namespace informatique.com.mx{
class iVehiculo{
private int iRueda;
private int iPuerta;
private int iVentana;
private int iHelice;
private int iMotor;
private int iAsiento;
private string sTipo;//Aereo, anfibio, terrestre, espacial
//Constructor
public iVehiculo(int Rueda, int Puerta, int Ventana, _
int Helice, int Motor, int Asiento, string Tipo){
iRueda = Rueda;
iPuerta = Puerta;
iVentana = Ventana;
iHelice = Helice;
iMotor = Motor;
iAsiento = Asiento;
sTipo = Tipo;
}
81
}
82
Console.WriteLine("Helices : " + MiAvion.Helices);
Console.WriteLine("Motores : " + MiAvion.Motores);
Console.WriteLine("Asientos : " + MiAvion.Asientos);
Console.WriteLine("Tipo : " + MiAvion.Tipo);
}
}
Compilar:
csc /r:informatique.com.mx.dll iAplic.cs
Recuerde que sus clases deben tener el modificador de acceso public, de lo
contrario el compilador notificara un error (error CS0122: is inaccessible due to its
protection level), por lo que tendrá que agregar el modificador de acceso y volver a
compilar.
Namespace y Ensambles
Un objeto puede ser utilizado desde un archivo fuente C# sólo si ese objeto puede ser
localizado por el compilador C#, por default el compilador sólo abre el ensamble
conocido como mscorlib.dll, el cual contiene las funciones principales para el CLR.
Para referenciar objetos localizados en otros ensambles, el nombre del archivo de
ensamble debe ser pasado al compilador, esto es posible utilizando un switch al
compilar:
/r:nombreEnsamble
Es así como se crea un correlación entre el namespace de un objeto y el nombre del
ensamble en el cual reside, por ejemplo los tipos de namespace en el System.Net
residen en el ensamble System.Net.dll
Compilación Condicional
La Compilación Condicional permite excluir o incluir código, en C# existen dos
formas de hacer esto:
Definición de símbolos
Exclusión de código basado en símbolos
Lanzamiento de errores y advertencias
Definición de símbolos
La Definición de símbolos es utilizada para excluir o incluir código dependiendo si
son o no son definidos ciertos símbolos.
Una forma para definir un símbolo es utilizando la directiva #define en un archivo
fuente C#, está definición deberá realizarse antes de cualquier otra sentencia:
#define DEBUG
#define RELEASE
En este caso #define DEBUG, define un símbolo DEBUG y su ámbito es el archivo
donde es definido, al igual que el símbolo RELEASE.
83
Otra forma utilizada para definir símbolos es usar el compilador y es de ámbito global
para todos los archivos:
csc /define:DEBUG nombreArchivo.cs
Si se requiere definir múltiples símbolos utilizando el compilador, es necesario separar
cada símbolo con una coma (,)
csc /define:DEBUG,RELEASE,DEMOVERSION nombreArchivo.cs
Si el código fuente incluye directivas de definición de símbolos, es posible
deshabilitarlas utilizando la directiva #undef cuyo ámbito también corresponde al
archivo donde es definida:
#undef DEBUG
Exclusión de código basado en símbolos
El principal propósito de los símbolos es la inclusión o exclusión condicional del
código, basado sobre si son o no son definidos los símbolos.
El siguiente código no define símbolos en el archivo fuente:
.
sentencia;
.
#if NOMBRE_SIMBOLO
sentencia;
#else
sentencia;
#endif
.
Pero es posible definir o no los símbolos al compilar la aplicación:
csc /define:NOMBRE_SIMBOLO NombreAplicacion.cs
Las directivas del preprocesador emulado utilizadas para evaluar el símbolo son: #if,
#else y #endif las cuales actuan como su contraparte, la sentencia condicional if C#,
es posible utilizar &&, || y !:
//#define SIMBOLO_A
#define SIMBOLO_B
#define SIMBOLO_C
#if SIMBOLO_A
#undef SIMBOLO_C
#endif
using System;
class NombreClase{
public static void Main(){
#if SIMBOLO_A
.
#elif SIMBOLO_B && SIMBOLO_C
.
#else
.
#endif
}
}
Lanzamiento de errores y advertencias
Existe otro uso de las directivas del preprocesador para lanzar errores del compilador
o advertencias dependiendo de ciertos símbolos, para ello se utilizan las directivas:
84
#warning
#error
Por ejemplo
#if SIMBOLO_ERROR
#error Presentar el mensaje de error correspondiente
#endif
.
#if SIMBOLO_WARNING
#error Presentar el mensaje de advertencia correspondiente
#endif
Atributo conditional
Un atributo conditional evalua la invocación de una función cuando sierto símbolo es
definido y evalua a nada cuando una versión liberada es construida.
Un atributo conditional debe tener un tipo void de regreso, cualquier otro tipo de
regreso no es permitido.
.
[conditional("NOMBRE_SIMBOLO")]
método A
[conditional("NOMBRE_SIMBOLO")]
método B
.
Comentarios de Documentación en XML
Es posible construir automáticamente la Documentación utilizando comentarios en el
código.
La salida que es generada por el compilador es XML puro y puede ser utilizada como
entrada para la documentación de un componente.
La documentación es una parte extremadamente importante del software y en
especial de los componentes por ser utilizados por otros desarrolladores.
Elementos XML
Nota: todo comentario de documentación son tags XML (eXtensible Markup
Language).
Para describir un elemento se utiliza el tag <summary></summary>, el cual se escribe
en el código fuente anteponiendo tres diagonales que son indicativo de un comentario
de documentación:
///<summary>Descripción...</summary>
En caso de requerir más una línea para el comentario de documentación utilice el tag:
///<para>
///.
///.
///</para>
En caso de requerir una referencia a otros elementos utilice el tag:
///<see cref="NombreElemento"/>
En caso de requerir una referencia a un tópico de interes utilice:
///<seealso cref="System.Net"/>
Un tag contrario a summary, para un volumen mayor de documentación es:
///<remarks>
Es posible incluir listas utilizando los tags :
///<list type="bullet">
85
/// <item>Constructor
/// <see cref="Constructor()"/>
/// .
/// </code>
/// .
///</example>
Para describir los parámetros de regreso utilice:
///<returns>
86
public void AsignarNombre(string sValor){
sNombre = sValor;
}
public string ObtenerNombre(){
return sNombre;
}
}
class AplicPersona{
public static void Main(){
Persona Empleado = new Persona();
Empleado.Sueldo = 33;
Console.WriteLine("Edad : " + Empleado.Sueldo);
N, denota un namespace
T, identifica un tipo, el cual puede ser una clase, interface, estructura,
enumeración o delegado
F, describe un campo o clase
P, se refiere a una propiedad la cual también puede ser un indíce o
propiedad índice.
M, identifica un método, incluyendo constructores y operadores.
E, denota eventos
!, denota un error el cual provee información acerca de una liga que el
compilador C# no pudo resolver.
87
major version.minor version.build number.revision
La versión compatible es utilizada por el class loader para decidir cual es la versión
del componente .NET que cargará, en caso de existir diferentes versiones.
Se considera una versión incompatible cuando major version.minor version es
diferente de la versión solicitada.
Se podría considerar una versión compatible cuando el build number es diferente a
la versión solicitada.
Se considera una QFE (Quick Fix Engineering) compatible cuando revision es
diferente.
Además del número de versión (versión compatible) se almacena otro número en el
componente llamado informational version, el cual es considerado sólo para
propósitos de documentación y su contenido podría ser SuperControl Build 1880, el
contenido representa algo humano y no para la máquina.
Para indicar al compilador que agregue un version information al componente se
utiliza el switch:
csc /a.version:1.0.1.0 /t:library /out:nombreArchivoFuente.dll nombreArchivoFuente.cs
El switch /a.version crea una biblioteca con el version information 1.0.1.0, esto puede
comprobarse en las propiedades del archivo.dll.
Componentes .NET Privados
Al ligar una aplicación a un componente .NET utilizando el switch
/reference:nombreBiblioteca la información de dependencia registra las herramientas
de desarrollo, incluyendo la versión de las bibliotecas ligadas, este registro se hace en
un manifiesto y el CLR los números de versión contenidos para cargar la versión
apropiada de un componente .NET dependiente en tiempo de ejecución.
Cualquier componente .NET que reside en el directorio de la aplicación es
considerado privado y no es version-checked
Componentes .NET Compartidos
Si se requiere construir software que se comparta con otras aplicaciones, el
componente .NET deberá ser instalado como compartido.
Interoperabilidad COM
COM es una técnica de interoperabilidad, por lo que los clientes .NET deberán ser
capaces de invocar componentes COM y componentes COM deberán hacer uso de
los nuevos componentes .NET, esta es una característica de iteroperabilidad
proporcionada por la plataforma .NET para todos los lenguajes de programación que
emiten código administrado.
Uso de Componentes .NET en Componentes COM
La interoperabilidad permite a clientes COM utilizar componentes .NET, para hacer
esto posible en COM primero es necesario registrar un objeto antes de poder ser
utilizado, para registrar un objeto COM se utiliza la aplicación regsvr32 y para registrar
un componente .NET se utiliza regasm.exe, esta herramienta permite registrar un
componente .NET en el Registry y también crear un archivo Registry.
Si requiere examinar las entradas agregadas al Registry escriba en la línea de
comandos:
regasm nombreComponente.dll /regfile:nombreArchivoComponente.reg
Ahora puede examinar el archivo generado nombreArchivoComponente.reg.
Nota asegurese de que el directorio en el que se encuentra exista el archivo .dll o
escriba la ruta completa de su ubicación.
Ejemplo:
REGEDIT4
88
[HKEY_CLASSES_ROOTinformatique.com.mx.iPersona]
@="informatique.com.mx.iPersona"
[HKEY_CLASSES_ROOTinformatique.com.mx.iPersonaCLSID]
@="{37504224-213A-3943-845A-E572758E4174}"
[HKEY_CLASSES_ROOTCLSID{37504224-213A-3943-845A-
E572758E4174}]
@="informatique.com.mx.iPersona"
[HKEY_CLASSES_ROOTCLSID{37504224-213A-3943-845A-
E572758E4174}InprocServer32]
@="mscoree.dll"
"ThreadingModel"="Both"
"Class"="informatique.com.mx.iPersona"
"Assembly"="iPersona, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null"
"RuntimeVersion"="v1.1.4322"
[HKEY_CLASSES_ROOTCLSID{37504224-213A-3943-845A-
E572758E4174}InprocServer32.0.0.0]
"Class"="informatique.com.mx.iPersona"
"Assembly"="iPersona, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null"
"RuntimeVersion"="v1.1.4322"
[HKEY_CLASSES_ROOTCLSID{37504224-213A-3943-845A-
E572758E4174}ProgId]
@="informatique.com.mx.iPersona"
[HKEY_CLASSES_ROOTCLSID{37504224-213A-3943-845A-
E572758E4174}Implemented Categories_
{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}]
El motor de ejecución mscoree.dll es invocado cuando una instancia del objeto
(componente registrado) es requerida, más no la biblioteca por si sóla.
El motor de ejecución es responsable de proveer la CCW (COM Callable Wrapper)
al objeto.
Una vez registrado el componente puede ser utilizado por lenguajes de programación
que soporten esta vinculación, también es posible emplear la utileria tlbexp la cual
permite generar una biblioteca tipo para el componente .NET
tlbexp nombreComponente.dll /out:nombreBiblioteca.tlb
Esta biblioteca tipo puede ser utilizada en lenguajes de programación que soporten
esta vinculación.
El Componente .NET y todas las clases ya están registradas y se tiene una biblioteca
tipo para ambientes
Uso de Componentes COM en Componentes .NET
Los Clientes .NET pueden interoperar con objetos clásicos COM, para que un
componente utilise COM debe tener una biblioteca tipo para el CLR esto se traduce a
los metadatos que son almacenados con los tipos.
Para que sea posible invocar un componente COM desde un objeto .NET, es
necesario envolver el código unsafe, cuando la envoltura o wrapper es invocada, un
RCW (Runtime Callable Wrapper) es construido desde la información de la biblioteca
89
tipo. Una herramienta genera el código wrapper basado en la información obtenida de
la biblioteca tipo.
La herramienta a utilizar es tlimp (type library import):
tlbimp nombreComponente.dll /out:nombreBiblioteca.dll
Esta herramienta importa el tipo de biblioteca COM, crea y almacena un RCW que
puede ser utilizado en el CLR en el archivo nombreBiblioteca.dll.
Para ver los metadatos para el RCW utilice ildasm.exe, así podrá distinguir el nombre
de la clase que fué generada para el objeto COM original, esta información es una
ayuda para poder escribir el objeto .NET que utiliza el componente COM.
Invocación de Servicios
Se se requiere invocar una función provista por el WIN32 o alguna DLL unmanaged,
se utilizan los Servicios de invocación de plataforma (PInvoke).
PInvoke se encarga de ejecutar la función correcta y también de la colocación de los
argumentos para y desde sus contrapartes unmanaged.
Simplemente utilice el atributo sysimport al definir un método externo:
[sysimport(
dll = nombreDLL,
name = puntoEntrada,
cgarset = conjuntoCaracteres
]
Unicamente el argumento dll es obligatorio y los demás opcionales, aunque si se
omite el atributo name, el nombre de la función de implementación externa debe
coincidir con el nombre del método estático interno.
Código No Seguro
Si requiere de escribir código no seguro, deberá utilizar dos palabras reservadas:
Debugging
El SDK .NET incorpora dos herramientas de depuración de errores:
90
Antes de depurar el código de una aplicación es necesario crear una versión
depurada, la cual contiene información de depuración no optimizada y un archivo
adicional PDB (program database) para depuración y un estado de información del
proyecto es creado.
Para crear una versión depurada son necesarios dos switches al compilar:
csc /optimize- /debug+ nombreArchivoFuente.cs
Estos comandos utilizados al compilar crea dos archivos:
nombreArchivoFuente.exe y
nombreArchivoFuente.pdb
Para configurar la sesión de depuración es necesario seleccionar la aplicación que se
desea depurar y comenzar el depurador SDK ejecutando dbgurt.exe, el cual esta
almacenado en el directorio ProgramFilesNGWSSDKGuiDebug
Una vez que la aplicación depuradora comienza se selecciona el programa que se
desea depurar en donde será también posible especificar argumentos en la línea de
comandos, mismos que son pasados a la aplicación cuando la sesión de depuración
inicia.
Es posible establecer diferentes tipos de breakpoint:
Step Over
Step Into
Step Out
Run to Cursor
91
.NET plantea dos posibles soluciones para la seguridad:
92
Permisos que son requeridos por el código, el beneficio de activar el
requerimiento de permisos es conocer cuando se tiene el permiso
apropiado para realizar acciones y cuando no. Es posible prevenir al
código de el otorgamiento de permisos adicionales que no son
necesarios. Los permisos mínimos garantizan que el código se ejecute
con los recursos justos cuando el código requiere de muchos permisos
sin que falle.
Permisos Estándar
Los Permisos Estándar son:
93
UIPermission, define el acceso a varios aspectos de la interfaz de usuario,
incluyendo el uso de windows, acceso a eventos y uso del portapapeles.
Permisos Identidad
Los Permisos Identidad son:
94
class Empleado{
string usr;
string pwd;
public Empleado(string login, string pwd){
this.usr = login;
this.pwd = pwd;
}
}
class App{
public static void Main(){
Empleado empleado = new Empleado("gangeles","123");
Console.WriteLine("Empleado : " + empleado);
}
}
Salida: Empleado : Empleado
La salida fue el nombre de la clase Empleado ya que es la representación más
cercana que encontro.
Es posible especificar algo con mayor sentido para ello se necesita redefinir la función
ToString():
using System;
class Empleado{
string usr;
string pwd;
public Empleado(string login, string pwd){
this.usr = login;
this.pwd = pwd;
}
public override string ToString(){
return("Usuario : " + usr + ", Password : " + pwd);
}
}
class App{
public static void Main(){
Empleado empleado = new Empleado("gangeles","123");
Console.WriteLine(empleado);
}
}
Salida: Usuario : gangeles, Password : 123
Función Equals()
La función Equals() es utilizada para determinar cuando dos objetos tienen el mismo
contenido.
En el siguiente ejemplo se redefinen las funciones operator==() y operator!=(), para
permitir la sintaxis del operador, estos operadores deben ser redefinidos en pares, no
pueden ser redefinidos separadamente.
Ejemplo:
using System;
class Empleado{
string usr;
string pwd;
public Empleado(string login, string pwd){
this.usr = login;
this.pwd = pwd;
}
95
public override string ToString(){
return("Usuario : " + usr + ", Password : " + pwd);
}
public override bool Equals(object o){
Empleado empB = (Empleado)o;
if(usr != empB.usr){return false;}
if(pwd != empB.pwd){return false;}
return true;
}
public static bool operator==(Empleado empA, Empleado empB){
return empA.Equals(empB);
}
public static bool operator!=(Empleado empA, Empleado empB){
return !empA.Equals(empB);
}
}
class App{
public static void Main(){
Empleado empleado1 = new Empleado("gangeles","123");
Empleado empleado2 = new Empleado("gangeles","123");
Console.WriteLine("El empleado1 es igual al empleado2:
"+empleado1.Equals(empleado2));
Console.Write("empleado1 == empleado2 : ");
Console.Write(empleado1 == empleado2);
}
}
Salida:
El empleado1 es igual al empleado2 : True
empleado1 == empleado2 : True
Al compilar el ejemplo anterior se presentara un warning indicando que no fue
redefinada la función GetHashCode() ya que los valores que regresa son requeridos
para ser relacionados al valor de regreso de Equals(). Cuando se invoca la función
Equals() y dos objetos son iguales siempre se debe regresar el mismo código hash.
Si no es redefinido el código hash podría ser sólo identico para la misma instancia de
un objeto y una búsqueda para un objeto que es igual pero no la misma instancia
podría fallar.
Es posible utilizar un miembro que es único para el código hash, pero si no existe un
valor único el código hash debería ser creado fuera de los valores contenidos en la
función.
Si la clase no tiene un identificador único pero tiene tiene otros campos, podrías ser
utilizados por la función hash:
using System;
class Empleado{
string usr;
string pwd;
public Empleado(string login, string pwd){
this.usr = login;
this.pwd = pwd;
}
public override string ToString(){
return("Usuario : " + usr + ", Password : " + pwd);
}
public override bool Equals(object o){
Empleado empB = (Empleado)o;
96
if(usr != empB.usr){return false;}
if(pwd != empB.pwd){return false;}
return true;
}
public static bool operator==(Empleado empA, Empleado empB){
return empA.Equals(empB);
}
public static bool operator!=(Empleado empA, Empleado empB){
return !empA.Equals(empB);
}
public override int GetHashCode(){
return usr.GetHashCode() + pwd.GetHashCode();
}
}
class App{
public static void Main(){
Empleado empleado1 = new Empleado("gangeles","123");
Empleado empleado2 = new Empleado("gangeles","123");
Console.WriteLine("El empleado1 es igual al empleado2:
"+empleado1.Equals(empleado2));
Console.Write("empleado1 == empleado2 : ");
Console.Write(empleado1 == empleado2);
}
}
La implementación del código GetHashCode anterior agrega los elementos y los
regresa.
Clase Hashtable
La clase Hashtable es muy utilizada para realizar una búsqueda de objetos por una
llave. Una tabla hash trabaja utilizando una función hash, la cual produce un entero
llave para una instancia específica de una clase, donde esta llave es una versión
condensada de la instancia.
Una tabla hash utiliza esta llave para limitar drasticamente el número de objetos que
deben ser buscados para encontrar un objeto específico en una colección de objetos.
Interface IHashCodeProvider
Si requiere definir diferentes códigos hash para un objeto específico, podría hacer
esto implementado la Interface IHashCodeProvider para proveer una función alterna
hash y además de que se requiere una coincidencia de la implementación de
IComparer, estas nuevas implementaciones son pasadas al contructor de la
Hashtable:
using System;
using System.Collections;
public class Lenguaje : IComparable{
public string nombre;
int id;
public Lenguaje(string nombre, int id){
this.nombre = nombre;
this.id = id;
}
97
return -1;
}else{
return 0;
}
}
class App{
public static void Main(){
Lenguaje[] aLenguaje = new Lenguaje[5];
aLenguaje[0] = new Lenguaje("C",3);
aLenguaje[1] = new Lenguaje("ActionScript",5);
aLenguaje[2] = new Lenguaje("JavaScript",2);
aLenguaje[3] = new Lenguaje("Java",8);
aLenguaje[4] = new Lenguaje("PHP",1);
98
lenguajes.Add(aLenguaje[0], "zxc");
lenguajes.Add(aLenguaje[1], "bnm");
lenguajes.Add(aLenguaje[2], "sdf");
lenguajes.Add(aLenguaje[3], "wer");
lenguajes.Add(aLenguaje[4], "tgh");
class Saludo{
public string s;
public Saludo(string s){
this.s = s;
}
}
class MiClase{
public Saludo saludo;
public MiClase(string s){this.saludo = new Saludo(s);}
public MiClase Clon(){return (MiClase)MemberwiseClone();}
}
class App{
public static void Main(){
MiClase miClase = new MiClase("Hello World!");
MiClase miClon = miClase.Clon();
Console.WriteLine("miClase : " + miClase.saludo.s);
Console.WriteLine("miClon : " + miClon.saludo.s);
miClon.saludo.s = "Hola Mundo";
Console.WriteLine("miClase : " + miClase.saludo.s);
Console.WriteLine("miClon : " + miClon.saludo.s);
}
}
Salida:
miClase : Hello World!
miClon : Hello World!
miClase : Hola Mundo
miClon : Hola Mundo
El resultado anterior es porque la copia hecha por la función MemberWiseClonre() es
una copia, el valor de saludo es el mismo en ambos objetos por lo que se se cambia
un valor dentro del objeto Saludo afecta ambas instancias de MiClase.
Interface ICloneable
Para crear una copia deep, donde una nueva instancia de Saludo es creada para para
la nueva instancia de MiClase, para ello se hace una implementación de la interface
ICloneable:
99
using System;
class Saludo{
public string s;
public Saludo(string s){
this.s = s;
}
}
class App{
public static void Main(){
MiClase miClase = new MiClase("Hello World!");
MiClase miClon = (MiClase) miClase.Clone();
Console.WriteLine("miClase : " + miClase.saludo.s);
Console.WriteLine("miClon : " + miClon.saludo.s);
miClon.saludo.s = "Hola Mundo!";
Console.WriteLine("miClase : " + miClase.saludo.s);
Console.WriteLine("miClon : " + miClon.saludo.s);
}
}
Salida:
miClase : Hello World!
miClon : Hello World!
miClase : Hello World!
miClon : Hola Mundo
La invocación a Memberwiseclone() regresa una nueva instancia de Saludo y su
contenido puede ser modificado sin afectar el contenido de miClase.
Note que en este caso ICloneable requiere implementar la función Clone().
Formato Numérico
Los tipos numéricos son formateados a través de la función miembro Format() del tipo
de dato, la cual puede ser invocada directamente a través de String.Format() la cual
invoca a la función Format() de cada tipo de dato o Console.WriteLine() la cual invoca
a String.Format().
Existen dos tipos de métodos para el formateo específico numérico:
Formato Estándar String
Formato Estándar String, el cual puede ser utilizado para convertir un tipo numérico
a una representación específica string.
Este formato consiste del formato específico del caracter seguido de la secuencia de
precisión específica de digitos, los formatos soportados son:
Formato Descripción Ejemplo Salida
Console.WriteLine("0:C",
C, c Currency $123,345.90
123.8977);
Console.WriteLine("0:D7"
D, d Decimal 0012345
, 12345);
100
Scientific
Console.WriteLine("0:E",
E, e (exponential 3.334590E+004
33345.8977);
)
Console.WriteLine("0:F",
E, f Fixed-point 3.334590E+004
33345.8977);
Console.WriteLine("0:G",
G, g General 33345.8977
33345.8977);
Console.WriteLine("0:N",
N, n Number 33,345.90
33345.8977);
Hexadecima Console.WriteLine("0:X",
X, x FF
l 255);
Formato Personalizado String
El Formato Personalizado String, son utilizados para obtener más control, sobre la
conversación que está disponible a través del formato estándar de strings.
Reemplazo de Cero o Digito
Console.WriteLine("{0:000}",12); Salida: 012
Reemplazo de Espacio o Digito
Console.WriteLine("{0:#####}",123); Salida: 123
Punto Decimal
Console.WriteLine("{0:#####.000}",12345.2); Salida: 12345.200
Separador de Grupo
Console.WriteLine("{0:##,###}",1123456789); Salida: 1,123,456,789
Separador de Grupo
Console.WriteLine("{0:000,.##}",12394); Salida: 123.95
Porcentaje
Console.WriteLine("{0:##.000%}",98144); Salida: 98.144%
Notación Exponencial
Console.WriteLine("{0:###.000E-00}",3.1415533E+04); Salida: 314.155E-02
Separador de Sección
Console.WriteLine("{0:###.00;0;(###.00)}",-456.55); Salida: 457
Escape y Literales
Console.WriteLine("{0:###\#}",255); Salida: 255#
Parseo Numérico
Los números pueden ser parseados utilizando el método Parse(), esto es posible ya
que lo provee el tipo de dato.
int iValue = Int32.Parse("123");
double dValue = Double.Parse("1.23");
Input/Output
El lenguaje Común en Tiempo de Ejecución .NET provee funciones de entrada/salida
en el namespace System.IO.
La lectura y escritura la realiza la clase Stream, la cual describe como los bytes
pueden ser escritos o leidos. Stream es una clase abstracta que en la práctica las
clases derivan de Stream para ser utilizadas.
Clases disponibles:
101
FileStream, flujo en un archivo de disco
MemoryStream, flujo que es almacenado en memoria
NetworkStream, flujo en una conexión de red
BufferedStream, implementa un buffer en la cima de otro stream.
using System;
using System.IO;
class App{
public static void Main(){
FileStream f = new FileStream("nuevo.txt",
FileMode.Create);
StreamWriter s = new StreamWriter(f);
for(int iNumberLine = 1; iNumberLine <= 10; iNumberLine+
+){
s.WriteLine("Linea " + iNumberLine);
}
s.Close();
f.Close();
}
}
Salida, Archivo Nuevo.txt cuyo contenido es:
Linea 1
Linea 2
Linea 3
Linea 4
Linea 5
Linea 6
Linea 7
Linea 8
Linea 9
Linea 10
Serialización
La Serialización es el proceso utilizado por el runtime para objetos persistentes en
algún orden de almacenamiento o para transferirlos de un lugar a otro.
La información de los metadatos en un objeto contiene información suficiente para
que el runtime serialice los campos, pero es necesario indicar al runtime hacerlo
correctamente a través de dos atributos [Serializable] el cual es utilizado para marcar
un objeto que es posible serializar y [NonSerialized] que es aplicado para un campo o
propiedad para indicar que no debería ser serializado.
Threading (Hilos)
102
El namespace System.Threading contiene clases utilizadas para threading y
sincronización. El tipo apropiado de sincronización y/o exclusión depende del diseño
del programa, pero C# soporta exclusión simple utilizando la sentencia lock.
lock utiliza la clase System.Threading.Monitor y provee funcionalidad similar a la
invocación de CriticalSection en Win32.
Agradecimientos a Juan Garcia por enviar el tutorial original de informatique.com.mx
103