You are on page 1of 53

Convenciones para los archivos de código fuente

(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.

Declaraciones de nivel superior

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#.

Declaraciones de nivel superior en Java

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
{
...
}

Declaraciones de nivel superior en C#

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
{
// ...
}
}

Nombres completos y alias de espacios de nombres

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#

using DataTier = Acme.SQLCode.Client;

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#.

Tipos de datos (C# y Java)

Actualización: noviembre 2007

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#.

Tipos de datos compuestos

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 (~).

Tipos de datos integrados

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#

static void Main()


{
int i = 10;
object o = i;
System.Console.WriteLine(o.ToString());
}

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#

public enum Color


{
Green, //defaults to 0
Orange, //defaults to 1
Red, //defaults to 2
Blue //defaults to 3
}

También se pueden asignar valores integrales a las enumeraciones, tal como se muestra en la siguiente
declaración de enumeración:

C#

public enum Color2


{
Green = 10,
Orange = 20,
Red = 30,
Blue = 40
}

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: ");

//Enum.GetNames returns a string array of named constants for the enum.


foreach(string s in System.Enum.GetNames(typeof(Color)))
{
System.Console.WriteLine(s);
}

Color favorite = Color.Blue;

System.Console.WriteLine("Favorite Color is {0}", favorite);


System.Console.WriteLine("Favorite Color value is {0}", (int) favorite);
}
}

Resultado

Possible color choices:

Green

Orange

Red

Blue

Favorite Color is Blue

Favorite Color value is 3

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#

static void Main()


{
//Using escaped characters:
string path1 = "\\\\FileShare\\Directory\\file.txt";
System.Console.WriteLine(path1);

//Using String Literals:


string path2 = @"\\FileShare\Directory\file.txt";
System.Console.WriteLine(path2);
}

Conversión y conversión de tipos

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:

Tipo de origen Tipo de destino


Byte short, ushort, int, uint, long, ulong, float, double o decimal
Sbyte short, int, long, float, double o decimal
Int long, float, double o decimal
Uint long, ulong, float, double o decimal
Short int, long, float, double o decimal
Ushort int, uint, long, ulong, float, double o decimal
Long float, double o decimal
Ulong float, double o decimal
Float double
Char ushort, int, uint, long, ulong, float, double o decimal

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

La tabla siguiente muestra las conversiones explícitas.

Tipo de origen Tipo de destino


Byte sbyte o char
Sbyte byte, ushort, uint, ulong o char
Int sbyte, byte, short, ushort, uint, ulong o char
Uint sbyte, byte, short, ushort, int o char
Short sbyte, byte, ushort, uint, ulong o char
Ushort sbyte, byte, short o char
Long sbyte, byte, short, ushort, int, uint, ulong o char
Ulong sbyte, byte, short, ushort, int, uint, ulong o char
Float sbyte, byte, short, ushort, int, uint, long, ulong, char o decimal
Double sbyte, byte, short, ushort, int, uint, long, ulong, char o decimal
Char sbyte, byte o short
Decimal sbyte, byte, short, ushort, int, uint, long, ulong, char, float o double

Tipos de referencia y valor

C# admite dos tipos de variables:

 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;

Tienen ubicaciones de memoria independiente:


Si cambia el valor de una de estas variables, la otra no se verá afectada de forma natural. Por ejemplo, si
tiene una expresión como la siguiente, aún no existe ninguna conexión entre las variables:

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#

Employee ee1 = new Employee();


Employee ee2 = ee1;

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#

int i = 123; // a value type


object o = i; // boxing
int j = (int)o; // unboxing

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#).

