You are on page 1of 17

Unidad l

OPTATIVO IV
.NET AVANZADO

INTRODUCCIÓN

C# es un lenguaje elegante, con seguridad de tipos y orientado a objetos, que permite a los de-
sarrolladores crear una gran variedad de aplicaciones seguras y sólidas que se ejecutan en .NET
Framework. Puede usar C# para crear aplicaciones cliente de Windows, servicios web XML, com-
ponentes distribuidos, aplicaciones cliente-servidor, aplicaciones de base de datos y muchas, mu-
chas más cosas. Visual C# proporciona un editor de código avanzado, prácticos diseñadores de
interfaz de usuario, un depurador integrado y muchas otras herramientas que facilitan el desarrollo
de aplicaciones basadas en el lenguaje C# y .NET Framework.

Lenguaje C#
IdLa sintaxis de C# es muy expresiva, pero también sencilla y fácil de aprender. Cualquier persona
familiarizada con C, C++ o Java, reconocerá al instante la sintaxis de llaves de C#. Los desarrolla-
dores que conocen cualquiera de estos lenguajes puede em-pezar normalmente a trabajar en C#
de forma productiva en un espacio muy corto de tiempo. La sintaxis de C# simplifica muchas de
las complejidades de C++ y proporciona características eficaces, como tipos de valor que aceptan
valores NULL, enumeraciones, delegados, expresiones lambda y acceso directo a memoria, que
no se encuentran en Java. C# admite métodos y tipo genéricos, que proporcionan una mayor se-
guridad de tipos y rendimiento, e iteradores, que permiten a los implementadores de clases de co-
lecciones definir comportamientos de iteración personalizados que son fáciles de usar por el código
de cliente. Las expresiones Language-Integrated Query (LINQ) convierten la consulta fuertemente
tipada en una construcción de lenguaje de primera clase.

En cuanto lenguaje orientado a objetos, C# admite los conceptos de encapsula-ción, herencia y po-
limorfismo. Todas las variables y métodos, incluido el método Main, el punto de entrada de la apli-
cación, se encapsulan dentro de las definiciones de clase. Una clase puede heredar directamente
de una clase primaria, pero puede implementar cualquier número de interfaces. Los métodos que
invalidan los métodos virtuales en una clase primaria requieren la palabra clave override como una
manera de evitar redefini-ciones accidentales. En C#, un struct es como una clase sencilla; es un
tipo asignado en la pila que puede implementar interfaces pero que no admite herencia.

1
Unidad l
Además de estos principios básicos orientados a objetos, C# facilita el desarrollo de
componentes de software mediante varias construcciones de lenguaje innovadoras,
incluidas las siguientes:

• Signaturas de método encapsulado lla- • Atributos, que proporcionan metadatos


madas delegados, que permiten notifi- declarativos sobre tipos en tiempo de eje-
ca-ciones de eventos con seguridad de cución.
tipos.
• Comentarios de documentación XML in-
• Propiedades, que actúan como descrip- sertados.
tores de acceso para variables miembro
privadas. • Language-Integrated Query (LINQ) que
proporciona funcionalidades de consul-ta
integradas en diversos orígenes de datos.

Si tiene que interactuar con otro software de Windows, como objetos COM o ar-chivos DLL nativos
de Win32, se puede hacer en C# mediante un proceso denominado “Interoperabilidad”. La intero-
perabilidad permite que los programas de C# hagan casi todo lo que puede hacer una aplicación
C++ nativa. C# admite incluso el uso de punte-ros y el concepto de código “no seguro” en los casos
en los que el acceso directo a me-moria es absolutamente crítico.

El proceso de compilación de C# es simple en comparación con C y C++ y más flexible que en


Java. No hay ningún archivo de encabezado independiente y ningún requisito de declaración de
métodos y tipos en un orden en particular. Un archivo de código fuente de C# puede definir cual-
quier número de clases, structs, interfaces y eventos.
Colecciones
Para muchas aplicaciones, puede que se desee crear y administrar grupos de ob-jetos relaciona-
dos. Existen dos formas de agrupar objetos: mediante la creación de ma-trices de objetos y con la
creación de colecciones de objetos.

Las matrices son muy útiles para crear y trabajar con un número fijo de objetos fuertemente tipados.

Las colecciones proporcionan una manera más flexible de trabajar con grupos de objetos. A dife-
rencia de las matrices, el grupo de objetos con el que trabaja puede au-mentar y reducirse de ma-
nera dinámica a medida que cambian las necesidades de la aplicación. Para algunas colecciones,
se puede asignar una clave a cualquier objeto que incluya en la colección para, de este modo,
recuperar rápidamente el objeto con la clave.

Una colección es una clase, por lo que se debe declarar una instancia de la clase para poder agre-
gar elementos a dicha colección.

Si la colección contiene elementos de un solo tipo de datos, se puede usar una de las clases del
espacio de nombres System.Collections.Generic. Una colección genérica cumple la seguridad de
tipos para que ningún otro tipo de datos se pueda agregar a ella. Cuando se recupera un elemento
de una colección genérica, no se tiene que determinar su tipo de datos ni realizar conversiones.

Matrices
Se puede almacenar varias variables del mismo tipo en una estructura de datos de matriz. Se pue-
de declarar una matriz mediante la especificación del tipo de sus ele-mentos.
type[] arrayName;

