Professional Documents
Culture Documents
(C# y Java)
La convención de nomenclatura para archivos que contienen clases de C# es un poco diferente a la de Java.
En Java, todos los archivos de código fuente tienen extensión .java. Cada archivo de código fuente contiene
una declaración de clase pública de nivel superior y el nombre de clase debe coincidir con el nombre de
archivo. Es decir, una clase denominada Customer declarada con un ámbito público se debe definir en un
archivo de código fuente con el nombre Customer.java.
La extensión .cs indica que se trata de código fuente de C#. A diferencia de Java, los archivos de código
fuente pueden contener más de una declaración de clase pública de nivel superior y no es necesario que el
nombre de archivo coincida con ninguno de los nombres de las clases.
En Java y C#, el código fuente comienza con algunas declaraciones de nivel superior en una secuencia
determinada. Existen sólo algunas diferencias entre las declaraciones realizadas en programas de Java y C#.
En Java, puede agrupar las clases con la palabra clave package. Una clase empaquetada debe utilizar la
palabra clave package en la primera línea ejecutable del archivo de código fuente. A continuación de ésta
viene cualquier instrucción de importación necesaria para tener acceso a clases de otros paquetes y, a
continuación, la declaración de clase, como se muestra a continuación:
package Acme;
import java.io.*;
class Customer
{
...
}
C# utiliza el concepto de espacio de nombres para agrupar clases relacionadas lógicamente mediante la
palabra clave namespace. Éstos actúan de forma similar a los paquetes de Java; además, podría aparecer una
clase con el mismo nombre en dos espacios de nombres diferentes. Para tener acceso a clases definidas en
un espacio de nombres externo al actual, utilice la directiva using seguida del nombre del espacio de
nombres, como se muestra a continuación:
C#
using System.IO;
namespace Acme
{
class Customer
{
// ...
}
}
Tenga en cuenta que las directivas using se pueden colocar dentro de una declaración de espacio de
nombres, en cuyo caso esos espacios de nombres importados forman parte del espacio de nombres
contenedor.
Java no permite varios paquetes en el mismo archivo de código fuente. Sin embargo, C# permite varios
espacios de nombres en un solo archivo .cs, como se muestra a continuación:
C#
namespace AcmeAccounting
{
public class GetDetails
{
// ...
}
}
namespace AcmeFinance
{
public class ShowDetails
{
// ...
}
}
Al igual que en Java, puede tener acceso a clases de .NET Framework o de espacios de nombres definidos
por el usuario sin una referencia using para ese espacio de nombres mediante el nombre completo de la
clase, como DataSet o AcmeAccounting.GetDetails en el ejemplo anterior.
Los nombres completos pueden ser largos y difíciles de manejar, en cuyo caso puede utilizar la palabra clave
using para especificar un nombre corto, o un alias, que haga el código más legible.
En el código siguiente, se crea un alias para hacer referencia al código escrito por una compañía ficticia:
C#
class OutputSales
{
static void Main()
{
int sales = DataTier.GetSales("January");
System.Console.WriteLine("January's Sales: {0}", sales);
}
}
Observe que en la sintaxis de WriteLine, con {x} en la cadena de formato, la x denota la posición en la lista
de argumentos en la que se va a insertar el valor. Si el método GetSales devolvió 500, el resultado de la
aplicación sería el siguiente:
January's Sales: 500
Directivas de preprocesamiento
Al igual que C y C++, C# incluye directivas de preprocesamiento que ofrecen la posibilidad de omitir
condicionalmente secciones de archivos de código fuente, informar de errores y condiciones de advertencia
y delimitar regiones características del código fuente. El término "directivas de preprocesamiento" sólo se
utiliza con el fin de mantener la coherencia con los lenguajes de programación C y C++, puesto que C# no
incluye un paso de preprocesamiento independiente. Para obtener más información, vea Directivas de
preprocesador de C#.
En este tema se describen algunas de las similitudes y diferencias principales en cómo se representan y
asignan los datos, y se recolectan los elementos no utilizados en Java y en C#.
El concepto de clase como tipo de datos compuesto de campos, métodos y eventos es similar en Java y C#.
(La herencia de clases se describe independientemente en el tema titulado Herencia y clases derivadas (C# y
Java).) C# introduce el concepto de estructura como tipo de datos compuesto asignado por pila que no
admite la herencia. En la mayoría de los otros aspectos, las estructuras son muy similares a las clases. Las
estructuras proporcionan una manera ligera de agrupar campos y métodos relacionados para el uso en los
bucles de pequeñas dimensiones y otros escenarios donde el rendimiento es crucial.
C# permite crear un método de destructor al que se llama antes de que se destruyan las instancias de una
clase. En Java, se puede utilizar un método finalize para contener código que limpia los recursos antes de
que se recolecten los elementos no utilizados del objeto. En C#, el que realiza esta función es el destructor
de clase. El destructor se parece a un constructor sin los argumentos y con un carácter de tilde delante (~).
C# proporciona todos los tipos de datos que están disponibles en Java y agrega compatibilidad para los
números sin signo y un nuevo tipo de punto flotante de 128 bits de alta precisión.
Para cada tipo de datos primitivo en Java, la biblioteca de clases principal proporciona una clase
contenedora, que lo representa como un objeto de Java. Por ejemplo, la clase Int32 contiene el tipo de datos
int y la clase Double contiene el tipo de datos double.
Por otro lado, todos los tipos de datos primitivos en C# son objetos en el espacio de nombres System. Para
cada tipo de datos, se proporciona un nombre corto o alias. Por ejemplo, int es el nombre corto
correspondiente a System.Int32 y double es la forma abreviada de System.Double.
En la tabla siguiente se proporciona la lista de tipos de datos de C# y sus alias. Como puede ver, los
primeros ocho de estos tipos corresponden a los tipos primitivos disponibles en Java. Sin embargo, tenga en
cuenta que el tipo boolean de Java se denomina bool en C#.
Nombre Clase
Tipo Ancho Intervalo (bits)
corto .NET
byte Byte Entero sin signo 8 0 a 255
sbyte SByte Entero con signo 8 -128 a 127
int Int32 Entero con signo 32 -2.147.483.648 a 2.147.483.647
uint UInt32 Entero sin signo 32 0 a 4294967295
short Int16 Entero con signo 16 -32.768 a 32.767
ushort UInt16 Entero sin signo 16 0 a 65535
-922337203685477508 a
long Int64 Entero con signo 64
922337203685477507
ulong UInt64 Entero sin signo 64 0 a 18446744073709551615
float Single Tipo de punto flotante de precisión simple 32 -3,402823e38 a 3,402823e38
-1,79769313486232e308 a
double Double Tipo de punto flotante de precisión doble 64
1,79769313486232e308
Símbolos Unicode utilizados en
char Char Un carácter Unicode 16
el texto
bool Boolean Tipo Boolean lógico 8 True o false
object Object Tipo base de todos los otros tipos
string String Una secuencia de caracteres
Tipo preciso fraccionario o integral, que
decimal Decimal puede representar números decimales con 128 ±1.0 × 10e−28 a ±7.9 × 10e28
29 dígitos significativos
Dado que C# representa todos los tipos de datos primitivos como objetos, es posible llamar a un método de
objeto de un tipo de datos primitivo. Por ejemplo:
C#
Esto se logra con la ayuda de las conversiones automáticas boxing y unboxing. Para obtener más
información, vea Conversión boxing y unboxing (Guía de programación de C#).
Constantes
Java y C# proporcionan la capacidad para declarar una variable cuyo valor se especifica en tiempo de
compilación y no se puede cambiar en tiempo de ejecución. Java utiliza el modificador de campo final para
declarar este tipo de variable, mientras que C# utiliza la palabra clave const. Además de const, C#
proporciona la palabra clave readonly para declarar variables a las que se puede asignar un valor una vez en
tiempo de ejecución, ya sea en la instrucción de declaración o en otra parte del constructor. Después de la
inicialización, el valor de una variable readonly no puede cambiar. Un escenario en el que las variables
readonly son útiles es cuando los módulos que se han compilado independientemente tienen que compartir
datos como un número de versión. Si el módulo A se actualiza y se vuelve a compilar con un nuevo número
de versión, el módulo B se puede inicializar con ese nuevo valor constante sin tener que volver a compilarlo.
Enumeraciones
Las enumeraciones se utilizan para agrupar constantes con nombres en forma similar a la forma en que se
utilizan en C y C++; no están disponibles en Java. En el ejemplo siguiente se define una enumeración Color
sencilla.
C#
También se pueden asignar valores integrales a las enumeraciones, tal como se muestra en la siguiente
declaración de enumeración:
C#
En el siguiente ejemplo de código se llama al método GetNames del tipo Enum para mostrar las constantes
disponibles para una enumeración. Luego, asigna un valor a una enumeración y muestra el valor.
C#
class TestEnums
{
static void Main()
{
System.Console.WriteLine("Possible color choices: ");
Resultado
Green
Orange
Red
Blue
Cadenas
Los tipos de cadena en Java y C# denotan un comportamiento similar con leves diferencias. Ambos tipos de
cadena son inmutables, lo que significa que los valores de las cadenas no se pueden cambiar una vez que se
han creado las cadenas. En ambos casos, los métodos que parecen modificar el contenido real de una cadena
crean en realidad una nueva cadena que se devolverá como resultado, dejando la cadena original sin
cambios. El proceso de comparación de los valores de cadena es diferente en C# y Java. Para comparar los
valores de cadena en Java, los desarrolladores deben llamar al método equals de un tipo string, mientras que
el operador == compara los tipos de referencia de forma predeterminada. En C#, los desarrolladores pueden
utilizar los operadores == o != para comparar directamente valores de cadena. Aunque una cadena es un tipo
de referencia en C#, los operadores == y != compararán, en forma predeterminada, los valores de las
cadenas en lugar de las referencias.
Como en Java, los desarrolladores de C# no deben usar el tipo string para concatenar cadenas con el fin de
evitar la sobrecarga de crear nuevas clases de cadenas cada vez que se concatene la cadena. En su lugar, los
desarrolladores pueden utilizar la clase StringBuilder, que es funcionalmente equivalente a la clase
StringBuffer de Java.
Literales de cadena
C# proporciona la posibilidad de evitar el uso de secuencias de escape como "\t" para la ficha o "\" para los
caracteres de barra diagonal inversa dentro de las constantes de cadena. Para ello, simplemente declare la
cadena textual mediante el símbolo @ para preceder la asignación del valor de cadena. Los siguientes
ejemplos muestran cómo utilizar los caracteres de escape y cómo asignar literales de cadena:
C#
Java y C# siguen reglas similares para la conversión automática y la conversión de tipos de datos.
Al igual que Java, C# admite conversiones de tipo implícitas y explícitas. En el caso de conversiones de
ampliación, las conversiones son implícitas. Por ejemplo, la siguiente conversión de int a long es implícita,
como en Java:
C#
int int1 = 5;
long long1 = int1; //implicit conversion
La siguiente es una lista de conversiones implícitas entre los tipos de datos de .NET Framework:
Puede convertir el tipo de expresiones que desee convertir explícitamente usando la misma sintaxis que en
Java:
C#
long long2 = 5483;
int int2 = (int)long2; //explicit conversion
Tipos de valor
Estos son los tipos de datos primitivos integrados, como char, int y float, así como también los tipos
definidos por el usuario declarados con la estructura.
Tipos de referencia
Clases y otros tipos de datos complejos que se construyen a partir de los tipos primitivos. Las
variables de estos tipos no contienen una instancia del tipo, sino sólo una referencia a una instancia.
Si se crean dos variables del tipo de valor, i y j, como se muestra a continuación, i y j son completamente
independientes entre sí:
C#
int i = 10;
int j = 20;
C#
int k = i;
Es decir, si cambia el valor de i, k permanecerá con el valor que tenía i en el momento de la asignación.
C#
i = 30;
System.Console.WriteLine(i.ToString()); // 30
System.Console.WriteLine(k.ToString()); // 10
Sin embargo, los tipos de referencia actúan de forma diferente. Por ejemplo, podría declarar dos variables de
la siguiente forma:
C#
Ahora, puesto que las clases son tipos de referencia de C#, ee1 se conoce como referencia a Employee. La
primera de las dos líneas anteriores crea una instancia de Employee en memoria y define ee1 para que haga
referencia a ella. Así, cuando se establece ee2 para que sea igual a ee1, el primero contiene un duplicado de
la referencia a la clase de la memoria. Si ahora cambia las propiedades de ee2, las propiedades de ee1
reflejan estos cambios, ya que ambas apuntan al mismo objeto de la memoria, tal como aparece a
continuación:
Conversiones boxing y unboxing
El proceso de convertir el tipo de un valor en el tipo de una referencia se denomina conversión boxing. El
proceso inverso, convertir el tipo de una referencia en el tipo de un valor, se denomina conversión unboxing.
Esto queda reflejado en el ejemplo de código siguiente:
C#
Java requiere que esas conversiones se realicen manualmente. Los tipos de datos primitivos se pueden
convertir en objetos de clases contenedoras construyendo esos objetos o aplicando la conversión boxing. De
igual manera, los valores de los tipos de datos primitivos se pueden extraer de los objetos de las clases
contenedoras llamando a un método adecuado de estos objetos, o realizar una conversión unboxing. Para
obtener más información acerca de las conversiones boxing y unboxing, vea Conversión boxing y unboxing
(Guía de programación de C#).
C# ofrece todos los operadores compatibles con Java aplicables, como se muestra en la tabla siguiente. Al
final de la tabla, verá algunos operadores nuevos disponibles en C#, pero no en Java:
Categoría Símbolo
Unario ++--+-!~()
Multiplicativo */%
Sumatorio +-
Desplazamiento << >>
Relacionales < > <= >= instanceof
Igualdad == !=
AND lógico &
XOR lógico ^
OR lógico |
AND condicional &&
OR condicional ||
Condicional ?:
Asignación = *= /= %= += -= <<= >>= &= ^= |=
Tipo de operando typeof
Tamaño del operando sizeof
Exigir comprobación de desbordamiento checked
Suprimir comprobación de desbordamiento unchecked
El único operador de Java no disponible en C# es el operador de desplazamiento (>>>). Este operador está
presente en Java debido a la ausencia de variables sin signo en ese lenguaje, para los casos en que se
requiere el desplazamiento a la derecha para insertar un uno (1) en los bits más significativos.
C# admite variables sin signo y, por consiguiente, sólo necesita el operador >> estándar. Este operador
genera resultados diferentes, lo que depende de si el operando está con o sin signo. Desplazar a la derecha
un número sin signo inserta un 0 en el bit más significativo, mientras que desplazar a la derecha un número
con signo copia el bit más significativo anterior.
Las operaciones aritméticas producirán desbordamiento si el resultado es demasiado grande para el número
de bits asignados al tipo de datos en uso. Ese desbordamiento se puede comprobar u omitir en una operación
aritmética integral dada con las palabras clave checked y unchecked. Si se trata de una expresión constante
que utiliza checked, se genera un error en tiempo de compilación.
C#
class TestCheckedAndUnchecked
{
static void Main()
{
short a = 10000;
short b = 10000;
En este código, el operador unchecked evita el error en tiempo de compilación que, de lo contrario, se
produciría por la instrucción siguiente:
C#
La siguiente expresión utiliza el operador unchecked de manera predeterminada, por lo que el valor produce
un desbordamiento pero no lo comunica:
C#
Con el operador checked, se puede exigir que se compruebe el desbordamiento de la expresión en tiempo de
ejecución:
C#
Al asignar los primeros dos valores a d y c se produce un desbordamiento, que no se comunica, con un valor
de -7936 cuando se ejecuta el programa, pero al intentar multiplicar el valor por e con checked(), el
programa producirá una excepción OverflowException.
Nota:
También puede controlar si desea comprobar el desbordamiento aritmético en un bloque de código con el
modificador de compiladores de línea de comandos (/checked) o directamente en Visual Studio por cada
proyecto.
Las instrucciones de control de flujo, como if else y switch, son muy similares en Java y C#.
Instrucciones de bifurcación
Las instrucciones de bifurcación cambian el flujo de la ejecución del programa en tiempo de ejecución según
ciertas condiciones.
La instrucción switch
Se debe tener en cuenta que donde un caso no especifica un código para ejecutar cuando coincide ese caso,
el control pasará al caso siguiente. Al utilizar goto en una instrucción switch, sólo se puede saltar a otro
bloque de casos en el mismo modificador. Si desea saltar al caso predeterminado, debe utilizar goto default.
De lo contrario, utilice goto case cond, donde cond es la condición que coincide con el caso al que desee
saltar. Otra diferencia con la instrucción switch de Java es que, en Java, sólo se pueden realizar cambios en
tipos enteros, mientras que C# permite realizar cambios en una variable de cadena.
C#
case "move":
//...
goto case "delete";
case "del":
case "remove":
case "delete":
//...
break;
default:
//...
break;
}
}
El valor que devuelve goto
En Java, goto es una palabra clave reservada que no se implementa. Sin embargo, puede utilizar
instrucciones con etiquetas break o continue para lograr un propósito similar a goto.
C# permite a la instrucción goto saltar a una instrucción con etiquetas. Sin embargo, se debe considerar que
para saltar a una etiqueta determinada, la instrucción goto debe estar dentro del ámbito de la etiqueta. Es
decir, goto no se puede utilizar para saltar a un bloque de instrucciones, aunque se puede saltar desde uno,
para saltar desde una clase o para salir del bloque finally en instrucciones try...catch. No se recomienda el
uso de goto en la mayoría de los casos, ya que contradice las buenas prácticas de la programación orientada
a objetos.
Las instrucciones de bucle repiten un bloque de código especificado hasta que se cumpla una condición
determinada.
Bucles for
C#
Bucles foreach
C# introduce un nuevo tipo de bucle denominado bucle foreach, que es similar a For Each de Visual Basic.
El bucle foreach permite la iteración a través de cada elemento en una clase contenedora, como una matriz,
que admite la interfaz IEnumerable. El código siguiente muestra el uso de la instrucción foreach para
obtener el contenido de una matriz:
C#
La sintaxis y el funcionamiento de las instrucciones while y do...while son iguales en ambos lenguajes:
C#
while (condition)
{
// statements
}
C#
do
{
// statements
}
while(condition); // Don't forget the trailing ; in do...while loops
Modificadores de acceso
Los modificadores de C# son bastante similares a los de Java, con varias diferencias pequeñas. Cada
miembro de una clase, o la propia clase, se puede declarar con un modificador de acceso para definir el
ámbito de acceso permitido. Las clases no declaradas dentro de otras clases sólo pueden especificar los
modificadores públicos o internos. Las clases anidadas, como otros miembros de clase, pueden especificar
cualquiera de los cinco modificadores de acceso siguientes:
public
protected
private
internal
protected internal
Visible sólo para el ensamblado actual o los tipos derivados de la clase contenedora.
Un modificador public permite que el miembro esté disponible en cualquier parte, tanto dentro como fuera
de la clase. Un modificador protected indica que el acceso está limitado al interior de la clase contenedora o
las clases derivadas de ésta. Un modificador private implica que el acceso sólo es posible desde dentro del
tipo contenedor. En C#, el modificador de acceso predeterminado es privado, mientras que en Java el acceso
se establece como predeterminado en cualquier parte desde dentro del paquete contenedor.
Modificador internal
A un elemento internal sólo se puede tener acceso desde dentro del ensamblado actual. Un ensamblado de
.NET Framework es casi equivalente a un archivo JAR de Java; representa los bloques de creación a partir
de los cuales se pueden crear otros programas.
Un elemento protected internal está visible sólo para el ensamblado actual o los tipos derivados de la clase
contenedora.
Modificador sealed
Una clase con el modificador sealed en la declaración de clase es lo opuesto a una clase abstracta: no se
puede heredar. Puede marcar una clase como sealed para evitar que otras clases reemplacen su
funcionalidad. Naturalmente, una clase con el modificador sealed no puede ser abstracta. Observe también
que a una estructura se le aplica implícitamente el modificador sealed; por consiguiente, no se puede
heredar. El modificador sealed es equivalente a marcar una clase con la palabra clave final en Java.
Modificador readonly
Para definir una constante en C#, utilice el modificador const o readonly en lugar de la palabra clave final de
Java. El factor distintivo entre los dos modificadores de C# es que los elementos const se tratan en tiempo de
compilación, mientras que valores de los campos readonly se especifican en tiempo de ejecución. Esto
significa que la asignación a los campos readonly se puede producir tanto en el constructor de clase como en
la declaración. Por ejemplo, la clase siguiente declara una variable readonly denominada IntegerVariable
que se inicializa en el constructor de clase:
C#
El método Main ()
Cada aplicación de C# debe contener un método Main único, que especifique dónde debe comenzar la
ejecución del programa. En C#, Main se pone en mayúsculas, mientras que Java utiliza main en minúscula.
Main puede devolver sólo int o void y tiene un argumento de matriz de cadena opcional para representar
parámetros de línea de comandos:
C#
El parámetro de matriz de cadena, que contiene todos los argumentos de la línea de comandos pasados,
funciona igual que en Java. Así, args[0] especifica el primer parámetro de línea de comandos, args[1] denota
el segundo parámetro, etc. A diferencia de C++, la matriz args no contiene el nombre del archivo EXE.
Otros métodos
Cuando se pasan parámetros a un método, se pueden pasar por valor o por referencia. Los parámetros de
valor simplemente toman el valor de cualquier variable para utilizarlo en el método. Por lo tanto, el valor de
variable en el código de llamada no se ve afectado por las acciones realizadas en los parámetros de un
método.
Sin embargo, los parámetros de referencia apuntan a una variable declarada en el código de llamada; por lo
tanto, los métodos modificarán el contenido de esa variable cuando se pase por referencia.
En Java y C#, los parámetros de método que hacen referencia a un objeto siempre se pasan por referencia,
mientras que los parámetros de tipo de datos primitivo (tipos de valor en C#) se pasan por valor.
En C#, para pasar un tipo de valor por referencia, debe especificar una de las palabras clave ref o out. La
diferencia entre estas dos palabras clave radica en la inicialización de los parámetros. Un parámetro ref se
debe inicializar antes de su utilización, mientras que un parámetro out no debe inicializarse explícitamente
sin que antes se haya pasado por referencia y se haya omitido cualquier valor anterior.
El código siguiente muestra un ejemplo de esto en el método Add, donde el segundo parámetro int se pasa
por referencia con la palabra clave ref:
C#
class TestRef
{
private static void Add(int i, ref int result)
{
result += i;
return;
}
El resultado de este sencillo ejemplo demuestra que los cambios realizados al parámetro resultante se
reflejan en la variable total, utilizada en la llamada al método Add :
Esto se debe a que el parámetro resultante hace referencia a la ubicación de memoria real que ocupa la
variable total en el código de llamada. Una propiedad de una clase no es una variable; por lo tanto, no se
puede utilizar directamente como parámetro ref.
La palabra clave ref debe preceder al parámetro cuando se llama al método, al igual que en la declaración de
método.
La palabra clave out tiene un efecto muy similar a la palabra clave ref. Las modificaciones realizadas a un
parámetro declarado que utiliza out serán visibles fuera del método. Las dos diferencias con respecto a ref
son que todo valor inicial de un parámetro out se omite dentro del método y que un parámetro out se debe
asignar durante la ejecución del método:
C#
class TestOut
{
private static void Add(int i, int j, out int result)
{
// The following line would cause a compile error:
// System.Console.WriteLine("Initial value inside method: {0}", result);
result = i + j;
return;
}
En este caso, el tercer parámetro para el método Add se declara con la palabra clave out y las llamadas al
método también necesitan la palabra clave out para ese parámetro. El resultado será:
Por lo tanto, en resumen, utilice la palabra clave ref cuando desee que un método modifique una variable
existente y utilice la palabra clave out, para devolver un valor generado dentro del método. Generalmente,
esto se utiliza junto con el valor que el método devuelve cuando éste genera más de un valor resultante para
el código de llamada.
Cuando el compilador intenta resolver una llamada al método, busca un método cuya lista de argumentos
coincida con el método llamado. Si no se puede encontrar una sobrecarga de métodos que coincida con la
lista de argumentos, pero hay una versión que coincide con un parámetro params del tipo apropiado, se
llamará a ese método y los argumentos adicionales se colocarán en una matriz.
C#
class TestParams
{
private static void Average(string title, params int[] values)
{
int sum = 0;
System.Console.Write("Average of {0} (", title);
En el ejemplo anterior, el método Average se declara con un parámetro params de matriz de tipo integer, lo
que permite llamarlo con cualquier número de argumentos. El resultado se muestra a continuación:
Average of List Two (5, 10, 15, 20, 25, 30, ): 17.5
Se puede especificar un parámetro params de tipo Object si se desea permitir parámetros indeterminados de
diferentes tipos.
En C#, una propiedad es un miembro con nombre de una clase, estructura o interfaz que ofrece una forma
ordenada de tener acceso a campos privados mediante lo que se denomina métodos de descriptor de
accesoget y set.
En el ejemplo de código siguiente declara una propiedad denominada Species para la clase Animal, que
resume el acceso a la variable privada denominada name:
C#
A menudo, la propiedad tendrá el mismo nombre que el miembro interno al que tiene acceso, pero con una
letra mayúscula inicial, por ejemplo, Name en el caso anterior o el miembro interno tendrá un prefijo _.
Observe también el parámetro implícito denominado value que se utiliza en el descriptor de acceso set; éste
tiene el tipo de la variable miembro subyacente.
De hecho, los descriptores de acceso se representan en forma interna como métodos get_X() y set_X() para
mantener la compatibilidad con los lenguajes basados en .NET Framework, que no admiten descriptores de
acceso. Una vez que una propiedad está definida, es muy fácil obtener o establecer su valor:
C#
class TestAnimal
{
static void Main()
{
Animal animal = new Animal();
animal.Species = "Lion"; // set accessor
System.Console.WriteLine(animal.Species); // get accessor
}
}
Si una propiedad sólo tiene un descriptor de acceso get, es una propiedad de sólo lectura. Si sólo tiene un
descriptor de acceso set, es una propiedad de sólo escritura. Si tiene ambos, es una propiedad de lectura y
escritura.
En el ejemplo siguiente, se inicializa struct con la palabra clave new, se llama al constructor predeterminado
sin parámetros y, a continuación, se establecen los miembros de la instancia.
C#
class TestCustomer
{
static void Main()
{
Customer c1 = new Customer(); //using the default constructor
c1.ID = 100;
c1.Name = "Robert";
Resultado
Cuando se compila y ejecuta el código anterior, el resultado muestra que las variables struct se inicializan de
manera predeterminada. La variable int se inicializa en 0 y la variable string se inicializa en una cadena
vacía:
ID = 0, Name =
Las matrices son colecciones ordenadas de elementos del mismo tipo de datos a los que se tiene acceso
utilizando el nombre de la matriz junto con el desplazamiento del elemento deseado desde el inicio de la
matriz. Hay algunas diferencias importantes entre C# y Java en lo referente a cómo se declaran y utilizan las
matrices.
Matriz unidimensional
Una matriz unidimensional almacena un número fijo de elementos en modo lineal, lo que requiere sólo un
valor de índice único para identificar cualquier elemento. En C#, los corchetes de la declaración de matriz
deben seguir al tipo de datos y no se pueden colocar después del nombre de variable, como se permite en
Java. Así, una matriz de tipo integers se declara mediante la siguiente sintaxis:
C#
int[] arr1;
C#
Una vez que se declara una matriz, se utiliza la palabra clave new para establecer su tamaño, como en Java.
En el ejemplo siguiente se declara la referencia de la matriz:
C#
int[] arr;
arr = new int[5]; // create a 5 element integer array
A continuación, se tiene acceso a los elementos de una matriz unidimensional mediante una sintaxis idéntica
a la de Java. Los índices de matriz de C# también se basan en cero. La sintaxis siguiente obtiene acceso al
último elemento de la matriz anterior:
C#
System.Console.WriteLine(arr[4]); // access the 5th element
Inicialización
C#
int[] arr2Lines;
arr2Lines = new int[5] {1, 2, 3, 4, 5};
C#
La otra manera de inicializar una matriz en C# es utilizar el bucle for. El siguiente bucle establece cada
elemento de una matriz en cero:
C#
Matrices escalonadas
Tanto C# como Java admiten la creación de matrices escalonadas o no rectangulares, en las que cada fila
contiene un número diferente de columnas. Por ejemplo, la siguiente matriz escalonada tiene cuatro entradas
en la primera fila y tres en la segunda:
C#
Matrices multidimensionales
Con C#, se pueden crear matrices multidimensionales normales que son como una matriz de valores del
mismo tipo. Mientras Java y C# admiten matrices escalonadas, C# también admite matrices
multidimensionales o matrices de matrices.
C#
C#
C#
arr2D[4,3] = 906;
Dado que las matrices se basan en cero, esta línea establece el elemento de la quinta columna de la cuarta
fila en 906.
Inicialización
Se pueden crear, configurar e inicializar matrices multidimensionales en una instrucción única a través de
uno de los métodos siguientes:
C#
Todos los elementos de una matriz se pueden inicializar con un bucle anidado, tal como se muestra aquí:
C#
La clase System.Array
En .NET Framework, las matrices se implementan como instancias de la clase Array. Esta clase proporciona
varios métodos útiles, como Sort y Reverse.
En el ejemplo siguiente se muestra lo fácil que es trabajar con estos métodos. En primer lugar, se invierten
los elementos de una matriz mediante el método Reverse y, a continuación, se ordenan con el método Sort:
C#
class ArrayMethods
{
static void Main()
{
// Create a string array of size 5:
string[] employeeNames = new string[5];
Resultados
Luca
Angie
Brian
Kent
Beatriz
La funcionalidad de una clase existente se puede extender al crear una nueva clase que se deriva de ella. La
clase derivada hereda las propiedades de la clase base y es posible agregar o reemplazar métodos y
propiedades según sea necesario.
En C#, el operador :, que equivale a extends e implements en Java, define la herencia e implementación de
interfaces. La clase base siempre debe estar en el extremo izquierdo en la declaración de clase.
Como Java, C# no admite herencia múltiple, lo que significa que las clases no pueden heredar más de una
clase. Sin embargo, se pueden utilizar interfaces para ese propósito, de la misma manera que en Java.
El código siguiente define una clase denominada CoOrds con dos variables miembro privadas x e y que
representan la posición del punto. Se tiene acceso a estas variables mediante propiedades denominadas X e
Y, respectivamente:
C#
public int X
{
get { return x; }
set { x = value; }
}
public int Y
{
get { return y; }
set { y = value; }
}
}
Una nueva clase, denominada ColorCoOrds, se deriva de la clase CoOrds del siguiente modo:
C#
Luego, ColorCoOrds hereda todos los campos y métodos de la clase base, a la cual se pueden agregar
nuevos campos y métodos para proporcionar características adicionales en la clase derivada, según sea
necesario. En este ejemplo, se agrega un miembro privado y descriptores de acceso para agregar color a la
clase:
C#
Como en Java, no se puede utilizar una referencia a una clase base para tener acceso a los miembros y
métodos de una clase derivada, aunque la referencia de la clase base pueda contener una referencia válida a
un objeto del tipo derivado.
Implícitamente, se puede hacer referencia a una clase derivada con una referencia al tipo derivado:
C#
En este código, la referencia de clase base, coords1, contiene una copia de la referencia color1.
Se puede tener acceso a los miembros de clase base en una subclase incluso cuando los miembros de base se
reemplazan en la superclase utilizando la palabra clave base. Por ejemplo, puede crear una clase derivada
que contenga un método con la misma firma que la clase base. Si se precede ese método con la palabra clave
new, se indica que se trata de un método totalmente nuevo que pertenece a la clase derivada. También se
podría proporcionar un método para tener acceso al método original de la clase base con la palabra clave
base.
Por ejemplo, supongamos que la clase base CoOrds tuviera un método denominado Invert() que intercambia
las coordenadas x e y. Se podría proporcionar un sustituto para este método en la clase derivada
ColorCoOrds con un código como éste:
C#
C#
A continuación, se invoca el método base en un objeto ColorCoOrds mediante una llamada al método
BaseInvert().
C#
Recuerde que se obtendría el mismo efecto si se asignara una referencia de la clase base a una instancia de
ColorCoOrds y, a continuación, se tuviera acceso a sus métodos:
C#
Los objetos de clase base siempre se construyen antes que cualquier clase derivada. De esta forma, el
constructor de la clase base se ejecuta antes que el constructor de la clase derivada. Si la clase base tiene más
de un constructor, la clase derivada puede decidir a qué constructor se va a llamar. Por ejemplo, podría
modificar la clase CoOrds para agregar un segundo constructor, del siguiente modo:
C#
public CoOrds()
{
x = 0;
y = 0;
}
C#
Reemplazar el método
Una clase derivada puede reemplazar el método de una clase base si se proporciona una nueva
implementación del método declarado. Una diferencia importante entre Java y C# es que, de forma
predeterminada, los métodos de Java se marcan como virtuales, mientras que en C# los métodos se deben
marcar explícitamente como virtuales con el modificador virtual. Los descriptores de acceso de propiedades,
así como los métodos, se pueden reemplazar de manera muy similar.
Métodos virtuales
Un método que será reemplazado en una clase derivada se declara con el modificador virtual. En una clase
derivada, el método reemplazado se declara con el modificador override.
El modificador override denota un método o propiedad de una clase derivada que reemplaza un método o
propiedad con el mismo nombre y firma en la clase base. El método base, que será reemplazado, se debe
declarar como virtual, abstract u override: no es posible reemplazar un método no virtual o estático de esta
forma. El método o la propiedad reemplazados y aquellos que se reemplazan deben tener los mismos
modificadores de nivel de acceso.
El ejemplo siguiente muestra un método virtual denominado StepUp que es reemplazado en una clase
derivada con el modificador que lo reemplaza:
C#
class TestCounters
{
static void Main()
{
CountClass counter1 = new CountClass(1);
CountClass counter100 = new Count100Class(1);
Cuando se ejecuta este código, se observa que el constructor de la clase derivada utiliza el cuerpo del
método proporcionado en la clase base, lo que permite inicializar el recuento de miembros sin duplicar el
código. Éste es el resultado:
Clases abstractas
Una clase abstracta declara uno o más métodos o propiedades como abstractos. La clase que declara dichos
métodos no les proporciona una implementación, aunque una clase abstracta también puede contener
métodos no abstractos, es decir, métodos para los que se ha proporcionado una implementación. No se
puede crear directamente una instancia de una clase abstracta; sólo se puede crear una instancia de una clase
derivada. Estas clases derivadas deben proporcionar implementaciones para todos los métodos y
propiedades abstractos, mediante la palabra clave override, a menos que el miembro derivado se declare
abstracto.
El ejemplo siguiente declara una clase abstracta Employee. También se crea una clase derivada denominada
Manager, que proporciona una implementación del método abstracto Show() definido en la clase Employee:
C#
class TestEmployeeAndManager
{
static void Main()
{
// Create an instance of Manager and assign it to a Manager reference:
Manager m1 = new Manager("H. Ackerman");
m1.Show();
Este código invoca la implementación del método Show() proporcionado por la clase Manager e imprime el
nombre del empleado en pantalla. Éste es el resultado:
Name : H. Ackerman
Name : M. Knott
Interfaces
Una interfaz es un tipo de clase esqueleto que contiene firmas de método pero no incluye ninguna
implementación de método. De esta manera, las interfaces son como clases abstractas que contienen sólo
métodos abstractos. Las interfaces de C# son muy similares a las de Java y funcionan de manera muy
similar.
Todos los miembros de una interfaz son públicos por definición y una interfaz no puede contener constantes,
campos (miembros de datos privados), constructores, destructores ni ningún tipo de miembro estático. El
compilador generará un error si se especifica un modificador para los miembros de una interfaz.
Las clases se pueden derivar de una interfaz para implementar esa interfaz. Estas clases derivadas deben
proporcionar implementaciones para todos los métodos de la interfaz, a menos que la clase derivada se
declare abstracta.
Una interfaz se declara de forma idéntica en Java. En una definición de interfaz, una propiedad indica sólo
su tipo y si es de sólo lectura, sólo escritura o de lectura y escritura únicamente por medio de las palabras
clave get y set. La interfaz siguiente declara una propiedad de sólo lectura:
C#
Una clase se puede heredar de esta interfaz utilizando dos puntos, en lugar de la palabra clave implements de
Java. La clase que se implementa debe proporcionar definiciones para todos los métodos y cualquier
descriptor de acceso de la propiedad necesario, del siguiente modo:
C#
C#
Si una clase implementa más de una interfaz donde hay ambigüedad en los nombres de los miembros, se
resuelve utilizando el calificador completo del nombre de la propiedad o método. Es decir, la clase derivada
puede resolver el conflicto si se utiliza el nombre completo del método para indicar a qué interfaz pertenece,
como en ICDPlayer.Play().
Un evento constituye un método para que una clase notifique a los usuarios de un objeto que algo interesante
sucede al objeto, como, por ejemplo, que se ha hecho clic en un control de una interfaz gráfica de usuario.
Esta notificación se denomina provocar un evento. El objeto que provoca un evento se conoce como el
origen o remitente del evento.
A diferencia del control de eventos en Java, que se realiza mediante la implementación de clases de agente
de escucha personalizadas, los programadores de C# pueden utilizar delegados para el control de eventos.
Un delegado es un tipo que encapsula un método. Una vez que un delegado se inicializa con un método, éste
se comporta exactamente como el método y puede invocarse con el operador (). Es similar a un puntero a
función de C++, pero posee seguridad de tipos.
El delegado se puede utilizar como cualquier otro método, con parámetros y un valor devuelto, como en este
ejemplo:
Para obtener más información sobre los delegados, vea Delegados (Guía de programación de C#).
Los eventos, al igual que los métodos, tienen una firma que incluye un nombre y una lista de parámetros.
Esta firma se define mediante un tipo delegado, por ejemplo:
En la programación de la interfaz de usuario de Windows, es común hacer que el primer parámetro se refiera
al origen del evento y que el segundo parámetro sea el objeto que contiene los datos relacionados con el
evento. Sin embargo, el lenguaje C# no requiere ni exige este diseño; una firma de evento puede ser la
misma que cualquier firma de delegado válida, siempre y cuando devuelva un valor void.
Un evento se puede declarar utilizando la palabra clave event como este ejemplo:
Para desencadenar el evento, defina el método que se va a invocar cuando el evento se provoca como en este
ejemplo:
Para provocar un evento, llame al delegado y pase los parámetros relacionados con el evento. A
continuación, el delegado llamará a todos los controladores que se hayan agregado al evento. Cada evento
puede tener más de un controlador asignado para recibir el evento. En este caso, el evento llama
automáticamente a cada receptor. Provocar un evento requiere sólo una llamada al evento sin tener en cuenta
el número de receptores.
Si desea que una clase reciba un evento, suscríbala a ese evento agregándole el delegado mediante el
operador +=, por ejemplo:
myEvent.TriggerIt += myEvent.MyMethod;
Para cancelar la suscripción a un evento, quite el delegado del evento utilizando al operador -=, por ejemplo:
Para obtener más información sobre eventos, vea Eventos (Guía de programación de C#).
Nota:
En C# 2.0, los delegados pueden encapsular métodos con nombre y métodos anónimos. Para obtener más
información sobre los métodos anónimos, vea Métodos anónimos (Guía de programación de C#).
Ejemplo
Descripción
El ejemplo siguiente define un evento con tres métodos asociados a él. Cuando se desencadena el evento, los
métodos se ejecutan. A continuación, se quita un método del evento y se desencadena de nuevo el evento.
Código
// Unsuscribe from the the event by removing the handler from the event:
myEvent.TriggerIt -= new MyEventHandler(myEvent.MyMethod2);
System.Console.WriteLine("\"Hello again!\" unsubscribed from the event.");
Resultado
Hello!
Hello again!
Good-bye!
"Hello again!" unsubscribed from the event.
Hello!
Good-bye!
Al igual que C++, C# permite sobrecargar operadores para utilizarlos en clases propias. Esto hace posible
que utilizar un tipo de datos definido por el usuario parezca tan natural y lógico como utilizar un tipo de
datos fundamental. Por ejemplo, podría crear un nuevo tipo de datos denominado ComplexNumber para
representar un número complejo y proporcionar métodos que realicen operaciones matemáticas en esos
números mediante operadores aritméticos estándar, como utilizar el operador + para sumar dos números
complejos.
Para sobrecargar un operador, se escribe una función que tenga el nombre del operador seguido del símbolo
del operador que se va a sobrecargar. Por ejemplo, a continuación se muestra cómo sobrecargar el operador
+:
C#
Todas las sobrecargas de operador son métodos estáticos de la clase. También tenga en cuenta que si
sobrecarga el operador de igualdad (==), también debe sobrecargar el operador de desigualdad (!=). Los
operadores < y >, y los operadores < = y > = también se deberían sobrecargar en pares.
El ejemplo de código siguiente crea una clase ComplexNumber que sobrecarga los operadores + y -:
C#
Esta clase permite crear y manipular dos números complejos con código de la manera siguiente:
C#
class TestComplexNumber
{
static void Main()
{
ComplexNumber a = new ComplexNumber(10, 12);
ComplexNumber b = new ComplexNumber(8, 9);
ComplexNumber sum = a + b;
System.Console.WriteLine("Complex Number sum = {0}", sum.ToString());
ComplexNumber difference = a - b;
System.Console.WriteLine("Complex Number difference = {0}",
difference.ToString());
}
}
Como se muestra en el programa, los operadores más y menos se pueden utilizar ahora casi intuitivamente
en objetos que pertenecen a la clase ComplexNumber. Éste es el resultado que se obtendría:
Complex Number b = 8 + 9i
Cada vez que sucede algo muy grave durante la ejecución de un programa, Common Language Runtime
(CLR) de .NET Framework crea un objeto Exception que detalla el error. En .NET Framework, Exception
es la clase base para todas las clases de excepción. Existen dos categorías de excepciones que derivan de la
clase Exception: SystemException y ApplicationException. Todos los tipos del espacio de nombres System
derivan de SystemException, mientras que las excepciones definidas por el usuario deben derivar de
ApplicationException para poder diferenciar entre los errores en tiempo de ejecución y los de aplicación.
Algunas excepciones System comunes incluyen:
Al igual que en Java, cuando se utiliza código que es responsable de producir una excepción, ese código se
tiene que colocar dentro de un bloque try . Inmediatamente después, uno o más bloques catch proporcionan
el control de errores. También puede utilizar un bloque finally para cualquier código que desee ejecutar, sin
importar si se produce o no una excepción. Para obtener más información, vea try-catch (Referencia de C#)
y try-catch-finally (Referencia de C#).
Cuando se utilizan varios bloques catch, las excepciones detectadas se deben colocar en orden creciente de
generalidad, ya que sólo se ejecutará el primer bloque catch que coincida con la excepción producida. El
compilador de C# forzará este proceso, mientras que el compilador de Java no lo hará.
Por ejemplo, al leer un archivo, podría encontrar una excepción FileNotFoundException o IOException, y
quizás desee colocar el identificador FileNotFoundException más específico en primer lugar, como se
muestra en el código siguiente:
C#
try
{
// code to open and read a file
}
catch (System.IO.FileNotFoundException e)
{
// handle the file not found exception first
}
catch (System.IO.IOException e)
{
// handle any other IO exceptions second
}
catch
{
// a catch block without a parameter
// handle all other exceptions last
}
finally
{
// this is executed whether or not an exception occurs
// use to release any external resources
}
Puede crear clases de excepción propias que deriven de Exception. Por ejemplo, el siguiente código crea una
clase InvalidDepartmentException que se podría producir, por ejemplo, si el departamento dado para un
nuevo Employee no es válido. El constructor de clase para la excepción definida por el usuario llama al
constructor de la clase base con la palabra clave base, que envía un mensaje adecuado:
C#
C#
class Employee
{
private string department;
C# no admite excepciones controladas. En Java, éstas se declaran con la palabra clave throws para
especificar que un método puede producir un tipo particular de excepción que el código de llamada debe
controlar.
C# proporciona algunas características de lenguaje útiles, como los indizadores, atributos y delegados, que
permiten emplear técnicas de programación avanzadas.
Indizadores
Los indizadores proporcionan una forma de tener acceso a class o struct de la misma manera que a una
matriz. Por ejemplo, se puede utilizar una clase que representa un departamento único en una compañía. La
clase podría contener los nombres de todos los empleados del departamento y los indizadores podrían
permitir el acceso a estos nombres, del siguiente modo:
C#
sales[0] = "Nikki";
sales[1] = "Becky";
Los indizadores se habilitan definiendo una propiedad con la firma siguiente, por ejemplo, en la definición
de clase:
C#
A continuación, se proporcionan los métodos get y set como se haría para una propiedad normal. Estos
descriptores de acceso son los que especifican a qué miembro interno se hace referencia cuando se utiliza el
indizador.
En el siguiente ejemplo, se crea una clase denominada Department que utiliza indizadores para tener acceso
a los empleados de ese departamento, representados internamente como una matriz de cadenas:
C#
A continuación, puede crear una instancia de esta clase y tener acceso a ella como se muestra en el ejemplo
de código siguiente:
C#
class TestDepartment
{
static void Main()
{
Department sales = new Department("Sales");
sales[0] = "Nikki";
sales[1] = "Becky";
El resultado es:
Atributos
C# proporciona un mecanismo, denominado atributo, para agregar información declarativa sobre los tipos.
Los atributos son de algún modo similares al concepto de anotaciones en Java. La información adicional
sobre un tipo se coloca dentro de etiquetas declarativas que preceden la definición de tipo. En los ejemplos
siguientes se muestra cómo utilizar los atributos de .NET Framework para decorar una clase o un método.
En el ejemplo siguiente, el método GetTime se marca como servicio Web XML al agregar el atributo
WebMethodAttribute.
C#
Agregar el atributo WebMethod provoca que .NET Framework se ocupe automáticamente del intercambio
de XML/SOAP necesario para llamar a esta función. Al llamar a este servicio Web, se recupera el valor
siguiente:
C#
[System.Serializable()]
public class Employee
{
public int ID;
public string Name;
[System.NonSerialized()] public int Salary;
}
Para obtener más información, vea Crear atributos personalizados (Guía de programación de C#).
Delegados
Los lenguajes como C++, Pascal y otros admiten el concepto de punteros a función, que le permiten elegir a
qué función desea llamar en tiempo de ejecución.
Java no proporciona ninguna construcción con la funcionalidad de puntero a función, pero C# sí lo permite.
A través del uso de la clase Delegate, una instancia de delegate encapsula un método que es una entidad
invocable.
En el caso de los métodos de instancia, el delegado consta de una instancia de la clase contenedora y un
método en la instancia. En los métodos estáticos, una entidad invocable consta de una clase y un método
estático en la clase. Así, se puede utilizar un delegado para invocar una función de cualquier objeto y los
delegados se orientan a objetos, tienen seguridad de tipos y son seguros.
Declaración
Creación de instancias
Invocación
C#
delegate void Del1();
Este delegado se puede utilizar posteriormente para hacer referencia a cualquier función que devuelve un
valor void y no toma ningún argumento.
Asimismo, a fin de crear un delegado para cualquier función que tome un parámetro de cadena y devuelva
un valor long, utilizaría la sintaxis siguiente:
C#
A continuación, podría asignar este delegado a cualquier método con esta firma, como:
C#
C#
Los objetos Delegate son inmutables; es decir, la firma con la que coinciden no se puede cambiar una vez
establecida. Sin embargo, puede señalar a otro método siempre y cuando ambos tengan la misma firma. En
este ejemplo, se reasigna d a un nuevo objeto de delegado para que d invoque el método DoMoreWork. Sólo
se puede hacer esto si DoWork y DoMoreWork tienen la misma firma.
C#
Invocar delegados
Invocar un delegado es bastante sencillo. Simplemente sustituya el nombre de la variable de delegado para
el nombre de método. Esto invoca el método Add con los valores 11 y 22, y devuelve un resultado de tipo
long que se asigna a la variable sum:
C#
C#
class TestMathClass
{
delegate long Del(int i, int j); // declare the delegate type
Resultado
11 + 22 = 33
30 * 40 = 1200
Una instancia de delegado debe contener una referencia de objeto. El ejemplo anterior soluciona esto al
declarar los métodos como estáticos, lo que significa que no hay necesidad de especificar una referencia de
objeto. Sin embargo, si un delegado hace referencia a un método de instancia, se debe proporcionar la
referencia de objeto del modo siguiente:
C#
C#
class TestMathClass
{
delegate long Del(int i, int j); // declare the delegate type
Resultado
Este ejemplo proporciona el mismo resultado que el ejemplo anterior, en el que los métodos se declararon
como estáticos.
11 + 22 = 33
30 * 40 = 1200
Delegados y eventos
.NET Framework también utiliza delegados en gran medida para las tareas de control de eventos, como un
evento de clic de un botón en una aplicación para Windows o Web. Mientras que el control de eventos de
Java se realiza generalmente implementando clases de agente de escucha personalizadas, los desarrolladores
de C# pueden aprovechar los delegados para el control de eventos. event se declara como un campo con un
tipo de delegado, sólo que la palabra clave event precede a la declaración del evento. Los eventos
generalmente se declaran como public, pero se permite cualquier modificador de accesibilidad. En el
ejemplo siguiente se muestra la declaración de delegate y event.
C#
Los delegados de evento son de multidifusión, lo que significa que pueden guardar referencias a más de un
método de control de eventos. Un delegado actúa como remitente de eventos de la clase que provoca el
evento y mantiene una lista de los controladores registrados para el evento. En el ejemplo siguiente se
muestra cómo suscribir varias funciones a un evento. La clase EventClass contiene el delegado, el evento y
un método para invocar el evento. Tenga en cuenta que un evento sólo se puede invocar desde la clase que
lo declaró. La clase TestEvents se puede suscribir luego al evento mediante el operador += y la suscripción
se puede cancelar con el operador -=. Cuando se llama al método InvokeEvent, éste desencadena el evento y
las funciones que se hayan suscrito al evento se desencadenarán en forma sincrónica, como se muestra en el
ejemplo siguiente.
C#
class TestEvents
{
private static void CodeToRun(object sender, System.EventArgs e)
{
System.Console.WriteLine("CodeToRun is executing");
}
System.Console.WriteLine("First Invocation:");
ec.InvokeEvent();
ec.CustomEvent -= new EventClass.CustomEventHandler(MoreCodeToRun);
System.Console.WriteLine("\nSecond Invocation:");
ec.InvokeEvent();
}
}
Resultado
First Invocation:
CodeToRun is executing
MoreCodeToRun is executing
Second Invocation:
CodeToRun is executing
En C y C++, es necesario que el programador asigne los recursos de varios objetos una vez declarados, antes
de que los objetos se puedan utilizar de forma segura. También es responsabilidad del programador volver a
liberar estos recursos para el bloque de espacio en memoria una vez utilizado el objeto. Si no se liberan los
recursos, se dice que el código tiene pérdida de memoria, porque cada vez más recursos se utilizan
inútilmente. Por otra parte, si los recursos se liberan de forma prematura, se puede producir pérdida de datos,
daños en otras áreas de memoria y excepciones de puntero nulo.
Java y C# evitan estos peligros al administrar independientemente el período de duración de todos los
objetos que una aplicación utiliza.
En Java, JVM se asegura de liberar la memoria no utilizada mediante el seguimiento de las referencias a los
recursos asignados. Cada vez que JVM detecta que una referencia válida ya no hace referencia a un recurso,
se recolectan los elementos no utilizados del recurso.
En C#, Common Language Runtime (CLR) controla la recolección de elementos no utilizados con una
funcionalidad similar a la de JVM. El recolector de elementos no utilizados de CLR busca periódicamente
en el montón de memoria cualquier objeto sin referencia y libera los recursos contenidos por estos objetos.
El código de C# que efectúa llamadas a la API de nivel inferior, utiliza aritmética de punteros o realiza
alguna otra operación poco segura, se debe colocar dentro de bloques marcados con la palabra clave unsafe.
Cualquiera de las siguientes situaciones se puede marcar como no segura:
Un método completo.
Un bloque de código entre llaves.
Una instrucción individual.
C#
class TestUnsafe
{
unsafe static void PointyMethod()
{
int i=10;
int *p = &i;
System.Console.WriteLine("*p = " + *p);
System.Console.WriteLine("Address of p = {0:X2}\n", (int)p);
}
unsafe
{
int *p = &i;
System.Console.WriteLine("*p = " + *p);
System.Console.WriteLine("Address of p = {0:X2}\n", (int)p);
}
}
En este código, el método PointyMethod() completo está marcado como no seguro porque declara y utiliza
punteros. El método StillPointy() marca un bloque de código como no seguro porque este bloque una vez
más utiliza punteros.
En código seguro, el recolector de elementos no utilizados tiene bastante libertad para mover un objeto
durante su duración con la misión de organizar y condensar recursos libres. Sin embargo, si el código utiliza
punteros, este comportamiento puede producir fácilmente resultados inesperados; por lo tanto, puede indicar
al recolector de elementos no utilizados que no mueva determinados objetos mediante la instrucción fixed.
En el código siguiente se muestra la palabra clave fixed, que se utiliza para garantizar que el sistema no
mueva una matriz durante la ejecución de un bloque de código en el método PointyMethod(). Observe que
fixed sólo se utiliza dentro de código no seguro:
C#
class TestFixed
{
public static void PointyMethod(char[] array)
{
unsafe
{
fixed (char *p = array)
{
for (int i=0; i<array.Length; i++)
{
System.Console.Write(*(p+i));
}
}
}
}
Aunque Microsoft y otros proveedores han introducido numerosos lenguajes para la plataforma .NET, C# es
un lenguaje muy similar a Java y muy adecuado para los desarrolladores que desean migrar de J2EE a la
plataforma .NET.
En este documento se comparan y se contrastan los dos lenguajes. En muchos aspectos, C# tiene la potencia
de C++, la elegancia de Java y la facilidad de desarrollo de Visual Basic; esperamos que este documento le
ayude a comprobarlo.