Tech·Ed by raona

Tuesday, November 28, 2006

DEV323 - C# 3.0

Bueno... como es habitual el título no era tan escueto, pero vamos, la presentación iba sobre eso: las novedades que Orcas traerá al lenguaje C#.
Hace ya algún tiempo (más o menos un añito) probé una alfa de LINQ y venia con C# 3.0, así que pude probarlo. Mis impresiones de entonces están en mi blog personal, en concreto en este post.

Digamos que la gran novedad de C# 3.0 es LINQ y que el resto de novedades incorporadas al lenguage se han incorporado en mayor o en menor medida para poder dar un buen soporte a lo que es LINQ... pero vayamos por partes y enumeremos las diferentes novedades que se nos fueron presentando.

Local variable type inference
El compilador puede determinar el tipo de una variable en función del tipo del rvalue correspondiente (es decir en función de lo que se encuentre a la derecha del signo igual). Existe una nueva palabra clave "var" para declarar variables.
var foo = new Dictionary<bar,baz>();
En esta línea automáticamente la variable foo queda definido de tipo Dictionary<bar,baz>. Debe notarse que declarar variables con var no hace que C# sea débilmente tipado, el lenguaje continua siendo fuertemente tipado. Es simplemente para no tener que escribir de forma redundante.
Hay otra razón por la que "var" se ha introducido en el lenguaje (se comenta más abajo).

Extension methods
Permiten extender una clase sin necesidad de crear una clase derivada. Interesante cuando no queremos (o no podemos) derivar.
Para ello simplemente declaramos un método estático cuyo primer parámetro se declare con la palabra clave this:

static FooType foo (this BarType bar, Baz baz) { .... }
Este método funciona como un extension method a la clase BarType. Es decir sobre los objetos de la clase BarType se puede invocar el método foo() pasando como parámetro un objeto Baz.
Los extension methods se implementan a nivel de compilador, no hay implicación para el CLR en ellos.
Si se implementa un extension method sobre una interfaz sólo debe implementarse una vez, y queda implementado para todas las clases que implementen la interfaz.

Evidentemente extension methods, es un mecanismo muy potente a la vez que peligroso: mal usado hace que al final uno no tenga ni idea donde está definido un método determinado. Por lo tanto es un recurso que debe usarse en último lugar (mmm... veremos como evoluciona).

Lambda expressions
Jejejee... todos los que odiaban lamda-calculus en la carrera y creían que ya podían olvidarlo para siempre... pues no.
Lo más importante de als lambda expressions es que permiten pasar código como parámetros. Por lo tanto se usan en muchos puntos donde antes usábamos un delegate.

La definición de una lambda expression es:
param_list => código
donde param_list es una lista de parámetros.

Por ejemplo:
c => c.State == "WA";
Define una expresión lambda que recibe un parámetro (c) y cuyo resultado es la evaluación del código (c.State == "WA").

Lo más interesante de las lambda expressions es cuando sustituyen a delegates.
Por ejemplo este código seria C# 2.0 usando delegates:

public delgate bool Predicate<t>(T item);
Predicate<Customer> isFromWA = CustomerFromWA;

private static bool CustomerFromWA (Customer c)
{ return c.State == "WA"; }

Y el código equivalente usando expresiones lambda:

Predicate<Customer> isFromWA = c=> c.State == "WA";

Object initializers
Se pueden crear e iniciar objetos en una sola línea:

Point a = new Point {X=0, Y=1};
equivale a:
Point a=new Point();
a.X=0;
a.Y=1;

Collection initializers
Se pueden inicializar colecciones junto con su declaración:

List foo = new List{100,200,300};
Para que funcione la clase debe tener dos requisitos:
  1. Implementar IEnumerable
  2. Tener un método publico llamado Add()
El método Add() puede tener más de 1 parámetro:

var foo = new Dictionary<int,string> { { 0,"zero"},{1,"one"}};