Los ejemplos siguientes crean matrices unidimensionales, multidimensionales y escalo-nadas:

public class Matrices


{
static void Main()
{
// Creamos una matriz unidimensional (vector).
2
Unidad l

int[] calificaciones = new int[] { 97, 92, 81, 60 };

// Definimos una consulta con LINQ.


IEnumerable<int> consulta =
from calificacion in calificaciones
where calificacion > 80
select calificacion;

// Recorremos la consulta e imprimimos los valores.


foreach (int calificacion in calificaciones)
{
Console.Write(calificacion + “ “);
}
}
}
// Salida: 97 92 81

Información general de las matrices


Una matriz tiene las propiedades siguientes:

 Puede ser una matriz unidimensional , multidimensional o escalonada .


 El número de dimensiones y la longitud de cada dimensión se establecen al crear la instan
cia de matriz. No se pueden cambiar estos valores durante la vigencia de la instancia.
 Los valores predeterminados de los elementos numéricos de matriz se establecen en cero
y los elementos de referencia se establecen en null.
 Una matriz escalonada es una matriz de matrices y, por consiguiente, sus ele-mentos son
tipos de referencia y se inicializan en null.
 Las matrices se indexan con cero: una matriz con n elementos se indexa de 0 a n-1.
 Los elementos de una matriz pueden ser de cualquier tipo, incluido un tipo de matriz.
 Los tipos de matriz son tipos de referencia que proceden del tipo base abstracto Array.
Como este tipo implementa IEnumerable y IEnumerable<T>, puede usar la iteración
foreach en todas las matrices de C#.

Uso de una colección Simple

Los ejemplos de esta sección usan la clase genérica List<T>, que le permite tra-bajar con una lista
de objetos fuertemente tipados.

En el ejemplo siguiente se crea una lista de cadenas y luego se recorre en itera-ción las cadenas
mediante una instrucción foreach.

// Crear una lista de cadena de caracteres (string).


var paises = new List<string>();
paises.Add(“Argentina”);
paises.Add(“Brasil”);
paises.Add(“Inglaterra”);
paises.Add(“Paraguay”);

// Iterar a través de la lista.


3
Unidad l
foreach (var pais in paises)
{
Console.Write(pais + “ “);
}
// Salida: Argentina Brasil Inglaterra Paraguay
Si el contenido de una colección se conoce de
antemano, se puede usar un inicializador de co-
lección para inicializar la colección.

El ejemplo siguiente es el mismo que el ejemplo


anterior, excepto que se usa un inicializador de
colección para agregar elementos a la colección.
// Crear una lista de cadena de caracteres utili-
zando

// un inicializador de colecciones.
var paises = new List<string> { “Argentina”, “Brasil”, “Inglaterra”, “Paraguay” };

// Iterar a través de la lista.


foreach (var pais in paises)
{
Console.Write(pais + “ “);
}
// Salida: Argentina Brasil Inglaterra Paraguay
Puede usar una instrucción for en lugar de una instrucción foreach para recorrer en iteración una
colección. Esto se consigue con el acceso a los elementos de la colección mediante la posición de
índice. El índice de los elementos comienza en 0 y termina en el número de elementos menos 1.
El ejemplo siguiente recorre en iteración los elementos de una colección mediante for en lugar de
foreach.
// Crear una lista de cadena de caracteres utilizando
// un inicializador de colecciones.
var paises = new List<string> {“Argentina”, “Brasil”, “Inglaterra”, “Paraguay”};

for (var i = 0; i < paises.Count; i++)


{
Console.Write(paises[i] + “ “);
}
// Salida: Argentina Brasil Inglaterra Paraguay
El ejemplo siguiente quita un elemento de la colección especificando el objeto que se quitará.
// Crear una lista de cadena de caracteres utilizando
// un inicializador de colecciones.
var paises = new List<string> {“Argentina”, “Brasil”, “Inglaterra”, “Paraguay”};

// Eliminar un elemento de la lista especificando el objeto.


salmons.Remove(“Inglaterra”);

// Iterar a través de la lista.


foreach (var pais in paises)
{
Console.Write(pais + “ “);
}
// Salida: Argentina Brasil Paraguay
El ejemplo siguiente quita elementos de una lista genérica. En lugar de una ins-trucción foreach, se
usa una instrucción for que procesa una iteración en orden descen-dente. Esto es porque el méto-
do RemoveAt hace que los elementos después de un ele-mento quitado tengan un valor de índice
inferior.
var numeros = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// Elimianr números impares.


for (var i = numeros.Count - 1; i >= 0; i--)
{
if (numeros [i] % 2 == 1)
4
Unidad l
{
// Eliminar el elemento especificando el índice de la lista.
numeros.RemoveAt(i);
}
}

// Iterando a través de la lista.


// Incluimos una expresión lambda para el método ForEach de la lista
// genérica.
numeros.ForEach(
numero => Console.Write(numero + “ “));
// Salida: 0 2 4 6 8

Tipos de colecciones
.NET Framework proporciona muchas colecciones comunes. Cada tipo de co-lección está diseña-
do para un fin específico.

A continuación, se describen algunas de las clases de colecciones comunes:

 Clases System.Collections.Generic.
 Clases System.Collections.Concurrent .
 Clases System.Collections.

System.Collections.Generic