Operadores (C# y Java)


Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007

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.

Operadores Checked y Unchecked

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.

El siguiente es un ejemplo simple que muestra el uso de estos operadores:

C#

class TestCheckedAndUnchecked
{
static void Main()
{
short a = 10000;
short b = 10000;

short c = (short)(a * b); // unchecked by default


short d = unchecked((short)(10000 * 10000)); // unchecked
short e = checked((short)(a * b)); // checked - run-time error

System.Console.WriteLine(10000 * 10000); // 100000000


System.Console.WriteLine(c); // -7936
System.Console.WriteLine(d); // -7936
System.Console.WriteLine(e); // no result
}
}

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#

short d = unchecked((short)(10000 * 10000)); // unchecked

La siguiente expresión utiliza el operador unchecked de manera predeterminada, por lo que el valor produce
un desbordamiento pero no lo comunica:

C#

short c = (short)(a * b); // unchecked by default

Con el operador checked, se puede exigir que se compruebe el desbordamiento de la expresión en tiempo de
ejecución:

C#

short e = checked((short)(a * b)); // checked - run-time error

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.

Control de flujo (C# y Java)


Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007

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.

if, else y else if

Estas instrucciones son idénticas en ambos lenguajes.

La instrucción switch

En ambos lenguajes, la instrucción switch proporciona múltiples operaciones de bifurcación condicionales.


Sin embargo, existe una diferencia en cuanto a que Java permite "pasar explícitamente" un caso y ejecutar el
siguiente, a menos que se utilice una instrucción break al final del caso. En cambio, C# exige el uso de una
instrucción break o goto al final de cada caso y, si no hay ninguna presente, el compilador produce el
siguiente error:

El control no puede pasar explícitamente de una etiqueta de caso a otra.

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.

Por ejemplo, lo siguiente sería válido en C#, pero no en Java:

C#

static void Main(string[] args)


{
switch (args[0])
{
case "copy":
//...
break;

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.

Instrucciones de bucle (C# y Java)


Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007

Las instrucciones de bucle repiten un bloque de código especificado hasta que se cumpla una condición
determinada.

Bucles for

La sintaxis y el funcionamiento de los bucles for son iguales en C# y Java:

C#

for (int i = 0; i<=9; i++)


{
System.Console.WriteLine(i);
}

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#

static void Main()


{
string[] arr= new string[] {"Jan", "Feb", "Mar"};

foreach (string s in arr)


{
System.Console.WriteLine(s);
}
}

Para obtener más información, vea Matrices (C# y Java).

Bucles while y do...while

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

Fundamentos de las clases (C# y Java)


Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007

En las secciones siguientes se comparan los modificadores de C# y Java.

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

Visible para todos.

 protected

Visible sólo desde clases derivadas.

 private

Visible sólo dentro de la clase dada.

 internal

Visible sólo dentro del mismo ensamblado.

 protected internal

Visible sólo para el ensamblado actual o los tipos derivados de la clase contenedora.

Modificadores public, protected y private

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.

Modificador protected internal

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#

public class SampleClass


{
private readonly int intConstant;

public SampleClass () //constructor


{
// You are allowed to set the value of the readonly variable
// inside the constructor
intConstant = 5;
}

public int IntegerConstant


{
set
{
// You are not allowed to set the value of the readonly variable
// anywhere else but inside the constructor

// intConstant = value; // compile-time error


}
get
{
return intConstant;
}
}
}
class TestSampleClass
{
static void Main()
{
SampleClass obj= new SampleClass();

// You cannot perform this operation on a readonly field.


obj.IntegerConstant = 100;

System.Console.WriteLine("intConstant is {0}", obj.IntegerConstant); // 5


}
}

Si se aplica un modificador readonly a un campo estático, se debe inicializar en el constructor estático de la


clase.

Main () y otros métodos (C# y Java)


Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007


En esta sección se analizan algunos métodos y la forma en que los parámetros del método se pasan por
referencia y por valor.

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#

static int Main(string[] args)


{
//...
return 0;
}

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.

Pasar 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.

La palabra clave ref


Especifique esta palabra clave en un parámetro de tipo de valor cuando desee que el método llamado cambie
permanentemente el valor de las variables utilizadas como parámetros. De esta manera, en lugar de pasar el
valor de una variable utilizada en la llamada, se pasa una referencia a la propia variable. Entonces el método
funciona en la referencia, de modo que los cambios realizados al parámetro durante la ejecución del método
se conservan en la variable original utilizada como parámetro para el método.

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;
}

static void Main()


{
int total = 20;
System.Console.WriteLine("Original value of 'total': {0}", total);

Add(10, ref total);


System.Console.WriteLine("Value after calling Add(): {0}", total);
}
}

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 :

Original value of 'total': 20

Value after calling Add(): 30

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

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;
}

static void Main()


{
int total = 20;
System.Console.WriteLine("Original value of 'total': {0}", total);

Add(33, 77, out total);


System.Console.WriteLine("Value after calling Add(): {0}", total);
}
}

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á:

Original value of 'total': 20

Value after calling Add(): 110

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.

Utilizar un número indeterminado de parámetros


(C# y Java)
Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007

C# permite enviar a un método un número variable de parámetros mediante la especificación de la palabra


clave params cuando se declara el método. La lista de argumentos también puede contener parámetros
normales, pero se debe tener en cuenta que el parámetro declarado con la palabra clave params debe ir en
último lugar. Toma la forma de una matriz de longitud variable y sólo puede haber un parámetro params por
cada método.

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.

En el ejemplo siguiente se ilustra esta idea:

C#

class TestParams
{
private static void Average(string title, params int[] values)
{
int sum = 0;
System.Console.Write("Average of {0} (", title);

for (int i = 0; i < values.Length; i++)


{
sum += values[i];
System.Console.Write(values[i] + ", ");
}
System.Console.WriteLine("): {0}", (float)sum/values.Length);
}
static void Main()
{
Average ("List One", 5, 10, 15);
Average ("List Two", 5, 10, 15, 20, 25, 30);
}
}

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 One (5, 10, 15, ): 10

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.

Propiedades (C# y Java)


Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007

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#

public class Animal


{
private string name;

public string Species


{
get
{
return name;
}
set
{
name = value;
}
}
}

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.

Structs (C# y Java)


Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007


C# admite la palabra clave struct, que es otro elemento que se origina en C pero no está disponible en Java.
struct se puede considerar como una clase ligera. Aunque las structs pueden contener constructores,
constantes, campos, métodos, propiedades, indizadores, operadores y tipos anidados, se utilizan
principalmente para encapsular grupos de campos relacionados. Dado que las estructuras son tipos de valor,
se pueden asignar de un modo ligeramente más eficaz que las clases. Las structs difieren de las clases en que
no pueden ser abstractas y no admiten la herencia de implementaciones.

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#

public struct Customer


{
public int ID;
public string Name;

public Customer(int customerID, string customerName)


{
ID = customerID;
Name = customerName;
}
}

class TestCustomer
{
static void Main()
{
Customer c1 = new Customer(); //using the default constructor

System.Console.WriteLine("Struct values before initialization:");


System.Console.WriteLine("ID = {0}, Name = {1}", c1.ID, c1.Name);
System.Console.WriteLine();

c1.ID = 100;
c1.Name = "Robert";

System.Console.WriteLine("Struct values after initialization:");


System.Console.WriteLine("ID = {0}, Name = {1}", c1.ID, c1.Name);
}
}

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:

Struct values before initialization:

ID = 0, Name =

Struct values after initialization:

ID = 100, Name = Robert


Matrices (C# y Java)
Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007

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;

La siguiente declaración no es válida en C#:

C#

//int arr2[]; //compile error

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

Los elementos de la matriz de C# se pueden inicializar en el momento de su creación mediante la misma


sintaxis que Java:

C#

int[] arr2Lines;
arr2Lines = new int[5] {1, 2, 3, 4, 5};

A diferencia de Java, el número de inicializadores de C# debe coincidir exactamente con el tamaño de la


matriz. Puede utilizar esta característica para declarar e inicializar una matriz de C# en una sola línea:

C#

int[] arr1Line = {1, 2, 3, 4, 5};

Esta sintaxis crea una matriz de tamaño igual al número de inicializadores.

Inicializar en un bucle del programa

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#

int[] TaxRates = new int[5];

for (int i=0; i<TaxRates.Length; i++)


{
TaxRates[i] = 0;
}

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#

int[][] jaggedArray = new int[2][];


jaggedArray[0] = new int[4];
jaggedArray[1] = new int[3];

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.

Declare una matriz rectangular multidimensional mediante la siguiente sintaxis:

C#

int[,] arr2D; // declare the array reference


float[,,,] arr4D; // declare the array reference

Una vez declarada, asigne memoria a la matriz del modo siguiente:

C#

arr2D = new int[5,4]; // allocate space for 5 x 4 integers

A continuación, se tiene acceso a los elementos de la matriz con la sintaxis siguiente:

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#

int[,] arr4 = new int [2,3] { {1,2,3}, {4,5,6} };


int[,] arr5 = new int [,] { {1,2,3}, {4,5,6} };
int[,] arr6 = { {1,2,3}, {4,5,6} };
Inicializar en un bucle del programa

Todos los elementos de una matriz se pueden inicializar con un bucle anidado, tal como se muestra aquí:

C#

int[,] arr7 = new int[5,4];

for(int i=0; i<5; i++)


{
for(int j=0; i<4; j++)
{
arr7[i,j] = 0; // initialize each element to zero
}
}

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];

// Read 5 employee names from user:


System.Console.WriteLine("Enter five employee names:");
for(int i=0; i<employeeNames.Length; i++)
{
employeeNames[i]= System.Console.ReadLine();
}

// Print the array in original order:


System.Console.WriteLine("\nArray in Original Order:");
foreach(string employeeName in employeeNames)
{
System.Console.Write("{0} ", employeeName);
}

// Reverse the array:


System.Array.Reverse(employeeNames);

// Print the array in reverse order:


System.Console.WriteLine("\n\nArray in Reverse Order:");
foreach(string employeeName in employeeNames)
{
System.Console.Write("{0} ", employeeName);
}

// Sort the array:


System.Array.Sort(employeeNames);

// Print the array in sorted order:


System.Console.WriteLine("\n\nArray in Sorted Order:");
foreach(string employeeName in employeeNames)
{
System.Console.Write("{0} ", employeeName);
}
}
}

Resultados

Enter five employee names:

Luca

Angie

Brian

Kent

Beatriz

Array in Original Order:

Luca Angie Brian Kent Beatriz

Array in Reverse Order:

Beatriz Kent Brian Angie Luca

Array in Sorted Order:

Angie Beatriz Brian Kent Luca

Herencia y clases derivadas (C# y Java)


Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007

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 class CoOrds


{
private int x, y;

public CoOrds() // constructor


{
x = 0;
y = 0;
}

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#

public class ColorCoOrds : CoOrds

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#

public class ColorCoOrds : CoOrds


{
private System.Drawing.Color screenColor;

public ColorCoOrds() // constructor


{
screenColor = System.Drawing.Color.Red;
}

public System.Drawing.Color ScreenColor


{
get { return screenColor; }
set { screenColor = value; }
}
}

El constructor de la clase derivada llama implícitamente al constructor de la clase base o la superclase, en


terminología de Java. En caso de herencia, se llama a todos los constructores de clase base antes que a los
constructores de la clase derivada en el orden en que las clases aparecen en la jerarquía de clases.

Convertir un tipo a una clase base

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#

ColorCoOrds color1 = new ColorCoOrds();


CoOrds coords1 = color1;

En este código, la referencia de clase base, coords1, contiene una copia de la referencia color1.

La palabra clave base

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#

public new void Invert()


{
int temp = X;
X = Y;
Y = temp;
screenColor = System.Drawing.Color.Gray;
}
Como se puede observar, este método intercambia x e y, luego establece el color del punto en gris. Se podría
proporcionar acceso a la implementación base para este método creando otro método en ColorCoOrds, como
el de este ejemplo:

C#

public void BaseInvert()


{
base.Invert();
}

A continuación, se invoca el método base en un objeto ColorCoOrds mediante una llamada al método
BaseInvert().

C#

ColorCoOrds color1 = new ColorCoOrds();


color1.BaseInvert();

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#

CoOrds coords1 = color1;


coords1.Invert();

Seleccionar los constructores

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 class CoOrds


{
private int x, y;

public CoOrds()
{
x = 0;
y = 0;
}

public CoOrds(int x, int y)


{
this.x = x;
this.y = y;
}
}
Luego, podría cambiar la clase ColorCoOrds para utilizar uno de los constructores disponibles mediante la
palabra clave base:

C#

public class ColorCoOrds : CoOrds


{
public System.Drawing.Color color;

public ColorCoOrds() : base ()


{
color = System.Drawing.Color.Red;
}

public ColorCoOrds(int x, int y) : base (x, y)


{
color = System.Drawing.Color.Red;
}
}

En Java, esta funcionalidad se implementa con la palabra clave super.

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#

public class CountClass


{
public int count;

public CountClass(int startValue) // constructor


{
count = startValue;
}

public virtual int StepUp()


{
return ++count;
}
}

class Count100Class : CountClass


{
public Count100Class(int x) : base(x) // constructor
{
}

public override int StepUp()


{
return ((base.count) + 100);
}
}

class TestCounters
{
static void Main()
{
CountClass counter1 = new CountClass(1);
CountClass counter100 = new Count100Class(1);

System.Console.WriteLine("Count in base class = {0}", counter1.StepUp());


System.Console.WriteLine("Count in derived class = {0}", counter100.StepUp());
}
}

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:

Count in base class = 2

Count in derived class = 101

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#

public abstract class Employee


{
protected string name;

public Employee(string name) // constructor


{
this.name = name;
}

public abstract void Show(); // abstract show method


}

public class Manager: Employee


{
public Manager(string name) : base(name) {} // constructor

public override void Show() //override the abstract show method


{
System.Console.WriteLine("Name : " + name);
}
}

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();

// Create an instance of Manager and assign it to an Employee reference:


Employee ee1 = new Manager("M. Knott");
ee1.Show(); //call the show method of the Manager class
}
}

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#

public interface ICDPlayer


{
void Play(); // method signature
void Stop(); // method signature

int FastForward(float numberOfSeconds);

int CurrentTrack // read-only property


{
get;
}
}

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#

public class CDPlayer : ICDPlayer


{
private int currentTrack = 0;

// implement methods defined in the interface


public void Play()
{
// code to start CD...
}

public void Stop()


{
// code to stop CD...
}

public int FastForward(float numberOfSeconds)


{
// code to fast forward CD using numberOfSeconds...

return 0; //return success code


}

public int CurrentTrack // read-only property


{
get
{
return currentTrack;
}
}

// Add additional methods if required...


}
Implementar múltiples interfaces

Una clase puede implementar múltiples interfaces mediante la sintaxis siguiente:

C#

public class CDAndDVDComboPlayer : ICDPlayer, IDVDPlayer

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().

Eventos (C# y Java)


Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007

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:

public delegate int ReturnResult(int x, int y);

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:

public delegate void MyEventHandler(object sender, System.EventArgs e);

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:

public event MyEventHandler TriggerIt;

Para desencadenar el evento, defina el método que se va a invocar cuando el evento se provoca como en este
ejemplo:

public void Trigger()


{
TriggerIt();
}

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:

myEvent.TriggerIt -= new MyEventHandler(myEvent.MyMethod);

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

// Declare the delegate handler for the event:


public delegate void MyEventHandler();
class TestEvent
{
// Declare the event implemented by MyEventHandler.
public event MyEventHandler TriggerIt;

// Declare a method that triggers the event:


public void Trigger()
{
TriggerIt();
}
// Declare the methods that will be associated with the TriggerIt event.
public void MyMethod1()
{
System.Console.WriteLine("Hello!");
}
public void MyMethod2()
{
System.Console.WriteLine("Hello again!");
}
public void MyMethod3()
{
System.Console.WriteLine("Good-bye!");
}

static void Main()


{
// Create an instance of the TestEvent class.
TestEvent myEvent = new TestEvent();

// Subscribe to the event by associating the handlers with the events:


myEvent.TriggerIt += new MyEventHandler(myEvent.MyMethod1);
myEvent.TriggerIt += new MyEventHandler(myEvent.MyMethod2);
myEvent.TriggerIt += new MyEventHandler(myEvent.MyMethod3);
// Trigger the event:
myEvent.Trigger();

// 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.");

// Trigger the new event:


myEvent.Trigger();
}
}

Resultado

Hello!
Hello again!
Good-bye!
"Hello again!" unsubscribed from the event.
Hello!
Good-bye!

Sobrecarga de operadores (C# y Java)


Otras versiones
 Visual Studio 2005

Actualización: noviembre 2007

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#

public static ComplexNumber operator+(ComplexNumber a, ComplexNumber b)

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.

La lista completa de operadores que se pueden sobrecargar es la siguiente:

 Operadores unarios: +, -, !, ~, ++, --, true, false


 Operadores binarios: +, -, *, /, %, &, |, ^, <<, >>, ==, !=, >, <, >=, <=

El ejemplo de código siguiente crea una clase ComplexNumber que sobrecarga los operadores + y -:

C#

public class ComplexNumber


{
private int real;
private int imaginary;

public ComplexNumber() : this(0, 0) // constructor


{
}

public ComplexNumber(int r, int i) // constructor


{
real = r;
imaginary = i;
}

// Override ToString() to display a complex number in the traditional format:


public override string ToString()
{
return(System.String.Format("{0} + {1}i", real, imaginary));
}

// Overloading '+' operator:


public static ComplexNumber operator+(ComplexNumber a, ComplexNumber b)
{
return new ComplexNumber(a.real + b.real, a.imaginary + b.imaginary);
}
// Overloading '-' operator:
public static ComplexNumber operator-(ComplexNumber a, ComplexNumber b)
{
return new ComplexNumber(a.real - b.real, a.imaginary - b.imaginary);
}
}

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);

System.Console.WriteLine("Complex Number a = {0}", a.ToString());


System.Console.WriteLine("Complex Number b = {0}", b.ToString());

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 a = 10 + 12i

Complex Number b = 8 + 9i

Complex Number sum = 18 + 21i

Complex Number difference = 2 + 3i

Java no admite la sobrecarga de operadores, aunque internamente sobrecarga el operador + para la


concatenación de cadenas.

Excepciones (C# y Java)


Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007


El control de excepciones en C# es muy similar al de Java.

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:

 IndexOutOfRangeException: se utiliza un índice mayor que el tamaño de una matriz o colección.


 NullReferenceException: se utilizó una propiedad o método de una referencia antes de que esa
referencia se haya establecido en una instancia válida.
 ArithmeticException: una operación produce desbordamiento o subdesbordamiento.
 FormatException: un argumento u operando está en formato incorrecto.

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á.

A diferencia de Java, C# no requiere un argumento para un bloque catch; si no se especifica ningún


argumento, el bloque catch se aplica a cualquier clase Exception.

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#

public class InvalidDepartmentException : System.Exception


{
public InvalidDepartmentException(string department) : base("Invalid Department: "
+ department)
{
}
}

A continuación, podría producir una excepción con código como el siguiente:

C#

class Employee
{
private string department;

public Employee(string department)


{
if (department == "Sales" || department == "Marketing")
{
this.department = department;
}
else
{
throw new InvalidDepartmentException(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.

Técnicas avanzadas de C# (C# y Java)


Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007

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#

public string this [int index] //indexer

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#

public class Department


{
private string name;
private const int MAX_EMPLOYEES = 10;
private string[] employees = new string[MAX_EMPLOYEES]; //employee array

public Department(string departmentName) //constructor


{
name = departmentName;
}

public string this [int index] //indexer


{
get
{
if (index >= 0 && index < MAX_EMPLOYEES)
{
return employees[index];
}
else
{
throw new System.IndexOutOfRangeException();
}
}
set
{
if (index >= 0 && index < MAX_EMPLOYEES)
{
employees[index] = value;
}
else
{
throw new System.IndexOutOfRangeException();
}
}
}

// code for the rest of the class...


}

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";

System.Console.WriteLine("The sales team is {0} and {1}", sales[0], sales[1]);


}
}

El resultado es:

The sales team is Nikki and Becky

Para obtener más información, vea Indizadores (Guía de programación de C#).

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#

public class Utilities : System.Web.Services.WebService


{
[System.Web.Services.WebMethod] // Attribute
public string GetTime()
{
return System.DateTime.Now.ToShortTimeString();
}
}

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:

<?xml version="1.0" encoding="utf-8" ?>

<string xmlns="http://tempuri.org/">7:26 PM</string>

En el ejemplo siguiente, la clase Employee se marca como serializable al agregar el atributo


SerializableAttribute. Mientras el campo Salary esté marcado como público, no se serializará puesto que está
marcado con el atributo NonSerializedAttribute.

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.

Hay tres pasos para definir y utilizar los delegados:

 Declaración
 Creación de instancias
 Invocación

Los delegados se declaran con la siguiente sintaxis:

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#

delegate long Del2(string s);

A continuación, podría asignar este delegado a cualquier método con esta firma, como:

C#

Del2 d; // declare the delegate variable


d = DoWork; // set the delegate to refer to the DoWork method

Donde la firma de DoWork es:

C#

public static long DoWork(string name)

Reasignar los delegados

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#

Del2 d; // declare the delegate variable


d = DoWork; // set the delegate to refer to the DoWork method
d = DoMoreWork; // reassign the delegate to refer to the DoMoreWork method

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#

Del operation; // declare the delegate variable


operation = Add; // set the delegate to refer to the Add method
long sum = operation(11, 22); // invoke the delegate
En el ejemplo siguiente se muestra la generación, creación de instancias e invocación de un delegado:

C#

public class MathClass


{
public static long Add(int i, int j) // static
{
return (i + j);
}

public static long Multiply (int i, int j) // static


{
return (i * j);
}
}

class TestMathClass
{
delegate long Del(int i, int j); // declare the delegate type

static void Main()


{
Del operation; // declare the delegate variable

operation = MathClass.Add; // set the delegate to refer to the Add method


long sum = operation(11, 22); // use the delegate to call the Add
method

operation = MathClass.Multiply; // change the delegate to refer to the


Multiply method
long product = operation(30, 40); // use the delegate to call the
Multiply method

System.Console.WriteLine("11 + 22 = " + sum);


System.Console.WriteLine("30 * 40 = " + product);
}
}

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#

Del operation; // declare the delegate variable


MathClass m1 = new MathClass(); // declare the MathClass instance
operation = m1.Add; // set the delegate to refer to the Add method
En este ejemplo, Add y Multiply son métodos de instancia de MathClass. Si los métodos de MathClass no se
declaran como estáticos, se los invoca a través del delegado mediante una instancia de MathClass, de la
forma siguiente:

C#

public class MathClass


{
public long Add(int i, int j) // not static
{
return (i + j);
}

public long Multiply (int i, int j) // not static


{
return (i * j);
}
}

class TestMathClass
{
delegate long Del(int i, int j); // declare the delegate type

static void Main()


{
Del operation; // declare the delegate variable
MathClass m1 = new MathClass(); // declare the MathClass instance

operation = m1.Add; // set the delegate to refer to the Add method


long sum = operation(11, 22); // use the delegate to call the Add method

operation = m1.Multiply; // change the delegate to refer to the Multiply


method
long product = operation(30, 40); // use the delegate to call the Multiply
method

System.Console.WriteLine("11 + 22 = " + sum);


System.Console.WriteLine("30 * 40 = " + product);
}
}

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#

// Declare the delegate type:


public delegate void CustomEventHandler(object sender, System.EventArgs e);

// Declare the event variable using the delegate type:


public event CustomEventHandler CustomEvent;

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#

public class EventClass


{
// Declare the delegate type:
public delegate void CustomEventHandler(object sender, System.EventArgs e);

// Declare the event variable using the delegate type:


public event CustomEventHandler CustomEvent;

public void InvokeEvent()


{
// Invoke the event from within the class that declared the event:
CustomEvent(this, System.EventArgs.Empty);
}
}

class TestEvents
{
private static void CodeToRun(object sender, System.EventArgs e)
{
System.Console.WriteLine("CodeToRun is executing");
}

private static void MoreCodeToRun(object sender, System.EventArgs e)


{
System.Console.WriteLine("MoreCodeToRun is executing");
}

static void Main()


{
EventClass ec = new EventClass();

ec.CustomEvent += new EventClass.CustomEventHandler(CodeToRun);


ec.CustomEvent += new EventClass.CustomEventHandler(MoreCodeToRun);

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

Recolección de elementos no utilizados (C# y Java)


Otras versiones

 Visual Studio 2005

Actualización: noviembre 2007

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.

Código seguro y no seguro (C# y Java)


Otras versiones
 Visual Studio 2005

Actualización: noviembre 2007

Una característica particularmente interesante de C# es la compatibilidad con el código no seguro.


Normalmente, Common Language Runtime (CLR) asume la responsabilidad de supervisar el
comportamiento del código de lenguaje intermedio de Microsoft (MSIL) y evita cualquier operación
cuestionable. No obstante, en ocasiones puede ser conveniente tener acceso directamente a una
funcionalidad de nivel inferior, como llamadas a la API Win32, y se puede hacer siempre y cuando se asuma
la responsabilidad de garantizar que ese código funcione correctamente. Ese código se debe colocar dentro
de bloques no seguros en el código fuente.

La palabra clave unsafe

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.

El ejemplo siguiente muestra el uso de unsafe en las tres situaciones anteriores:

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);
}

static void StillPointy()


{
int i=10;

unsafe
{
int *p = &i;
System.Console.WriteLine("*p = " + *p);
System.Console.WriteLine("Address of p = {0:X2}\n", (int)p);
}
}

static void Main()


{
PointyMethod();
StillPointy();
}
}

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.

La palabra clave fixed

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));
}
}
}
}

static void Main()


{
char[] array = { 'H', 'e', 'l', 'l', 'o' };
PointyMethod(array);
}
}

Resumen (C# y Java)


Actualización: noviembre 2007

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.

You might also like