Automatic properties
Bueno... esa es una pequeña modificación para evitar tener que crear código para crear propiedades get/set que únicamente devuelven/acceden a una variable miembro.

public class Product {
public String Name {get; set;}
}

equivale a:

public class Product {
private string _name;
public String Name { get { return _name;} set { _name = value; } }
}

La variable privada _name y el código de get/set lo crea el compilador para nosotros :)

Query expressions
Query expressions es la sintaxis propia de C# para LINQ.
Las nuevas palabras clave (p.ej. select o join) sólo lo son dentro del contexto de una query expression. El inicio del contexto de una query expression la marca la palabra clave from. Eso significa que el resto de palabras clave de LINQ no se comportan como tal fuera del contexto de una query expression.

var query = from c in customers
where c.State == "WA"
select new {c.Name, c.Phone};

LINQ está implementada como un conjunto de extension methods (p.ej. muchos de ellos sobre IEnumerable). El compilador traduce las query expressions a las llamadas a métodos LINQ equivalentes. La query expression anterior el compilador la traduciria a:

var query = customers.Where(c => c.State == "WA").Select( c=> new {c.Name, c.Phone});

Es aquí donde se pone de manifiesto que la mayoría de añadidos al lenguaje C# 3.0 lo son para poder dar soporte a LINQ (esa línea de "código convertido" usa anonymous types, lambda expressions, object initializers y local variable type inference).

Anonymous Types
Permite declarar objetos de clases anónimas, sin necesidad de declarar la clase.
Se usan mucho en LINQ:

IEnumerable<Contact> plq = from c in customers
where c.State == "WA"
select new Contact { Name = c.Name, Phone = c.Phone};

Esa consulta, tal y como está redactada nos obliga a implementar la clase Contact, que debe tener al menos, dos propiedades (Name y Phone).
Con anonymous types podemos usar el siguiente código:
var plq = from c in customers
where c.State == "WA"
select new {Name = c.Name, Phone = c.Phone};

La parte marcada en cursiva es la creación del objeto de un tipo anónimo (se pone new sin poner el nombre de la clase). Debe notarse que ahí estamos obligados a definir la variable plq usando "var" porque no hay manera de saber el tipo de un objeto anónimo (esa es la razón de peso por la cual "var" se ha introducido en el lenguaje).

Si queremos iterar sobre plq (que es un IEnumerable de un tipo anónimo) también estamos obligados a usar var:

foreach (var item in plq) {...}
La variable item es del tipo anónimo creado por el compilador, por lo que item.Name compila perfectamente.

Expressions Tree (Code as Data)
Expressions Tree es un nuevo modelo de clases y objetos que permite obtener el DOM de una expresión. De esta manera se pueden crear expresiones de forma dinámica.

Imaginemos que tenemos un delegate Func definido como:
public delegate T Func<a0, T> (A0 arg);
es decir representa a una función de un argumento de tipo A0 y que devuelve un valor de tipo T.
Usando lambda expressions podríamos definir el delegate apuntando a una función:
Func<double,double> f = x => x*2 + 1;
(para invocarlo: f(10.2);)

La classe Expression es el punto de entrada a la API de Expressions Tree:
Expression<Func<double, double>> e = x => x*2+1;

La variable e contiene el árbol DOM que usa el compilador para evaluar la expresión. Entre otras cosas lo que podemos hacer es compilar la expresión y obtener el código MSIL correspondiente (y por lo tanto asignarlo a un delegate):
Func<double,double> foo = e.Compile();

Evidentemente al ser un árbol DOM podemos añadir, quitar, mover, modificar nodos, lo que a la práctica siginifica que podemos crear expresiones de forma dinámica, y al final compilarlas y asignarlas a delegates!

Y eso fue todo lo que dio la sesión de si... como podeis ver bastante cosa, no????

0 Comments:

Post a Comment

<< Home