Se puede crear una colección genérica mediante una de las clases del espacio de nombres Sys-
tem.Collections.Generic. Una colección genérica es útil cuando todos los elementos de la colección
tienen el mismo tipo. Una colección genérica exige el esta-blecimiento de fuertes tipos al permitir
agregar solo los tipos de datos deseados.

En la tabla siguiente se enumeran algunas de las clases usadas con frecuencia del espacio de
nombres System.Collections.Generic:

Clase Descripción
Dictionary<TKey,TValue> Representa una colección de pares de clave y valor que se organizan
según la clave.
List<T> Representa una lista de objetos a los que puede tener acceso el índice. Proporciona
métodos para buscar, ordenar y modificar listas.
Queue<T> Representa una colección de objetos de primeras entradas, primeras salidas (FIFO).
SortedList<TKey,TValue> Representa una colección de pares clave-valor que se ordenan por cla-
ves según la implementación de IComparer<T> asociada.
Stack<T> Representa una colección de objetos de últimas en-tradas, primeras salidas

Delegado

Un delegado es un tipo que representa referencias a métodos con una lista de pa-rámetros deter-
minada y un tipo de valor devuelto. Cuando se crea una instancia de un delegado, puede asociar
su instancia a cualquier método mediante una signatura compa-tible y un tipo de valor devuelto. Se
puede invocar (o llamar) al método a través de la instancia del delegado.

5
Unidad l
Los delegados se utilizan para pasar métodos como argumentos a otros méto-dos. Los controla-
dores de eventos no son más que métodos que se invocan a través de delegados. Por ejemplo,
cuando se crea un método personalizado y una clase, como un control de Windows, se podrá lla-
mar al método cuando se produzca un determinado evento. En el siguiente ejemplo se muestra una
declaración de delegado:
public delegate int RealizarCalculo(int x, int y);
Cualquier método de cualquier clase o struct accesible que coincida con el tipo de delegado se
puede asignar al delegado. El método puede ser estático o de instan-cia. Esto permite cambiar las
llamadas a métodos mediante programación y agregar nuevo código a las clases existentes.
Esta capacidad de hacer referencia a un método como parámetro hace que los delegados sean
idóneos para definir métodos de devolución de llamada. Por ejemplo, una referencia a un método
que compara dos objetos podría pasarse como argumento a un algoritmo de ordenación. Dado que
el código de comparación está en un procedi-miento independiente, el algoritmo de ordenación se
puede escribir de manera más ge-neral.

Los delegados tienen las propiedades siguientes:

 Los delegados son como los punteros de función de C++, pero tienen seguridad de tipos.
 Los delegados permiten pasar los métodos como parámetros.
 Los delegados pueden usarse para definir métodos de devolución de llamada.
 Los delegados pueden encadenarse entre sí; por ejemplo, se puede llamar a varios métodos
en un solo evento.
 No es necesario que los métodos coincidan exactamente con el tipo de delega-do.
 La versión 2.0 de C# presentó el concepto de Métodos anónimos, los cuales permiten pasar
bloques de código como parámetros en lugar de un método defi-nido por separado. En C# 3.0 se
presentaron las expresiones lambda como una manera más concisa de escribir bloques de códi-
go alineado. Tanto los métodos anónimos como las expresiones lambda (en ciertos contextos) se
compilan en ti-pos de delegado. Juntas, estas características se conocen ahora como funciones
anónimas.

Un delegado es un tipo que encapsula de forma segura un método, similar a un pun-tero de función
en C y C++. A diferencia de los punteros de función de C, los delegados están orientados a objetos,
proporcionan seguridad de tipos y son seguros. El tipo de un delegado se define por el nombre del
delegado. En el ejemplo siguiente, se declara un delegado denominado Del que puede encapsular
un método que toma una string como argumento y devuelve void:
public delegate void Del(string message);
Normalmente, un objeto delegado se construye al proporcionar el nombre del método que el de-
legado encapsulará o con un método anónimo. Una vez que se crea una instan-cia de delegado,
el delegado pasará al método una llamada de método realizada al dele-gado. Los parámetros

pasados al delegado por el autor de la llamada


se pasan a su vez al método, y el valor devuel-
to desde el método, si lo hubiera, es devuelto
por el delegado al autor de la llamada. Esto se
conoce como invocar al delegado. Un delegado
con ins-tancias se puede invocar como si fuera
el propio método encapsulado.

Por ejemplo:
// Crear un método para el delegado.
public static void DelegateMethod(string messa-
ge)
{
System.Console.WriteLine(message);
}

// Instanciar el delegado.
Del handler = DelegateMethod;

// Lllamada al delegado.
handler(“Hola Mundo”);

Los tipos de delegado se derivan de la clase De-


6
Unidad l
legate en .NET Framework. Los tipos de delegados son sealed (no se pueden derivar) y no se pue-
den derivar clases per-sonalizadas de Delegate. Dado que el delegado con instancias es un objeto,
puede pa-sarse como parámetro o asignarse a una propiedad. De este modo, un método puede
aceptar un delegado como parámetro y llamar al delegado en algún momento poste-rior. Esto se
conoce como devolución de llamada asincrónica y es un método común para notificar a un llamador
que un proceso largo ha finalizado. Cuando se utiliza un delegado de esta manera, el código que
usa al delegado no necesita ningún conocimien-to de la implementación del método empleado. La
funcionalidad es similar a la encapsu-lación que proporcionan las interfaces.
Otro uso común de devoluciones de llamada es definir un método de compara-ción personalizado
y pasar ese delegado a un método de ordenación. Permite que el có-digo del llamador se convierta
en parte del algoritmo de ordenación. En el siguiente método de ejemplo se usa el tipo Del como
parámetro:
public void MethodWithCallback(int param1, int param2, Del callback)
{
callback(“El número es: “ + (param1 + param2).ToString());
}
Luego puede pasar el delegado creado anteriormente a ese método:
MethodWithCallback(1, 2, handler);
Y recibir los siguientes resultados en la consola:

El número es: 3
Si se usa el delegado como abstracción, no es necesario que MethodWithCallback llame directa-
mente a la consola —no tiene que estar diseñado pensando en una consola—. Lo que MethodWith-
Callback hace es simplemente preparar una cadena y pasarla a otro método. Esto es especialmen-
te eficaz, puesto que un método delegado puede utilizar cualquier número de parámetros.
Cuando se crea un delegado para encapsular un método de instancia, el delegado hace referencia
tanto a la instancia como al método. Un delegado no tiene conocimiento del tipo de instancia —
aparte del método al que encapsula—, por lo que un delegado puede hacer referencia a cualquier
tipo de objeto siempre que haya un método en ese objeto que coincida con la signatura del dele-
gado. Cuando se crea un delegado para encapsular un método estático, solo hace referencia al
método. Se deben tener en cuenta las siguientes consideraciones:
public class MethodClass
{
public void Method1(string message) { }
public void Method2(string message) { }
}
Junto con el DelegateMethod estático mostrado anteriormente, ahora tenemos tres métodos que se
pueden encapsularse mediante una instancia de Del.

Un delegado puede llamar a más de un método cuando se invoca. Esto se conoce como multidi-
fusión. Para agregar un método adicional a la lista de métodos del delega-do —la lista de invoca-
ción—, simplemente es necesario agregar dos delegados mediante los operadores de adición o
asignación y suma (‘+’ o ‘+=’). Por ejemplo:
MethodClass obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;

//Ambos tipos de asignaciones son válidas.


Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

En este momento allMethodsDelegate contiene tres métodos en su lista de in-vocación: Method1,


Method2 y DelegateMethod. Los tres delegados origina-les, d1, d2 y d3, permanecen sin cambios.
Cuando se invoca allMethodsDelegate, todos los métodos se llaman en orden. Si el delegado usa
parámetros de referencia, la referencia se pasa secuencialmente a cada uno de los tres métodos
por turnos, y cual-quier cambio que realice un método es visible para el siguiente método.Cuando
alguno de los métodos produce una excepción que no se captura dentro del método, esa excep-ción
se pasa al llamador del delegado y no se llama a los métodos siguientes de la lista de invocación.
Si el delegado tiene un valor devuelto o los parámetros de salida, de-vuelve el valor devuelto y los
parámetros del último método invocado. Para quitar un método de la lista de invocación, utilice el
operador de decremento o el operador de asignación de decremento (‘-’ o ‘-= ‘).

7
Unidad l

Por ejemplo:
//Eliminar Method1
allMethodsDelegate -= d1;

// Copiar AllMethodsDelegate mientras se remueve d2


Del oneMethodDelegate = allMethodsDelegate - d2;
Dado que los tipos de delegado se derivan de System.Delegate, los métodos y propiedades defini-
das por esa clase se pueden llamar en el delegado. Por ejemplo, para buscar el número de méto-
dos en la lista de invocación de un delegado, se puede escribir:
int invocationCount = d1.GetInvocationList().GetLength(0);

Los delegados con más de un método en su lista de invocación derivan de MulticastDelegate, que
es una subclase de System.Delegate. El código anterior funciona en ambos casos porque las dos
clases admiten GetInvocationList.

Los delegados de multidifusión se utilizan mucho en el control de eventos. Los objetos de origen
de evento envían notificaciones de evento a los objetos de destinatario que se han registrado para
recibir ese evento. Para suscribirse a un evento, el destinatario crea un método diseñado para con-
trolar el evento; a continuación, crea a un delegado para dicho método y pasa el delegado al origen
de eventos. El origen llama al delegado cuando se produce el evento. Después, el delegado llama
al método que controla los eventos en el destinatario y entrega los datos del evento. El origen del
evento define el tipo de delegado para un evento determinado.

La comparación de delegados de dos tipos distintos asignados en tiempo de compilación generará


un error de compilación. Si las instancias de delegado son estáti-camente del tipo System.Delega-
te, entonces se permite la comparación, pero devolverá false en tiempo de ejecución.
Por ejemplo:
delegate void Delegate1();
delegate void Delegate2();

static void method(Delegate1 d, Delegate2 e, System.Delegate f)


{
// Esto produce error en tiempo de compilación.
//Console.WriteLine(d == e);

//OK en tiempo de compilación. False si el tipo de f no es el mismo que d.


System.Console.WriteLine(d == f);
}

Expresiones lambda
Una expresión lambda es una función anónima que se puede usar para crear ti-pos delegados o de
árbol de expresión. Al utilizar expresiones lambda, puede escribir funciones locales que se pueden
pasar como argumentos o devolverse como valor de llamadas de función. Las expresiones lambda
son especialmente útiles para escribir ex-presiones de consulta LINQ.

Para crear una expresión lambda, especifique los parámetros de entrada (si exis-ten) a la izquierda
del operador lambda => y coloque el bloque de expresiones o de ins-trucciones en el otro lado. Por
8
Unidad l
ejemplo, la expresión lambda x => x * x especifica un parámetro denominado x y devuelve el valor
de x cuadrado. Puede asignar esta expre-sión a un tipo delegado, como se muestra en el ejemplo
siguiente:
delegate int del(int i);
static void Main(string[] args)
{
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25
}
Para crear un tipo de árbol de expresión:
using System.Linq.Expressions;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Expression<del> myET = x => x * x;
}
}
}
El operador => tiene la misma prioridad que la asignación (=) y es asociativa a la derecha (vea la
sección “Asociatividad” del artículo Operadores).

Las lambdas se usan en consultas LINQ basadas en métodos como argumentos de los métodos de
operador de consulta estándar, tales como Where.

Cuando se utiliza la sintaxis de método para llamar al método Where en la cla-se Enumerable
(como se hace en LINQ to Objects y LINQ to XML), el parámetro es un tipo delegado System.Fun-
c<T,TResult>. Una expresión lambda constituye la manera más práctica de crear ese delegado.
Cuando se llama al mismo método en, por ejemplo, la clase System.Linq.Queryable (como se hace
en LINQ to SQL), el tipo de parámetro es System.Linq.Expressions.Expression<Func>, donde Func
es uno de los delegados de Func, que tiene hasta dieciséis parámetros de entrada. De nuevo, una
expresión lambda constituye una manera muy concisa de construir ese árbol de expresión. Las
lambdas permiten que las llamadas a Where tengan un aspecto similar, aunque, de hecho, el tipo
de objeto creado a partir de la lambda sea diferente.

En el ejemplo anterior, observe que la signatura de delegado tiene un parámetro de entrada con
tipo implícito inty devuelve un int. La expresión lambda se puede convertir en un delegado de ese
tipo porque también tiene un parámetro de entrada (x) y un valor devuelto que el compilador puede
convertir implícitamente al tipo int. (La inferencia de tipos se analiza con más detalle en las seccio-
nes siguientes). Cuando el delegado se invoca mediante un parámetro de entrada de 5, devuelve
un resultado de 25.
Las lambdas no se permiten en el lado izquierdo del operador is o as.

Todas las restricciones que se aplican a los métodos anónimos también se aplican a las expresio-
nes lambda.

Lambdas de expresión
Una expresión lambda con una expresión en el lado derecho del operador => se denomina lambda
de expresión. Las lambdas de expresión se usan ampliamente en la construcción de árboles de
expresión. Una expresión lambda devuelve el resultado de evaluar la condición y tiene la siguiente
forma:

(input-parameters) => expression


Los paréntesis solo son opcionales si la lambda tiene un parámetro de entrada; de lo contrario, son
obligatorios. Dos o más parámetros de entrada se separan por comas y se encierran entre parén-
tesis:

(x, y) => x == y
9
Unidad l
A veces, es difícil o imposible que el compilador deduzca los tipos de entra-da. Cuando ocurre esto,
los tipos se pueden especificar explícitamente como se muestra en el ejemplo siguiente:
(int x, string s) => s.Length > x
Para especificar cero parámetros de entrada, utilice paréntesis vacíos:
() => SomeMethod()
Observe en el ejemplo anterior que el cuerpo de una lambda de expresión puede estar compuesto
de una llamada a método. Sin embargo, si crea árboles de expresiones que se evalúan fuera de
.NET Framework, por ejemplo, en SQL Server, no debe utilizar llamadas a métodos en expresiones
lambda. Los métodos no tendrán ningún significado fuera del contexto de .NET Common Language
Runtime.

Lambdas de instrucción
Una lambda de instrucción es similar a un lambda de expresión, salvo que las instrucciones se
encierran entre llaves:
(parámetros de entrada) => {instrucción; }
El cuerpo de una lambda de instrucción puede estar compuesto de cualquier nú-mero de instruccio-
nes; sin embargo, en la práctica, generalmente no hay más de dos o tres.
delegate void TestDelegate(string s);
TestDelegate del = n => { string s = n + “ World”;
Console.WriteLine(s); };
Las lambdas de instrucción, como los métodos anónimos, no se pueden utilizar para crear árboles
de expresión.

Lambdas asincrónicas
Puede crear fácilmente expresiones e instrucciones lambda que incorporen el procesamiento asin-
crónico mediante las palabras clave async y await. Por ejemplo, en el siguiente ejemplo de formu-
larios Windows Forms se incluye un controlador de eventos que llama y espera un método asincró-
nico, ExampleMethodAsync.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private async void button1_Click(object sender, EventArgs e)


{
// ExampleMethodAsync retorna Task.
await ExampleMethodAsync();
textBox1.Text += “\r\nControl retorna el manejador del evento Click.\n”;
}

async Task ExampleMethodAsync()


{
// La siguiente línea simula un proceso asincrónico de la tarea.
await Task.Delay(1000);
}
}
Puede agregar el mismo controlador de eventos utilizando una lambda asincró-nica. Para agregar
este controlador, agregue un modificador async antes de la lista de parámetros lambda, como se
muestra en el ejemplo siguiente.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += async (sender, e) =>
{
// ExampleMethodAsync retorna Task.
await ExampleMethodAsync();
textBox1.Text += “\nControl retorna el manejador del evento Click.\n”;
10
Unidad l
};
}

async Task ExampleMethodAsync()


{
// La siguiente línea simula un proceso asincrónico de la tarea.
await Task.Delay(1000);
}
}

Lambdas con los operadores de consulta estándar


Muchos operadores de consulta estándar tienen un parámetro de entrada cuyo ti-po es uno de la
familia Func<T,TResult> de delegados genéricos. Estos delegados usan parámetros de tipo para
definir el número y los tipos de los parámetros de entrada y el tipo de valor devuelto del delegado.
Los delegados Func son muy útiles para encapsular expresiones definidas por el usuario que se
aplican a cada elemento en un conjunto de datos de origen. Por ejemplo, considere el siguiente tipo
delegado:

public delegate TResult Func<TArg0, TResult>(TArg0 arg0)

Se pueden crear instancias del delegado como Func<int,bool> myFunc , don-de int es un pará-
metro de entrada y bool es el valor devuelto. El valor devuelto siem-pre se especifica en el último
parámetro de tipo. Func<int, string, bool> define un delegado con dos parámetros de entrada, int y
string, y un tipo de valor devuelto de bool. El siguiente delegado Func , cuando se invoca, devolverá
true o false para indicar si el parámetro de entrada es igual a 5:
Func<int, bool> myFunc = x => x == 5;

bool result = myFunc(4); // Retorna false como se espera


También puede proporcionar una expresión lambda cuando el tipo de argumento es Expression<-
Func>, por ejemplo, en los operadores de consulta estándar que se defi-nen en System.Linq.Quer-
yable. Al especificar un argumento Expression<Func>, la lambda se compilará en un árbol de ex-
presión.

A continuación, se muestra un operador de consulta estándar, el método Count:


int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);

El compilador puede deducir el tipo del parámetro de entrada o también se puede especificar explí-
citamente. Esta expresión lambda particular cuenta aquellos enteros (n) que divididos por dos dan
como resto 1.

La siguiente línea de código produce una secuencia que contiene todos los ele-mentos de la matriz
numbers que aparecen a la izquierda del 9, ya que ese es el primer número de la secuencia que no
cumple la condición:

var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);


En este ejemplo se muestra cómo especificar varios parámetros de entrada ence-rrándolos entre
paréntesis. El método devuelve todos los elementos de la matriz de nú-meros hasta encontrar un
número cuyo valor sea menor que su posición. No confunda el operador lambda (=>) con el opera-
dor mayor o igual que (>=).

var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);


Inferencia de tipos en las lambdas

Al escribir lambdas, no tiene por qué especificar un tipo para los parámetros de entrada, ya que el
compilador puede deducir el tipo según el cuerpo de lambda, el tipo de delegado del parámetro y
otros factores que se describen en la especificación del lenguaje C#. Para la mayoría de los ope-
radores de consulta estándar, la primera entrada es el tipo de los elementos en la secuencia de
origen. Por consiguiente, si está realizando una consulta sobre un IEnumerable<Customer>, se
deducirá que la variable de entrada será un objeto Customer , lo cual significa que se podrá tener
acceso a sus métodos y propiedades:

11
Unidad l
customers.Where(c => c.City == “London”);

Las reglas generales para las lambdas son las siguientes:

• La lambda debe contener el mismo número de parámetros que el tipo delegado.


• Cada parámetro de entrada de la lambda debe poder convertirse implícitamente a su pará-
metro de delegado correspondiente.
• El valor devuelto de la lambda (si existe) debe poder convertirse implícitamente al tipo de
valor devuelto del delegado.

Las expresiones lambda, en sí mismas, no tienen tipo, ya que el sistema de tipos comunes no tiene
ningún concepto intrínseco de “expresión lambda”. Sin embargo, a veces resulta práctico hablar
coloquialmente del “tipo” de una expresión lambda. En estos casos, el tipo hace referencia al tipo
del delegado o el tipo de Expression en el que se convierte la expresión lambda.

Ámbito de las variables en las expresiones lambda


Las lambdas pueden hacer referencia a las variables externas que están en el ámbito del método
que define la función lambda o en el ámbito del tipo que contiene la expresión lambda. Las variables
que se capturan de esta manera se almacenan para su uso en la expresión lambda, cuando de otro
modo quedarían fuera de ámbito y serían eliminadas por la recolección de elementos no utilizados.
Para poder utilizar una variable externa en una expresión lambda, debe estar previamente asigna-
da. En el ejemplo siguiente se muestran estas reglas:
delegate bool D();
delegate bool D2(int i);

class Test
{
D del;
D2 del2;
public void TestMethod(int input)
{
int j = 0;

del = () => { j = 10; return j > input; };


del2 = (x) => {return x == j; };
// Salida: j = 0
Console.WriteLine(“j = {0}”, j);
bool boolResult = del();

// Salida: j = 10 b = True
Console.WriteLine(“j = {0}. b = {1}”, j, boolResult);
}

static void Main()


{
Test test = new Test();
test.TestMethod(5);

bool result = test.del2(10);

// Salida: True
Console.WriteLine(result);

Console.ReadKey();
}
}

Las reglas siguientes se aplican al ámbito de las variables en las expresiones lambda:

• Una variable capturada no se recolectará como elemento no utilizado hasta que el delegado
que hace referencia a ella sea elegible para la recolección de elementos no utilizados.
• Las variables introducidas en una expresión lambda no son visibles en el método externo.
12
Unidad l
• Una expresión lambda no puede capturar directamente un paráme-tro in, ref o outdesde un
método que la englobe.
• Una instrucción return en una expresión lambda no hace que el método que la engloba de-
vuelva un valor.
• Una expresión lambda no puede contener una instruc-ción goto , break o continue que esté
dentro de la función lambda si el des-tino de la instrucción de salto está fuera del bloque. También
es un error utilizar una instrucción de salto fuera del bloque de la función lambda si el destino está
dentro del bloque.

Indexers
Los indizadores permiten indizar las instancias de una clase o struct como matri-ces. El valor indi-
zado se puede establecer o recuperar sin especificar explícitamente un miembro de tipo o de ins-
tancia. Son similares a propiedades, excepto en que sus des-criptores de acceso usan parámetros.

En el ejemplo siguiente se define una clase genérica con métodos de descriptor de acceso get y set
sencillo para asignar y recuperar valores. La clase Program crea una instancia de esta clase para
almacenar cadenas.
using System;

class SampleCollection<T>
{
private T[] arr = new T[100];
public T this[int i]
{
get { return arr[i]; }
set { arr[i] = value; }
}
}

class Program
{
static void Main()
{
var stringCollection = new SampleCollection<string>();
stringCollection[0] = “Hola, Mundo”;
Console.WriteLine(stringCollection[0]);
}
}
// Salida:
// Hola, Mundo.

Definiciones de cuerpos de expresión


Es habitual que un descriptor de acceso get o set de un indizador conste de una única instrucción
que devuelve o establece un valor. Los miembros con forma de expre-sión proporcionan una sinta-
xis simplificada para admitir este escenario. A partir de C# 6, un indizador de solo lectura puede im-
plementarse como un miembro con forma de expresión, como se muestra en el ejemplo siguiente.
using System;

class SampleCollection<T>
{
private T[] arr = new T[100];
int nextIndex = 0;

public T this[int i] => arr[i];

public void Add(T value)


{
if (nextIndex >= arr.Length)
throw new IndexOutOfRangeException($”Error”);
arr[nextIndex++] = value;
}

13
Unidad l
}

class Program
{
static void Main()
{
var stringCollection = new SampleCollection<string>();
stringCollection.Add(“Hola, Mundo”);
System.Console.WriteLine(stringCollection[0]);
}
}
// Salida:
// Hola, Mundo.
Tenga en cuenta que => presenta el cuerpo de la expresión y que la palabra cla-ve get no se utiliza.
A partir de C# 7.0, los descriptores de acceso get y set se pueden implementar como miembros
con forma de expresión. En este caso, sí deben utilizarse las palabras clave get y set. Por ejemplo:
using System;

class SampleCollection<T>
{
private T[] arr = new T[100];

public T this[int i]
{
get => arr[i];
set => arr[i] = value;
}
}

class Program
{
static void Main()
{
var stringCollection = new SampleCollection<string>();
stringCollection[0] = “Hola, Mundo.”;
Console.WriteLine(stringCollection[0]);
}
}
// Salida:
// Hola, Mundo.

Información general sobre los indizadores

• Los indizadores permiten indizar objetos de manera similar a como se hace con las matrices.
• Un descriptor de acceso get devuelve un valor. Un descriptor de acce-so set asigna un valor.
• La palabra clave this se usa para definir los indizadores.
• La palabra clave value se usa para definir el valor asignado por el indizador set.
• Los indizadores no tienen que ser indizados por un valor entero; depende de us-ted
cómo definir el mecanismo de búsqueda concreto.
• Los indizadores se pueden sobrecargar.
• Los indizadores pueden tener más de un parámetro formal, por ejemplo, al tener acceso a
una matriz bidimensional.

Los indexadores son una comodidad sintáctica que le permiten crear una clase, un struct o una in-
terfaz a los que pueden acceder las aplicaciones cliente simplemente como una matriz. Los indexa-
dores se implementan con más frecuencia en tipos cuyo propósito principal consiste en encapsular
una matriz o colección interna. Por ejemplo, suponga que tiene una clase denominada TempRecord
que representa la temperatura en grados Fahrenheit que se registra en 10 momentos diferentes
durante un período de 24 horas. La clase contiene una matriz denominada “temps” de tipo float
para representar la temperatura y un DateTime que representa la fecha en que se han registrado
las tempe-raturas. Si implementa un indexador en esta clase, los clientes pueden tener acceso a
las temperaturas en una instancia de TempRecord como float temp = tr[4] en lugar de como float
14
Unidad l
temp = tr.temps[4]. La notación del indexador no solo simplifica la sin-taxis para las aplicaciones
cliente; también hace que la clase y su finalidad sean más intuitivas para que las conozcan otros
desarrolladores.

Para declarar un indexador en una clase o un struct, use la palabra clave this, como en este ejem-
plo:

public int this[int index] // Declaración de Indexer.


{
// get and set accessors
}
La firma de un indexador consta del número y los tipos de sus parámetros for-males. No incluye el
tipo de indexador ni los nombres de los parámetros formales. Si declara más de un indexador en la
misma clase, deben tener firmas diferentes.

Un valor de indexador no está clasificado como una variable; por tanto, no se puede pasar un valor
de indexador como un parámetro ref u out.

Para proporcionar el indexador con un nombre que puedan usar otros lenguajes, use un atributo
name en la declaración. Por ejemplo:

[System.Runtime.CompilerServices.IndexerName(“TheItem”)]
public int this [int index] // Indexer declaration
{
}
Este indexador tendrá el nombre TheItem. Si no se proporciona el atributo de nombre, Item será el
nombre predeterminado.

Ejemplo 1

En el ejemplo siguiente, se muestra cómo declarar un campo de matriz priva-da, temps, como un
indexador. El indexador permite el acceso directo a la instan-cia tempRecord[i]. La alternativa a
usar el indexador es declarar la matriz como un miembro public y tener acceso directamente a sus
miembros tempRecord.temps[i].

Tenga en cuenta que, cuando se evalúa el acceso de un indexador (por ejemplo, en una instrucción
Console.Write), se invoca al descriptor de acceso get. Por tanto, si no hay ningún descriptor de
acceso get, se produce un error en tiempo de compilación.
Código

class TempRecord
{
// Vector con valores de temperatura
private float[] temps = new float[10] { 56.2F, 56.7F, 56.5F, 56.9F, 58.8F,
61.3F, 65.9F, 62.1F, 59.2F, 57.5F };

public int Length


{
get { return temps.Length; }
}
// Declaración de Indexer.
public float this[int index]
{
get
{
return temps[index];
}

set
{
temps[index] = value;
}
15
Unidad l
}
}

class MainClass
{
static void Main()
{
TempRecord tempRecord = new TempRecord();
tempRecord[3] = 58.3F;
tempRecord[5] = 60.1F;
for (int i = 0; i < 10; i++)
{
System.Console.WriteLine(“Elemento #{0} = {1}”, i, tempRecord[i]);
}
System.Console.WriteLine(“Presione una Tecla para continuar.”);
System.Console.ReadKey();

}
}
/* Salida:
Elemento #0 = 56.2
Elemento #1 = 56.7
Elemento #2 = 56.5
Elemento #3 = 58.3
Elemento #4 = 58.8
Elemento #5 = 60.1
Elemento #6 = 65.9
Elemento #7 = 62.1
Elemento #8 = 59.2
Elemento #9 = 57.5
*/

Indexar con otros valores


C# no limita el tipo de índice a un entero. Por ejemplo, puede ser útil usar una cadena con un in-
dexador. Este tipo de indexador podría implementarse al buscar la ca-dena de la colección y devol-
ver el valor adecuado. Ya que los descriptores de acceso se pueden sobrecargar, las versiones de
cadena y entero pueden coexistir.

Ejemplo 2

En este ejemplo, se declara una clase que almacena los días de la semana. Se de-clara un des-
criptor de acceso get que toma una cadena, el nombre de un día, y devuel-ve el entero correspon-
diente. Por ejemplo, el domingo devolverá 0, el lunes devolverá 1, etc.

Código

class DayCollection
{
string[] days = { “Sun”, “Mon”, “Tues”, “Wed”, “Thurs”, “Fri”, “Sat” };
private int GetDay(string testDay)
{

for (int j = 0; j < days.Length; j++)


{
if (days[j] == testDay)
{
return j;
}
}

throw new System.ArgumentOutOfRangeException(testDay, “Error”);


16
Unidad l
}

public int this[string day]


{
get
{
return (GetDay(day));
}
}
}

class Program
{
static void Main(string[] args)
{
DayCollection week = new DayCollection();
System.Console.WriteLine(week[“Fri”]);
System.Console.WriteLine(week[“Made-up Day”]);
System.Console.WriteLine(“Presione una Tecla para salir.”);
System.Console.ReadKey();
}
}
// Salida: 5

Programación sólida
Hay dos formas principales en que se pueden mejorar la seguridad y confiabilidad de los
indexadores:

• Se debe asegurar la incorporación de algún tipo de estrategia de control de erro-res para


controlar la posibilidad de que el código de cliente pase un valor de ín-dice no válido. En el primer
ejemplo de este tema, la clase TempRecord propor-ciona una propiedad Length que permite al
código de cliente comprobar la en-trada antes de pasarla al indexador. También puede colocar el
código de control de errores en el propio indexador. Se debe documentar para los usuarios cual-
quier excepción que se produzca dentro de un descriptor de acceso del indexa-dor.

• Se debe establecer la accesibilidad de los descriptores de acceso get y set para que sea tan
restrictiva como sea razonable. Esto es importante para el descriptor de acceso set en particular.

BIBLIOGRAFÍA

cambiocultural.org/cultura-política/concepto-de-cultura-política

informacionporlaverdad.wordpress.com/2011/10/02/¿que-es-el-nuevo-orden-mundial.

sedici.unlp.edu.ar/bitstream/handle/10915/38737/Ponencia.pdf

www.cepal.org/es/comunicados/paises-de-la-region-adoptan-el-consenso-de-montevideo

www.eaeprogramas.es/internacionalizacion/que-son-los-mercados-emergentes

www.icps.cat/archivos/WorkingPapers

www.nexos.com.mx

www.sem-wes.org/sites/default/files/revistas/rem4_5.pdf

17

You might also like