SlideShare una empresa de Scribd logo
1 de 42
Descargar para leer sin conexión
Diseño del avatar: Inmaculada Martínez Lobo (@inmmastar)
Diseño el buho: Vanessa Redondo López (@creative_vanesa)
Fan-C#ines
Entendiendo conceptos
de C# de forma fácil ☺
© José Manuel Redondo López
LICENCIA
Copyright (C) 2020 José Manuel
Redondo López.
Permission is granted to copy,
distribute and/or modify this document
under the terms of the GNU Free
Documentation License, Version 1.3
or any later version published by the
Free Software Foundation; with no
Invariant Sections, no Front-Cover
Texts, and no Back-Cover Texts.
A copy of the license is included in the
section entitled "GNU Free
Documentation License"
© José Manuel Redondo López
V1.2 (29 zines)
Historial de cambios
V1.0 (22/05/2019):
* Recopilación de todos los zines originales en un PDF de alta
calidad
* Estilo visual completamente renovado siguiendo el feedback
de los usuarios
* Cinco zines adicionales:
1) Polimorfismo y enlace dinámico
2) Master-Worker
3) Producer-Consumer
4) Evaluación dinámica de código con Roslyn CaaS
5) Conversión de texto a voz y de voz a texto
V1.1 (23/05/2019):
* Publicación en abierto
* Nuevo zine: “ejecución” de expresiones en lambda cálculo
V1.2 (03/02/2020):
* Añadido un nuevo índice por temática navegable
* 3 nuevos zines: Equals, Downcasting y Genericidad en C# vs
Java
* Licencia de la FSF incluida explícitamente
© José Manuel Redondo López
Índice de Zines
PROGRAMACIÓN ORIENTADA A OBJETOS
Polimorfismo y enlace dinámico Ver
Equals Ver
Downcast y Upcast Ver
Paso de parámetros, ref y out Ver
Excepciones Ver
Asertos Ver
Genericidad Ver
Genericidad en C# vs Java Ver
IEnumerable Ver
PROGRAMACIÓN FUNCIONAL
Introducción a la programación
funcional
Ver
Usando funciones Ver
Funciones lambda Ver
Linq Ver
Generadores Ver
Currificación Ver
Operaciones lazy Ver
"Ejecución" de cálculo lambda Ver
Join (Linq) Ver
GroupBy (Linq) Ver
PROGRAMACIÓN CONCURRENTE
Uso de lock Ver
Contextos en los que usar lock Ver
Convirtiendo el código a paralelo
(TPL/PLinq)
Ver
Claúsulas e hilos Ver
ReaderWriterLockSlim Ver
Master-Worker Ver
Consumer-Producer Ver
METAPROGRAMACIÓN & OTROS TEMAS
Imitando a los lenguajes
dinámicos
Ver
Roslyn Compiler-As-A-Service Ver
API de Voz Ver
C#
© José Manuel Redondo López
© José Manuel Redondo López
Yo os declaro virtual y override
Viniendo de Java,
probablemente
contestarás que al de
Manager
Esto implica que referencias a
Empleado pueden contener
instancias de Manager
Y Manager puede especializar
(crear nuevo comportamiento)
los métodos de Empleado
Por tanto, esto es
posible
De hecho usa la política contraria: enlace dinámico solo si lo
autorizas expresamente
Primero, en la clase padre hay que marcar los métodos
como redefinibles con la palabra reservada virtual
Esto abre la posibilidad de redefinir estos métodos en las
clases hijas (¡si se quiere hacer!)
Y así se obtiene el mismo comportamiento que Java
¡Mira bien los métodos virtual del API de C# en caso de que
necesites crear tu propia versión de alguno en tus clases hijas!
❖ El polimorfismo es un mecanismo de
generalización: una abstracción general
puede representar otras más específicas
❖ Las referencias de clases derivadas
(especificas) promocionan (se convierten
implícitamente) a referencias de clases
base (generales)
❖ El enlace dinámico es un mecanismo de
especialización: la implementación de un
método puede especializarse en clases
derivadas
class Empleado {
double getSalario() {
return 1000.0;
}
}
class Manager : Empleado {
double getSalario() {
return 2000.0;
}
} Empleado empleado = new Manager();
Console.WriteLine(empleado.getSalario());
Empleado
-Nombre
+ GetSalario ()
…
-Apellido
…
Manager
-…
+ GetSalario ()
…
class Empleado {
double getSalario() {
return 1000.0;
}
}
class Manager : Empleado {
double getSalario() {
return 2000.0;
}
}
¡Pero no! C#, a diferencia de Java, ¡no tiene enlace dinámico
por defecto!
class Empleado {
virtual double getSalario() {
return 1000.0;
}
}
class Manager : Empleado {
double getSalario() {
return 2000.0;
}
}
class Empleado {
virtual double getSalario() {
return 1000.0;
}
}
class Manager : Empleado {
override double getSalario() {
return 2000.0;
}
}
¿Y cómo se autoriza? Segundo, se marca el método que redefine
al del padre con override
Pero ¿a qué método
getSalario() se
llama?
<-
© José Manuel Redondo López
Toss a call to your Equals
❖ Ahora podemos controlar que hacen
Equals y el operador ==
❖ Implementar == requiere implementar
también !=
❖ Redefinir Equals normalmente también
requiere redefinir GetHashCode
❖ Por qué? Para permitir a los objectos
trabajar normalmente en una Hashtable o
similar
❖ https://docs.microsoft.com/es-
es/visualstudio/ide/reference/generate-equals-
gethashcode-methods?view=vs-2019
p1 y p2 son objetos diferentes porque se han creado con dos
new diferentes, aunque sus contenidos sean los mismos
Pero ¿qué pasa si necesito comparar por referencia y
he modificado tanto el == como Equals?
C# ha pensado en ello; ¡puedes usar el método
estático Object.ReferenceEquals!
❖ Todo objeto puede comprobar si es
igual a otro
❖ Pero si no implementa un Equals, se usa
Object.Equals
❖ Esto se llamada comparación de
igualdad por referencia
❖ Y solo es true cuando los dos
operandos se refieren AL MISMO
objeto
var p1 = new Persona { Nombre = "Jhon"};
var p2 = new Persona { Nombre = "Jhon" };
Console.WriteLine(p1.Equals(p2)); // False
Console.WriteLine(p1 == p2); // False
p2 = p1;
Console.WriteLine(p1.Equals(p2)); // True
Console.WriteLine(p1 == p2); // True
class Persona {
public string Nombre { get; set; }
public override bool Equals(object obj) {
return this.Nombre.Equals(((Person) obj). Nombre);
}
…
p1 = new Persona { Name = "Jhon" };
p2 = new Persona { Name = "Jhon" };
Console.WriteLine(p1.Equals(p2)); // True
Console.WriteLine(p1 == p2); // False
class Persona {
…
public static bool operator==(Person p1, Person p2) {
return p1.Nombre.Equals(p2.Nombre);
}
public static bool operator!=(Person p1, Person p2) {
return !p1.Nombre.Equals(p2.Nombre);
}
…
p1 = new Persona { Name = "Jhon" };
p2 = new Persona { Name = "Jhon" };
Console.WriteLine(p1.Equals(p2)); // True
Console.WriteLine(p1 == p2); // True
p1 = new Persona { Nombre = "Jhon" };
p2 = new Persona { Nombre = "Jhon" };
Console.WriteLine(p1.Equals(p2)); // True
Console.WriteLine(Object.ReferenceEquals(p1, p2));
// False
Solo cuando se refieren al mismo objeto, Object.Equals
devuelve true!
Pero ¿qué pasa si implementamos nuestro propio
Equals (por nombre)? ¡El operador == no funciona!
Porque nuestro Equals no se
usa, aún usa las referencias
¿Qué puedo hacer para cambiar el comportamiento del
operador ==?... ¡Sobrecargarlo!
<-
© José Manuel Redondo López
AasC/DisC
❖ Cada método puede combinarse y/o
usarse para tratar varios tipos de
objetos de forma diferente cuando se
haga downcasting!
❖ Cada uno tiene sus particularidades
❖ El de la excepción es lento, propenso a
fallos (se te olvida el try-catch) y no
recomendable
❖ El de la reflexión es lento y te obliga a
programar un if nuevo para cada tipo
(no es mantenible)
❖ is te obliga a comprobar antes
❖ as hace el cast si puede, pero te obliga
a comprobar después
try {
r = (Rectangulo) f; // Error!
//Rectangulo r2 = (Rectangulo) c; // No compila :)
}
catch (InvalidCastException ex) {
c = (Circulo) f; // La otra opción
… // Más tipos
}
if (f is Rectangulo) {
r = (Rectangulo)f;
}
else {
if (f is Circulo) {
c = (Circulo) f;
… // Más tipos
}
}
r = f as Rectangulo;
if (r == null) {
c = f as Circulo;
… // Más tipos
}
if (f.GetType().Name.Equals("Rectangulo"))
r = f as Rectangulo; // O un simple cast
if (f.GetType().Name.Equals("Circulo"))
c = f as Circulo; // O un simple cast
… // Más tipos
¡Hacer el cast y rezar! :)
Si es el esperado, ¡se puede hacer el cast sin
problemas!
Usar el operador safe-cast (as)
Hace el cast si puede...y, si no, devuelve null. ¡Puedes
comprobar si vale null para evitar errores de
ejecución!
class Figura {…}
class Circulo : Figura {…}
class Rectangulo : Figura {…}
…
Figura f = new Circulo();
Circulo c = (Circulo) f;
Rectangulo r;
En una jerarquía de
clases, tanto el upcasting
(de hijo a padre) como el
downcasting (de padre
hijo) están permitidos
Upcasting es seguro, pero el downcasting ¡puede fallar! ¿Por
qué?, porque aunque una referencia de un tipo padre pueda
contener objetos de cualquiera de sus hijos, ¡se puede hacer
cast al hijo incorrecto!
¿Qué hacemos?
Rezar no es muy “ingenieril", por lo que deberías capturar la
excepción adecuada
Preguntar a la referencia por su
tipo actual de lo que guarda
(operador is)
Puedes preguntar al objeto por el nombre de su
clase, y usarlo para decidir si hacer o no el cast
¡Es la opción más lenta!
Usar reflexión (tema avanzado)
<-
© José Manuel Redondo López
Ref-rescate
Mira el ejemplo anterior: en la llamada a MiMetodo, n es una variable
nueva y distinta en memoria que recibe una copia del valor pasado
void MiMetodo(int n) {
n = 3
}
El paso de parámetros de C# o Java no es complejo: la política
estándar es COPIAR lo que se le pasa a un método
Pasar una referencia ¡implica hacerle
una copia también! out funciona como ref, pero no obliga
a inicializar la variable pasada (¡ref sí!)
Persona p = new Persona();
Salvo que uses ref
Las referencias ocupan un espacio
en memoria, diferente del que
ocupa el objeto al que apuntan
Y así, tenemos dos referencias
¡apuntando al mismo objeto!
El método trabaja con la variable
local n, que se destruye cuando
el método acaba
Así a no se modifica, porque hemos
modificado una copia independiente
Pero la gente habitualmente se
confunde con este mecanismo 
int a = 5;
MiMetodo(a);
Console.Write(a); //Imprime 5
Las referencias son variables que
me permiten acceder (usando .) al
sitio donde verdaderamente está
guardado un objeto
Ambas pueden usarse para modificar los contenidos de un objeto.
Cambiar el valor de una solo rompe el enlace con el objeto
Usar ref implica que no se hacen copias: el parámetro y la
variable pasada son como ¡dos nombres asociados a la
misma localización de memoria!
Y, por tanto, los cambios hechos en los métodos ¡afectan al llamador!
Se usaba para emular el retorno de varios valores
desde un método (hasta que llegaron las tuplas…☺)
5
5 3
a
n
RAM
RAM
p
(Person
instance)
.
void MiMetodo(Persona n) {
n.Nombre = “Paco”;
//Solo rompe el enlace
n = null;
}
Persona p = new Persona();
MiMetodo(p);
// Imprime “Paco”
Console.Write(p.Nombre);
RAM
p
(objeto
Persona)
.
.
n
void MiMetodo(ref Persona n) {
n.Nombre = “Paco”;
//¡Ahora p es también null! :O
n = null;
}
Persona p = new Persona();
MiMetodo(ref p);
//¡NullReferenceException!
Console.Write(p.Nombre);
RAM
(objeto
Persona)
.
p, n
void MiMetodo(out Persona n) {
n = new Persona();
n.Nombre = “Paco”;
}
Persona p;
MiMetodo(out p);
// Imprime “Paco”
Console.Write(p.Nombre);
RAM
p, n
(objeto
Persona)
.
¡El compilador te obliga a darle un valor a todas las
variables out antes de que el método termine!
<-
© José Manuel Redondo López
Errores fantásticos y cómo capturarlos
void CambiarClave(string usuario, string hashClave) {
ConectarSGBD();
CambiarClave(usuario, hashClave);
}
En un mundo ideal, el código solo debería
preocuparse de implementar una funcionalidad
void CambiarClave(…) Pero, por desgracia, ¡¡hay un
montón de problemas que
pueden romper nuestro código!!
❖ Puede ejecutarse con parámetros
erróneos o en momentos inadecuados
por error…o ¡a propósito!
❖ ¡Debemos proteger nuestro código
contra el mal! (o la pereza, la
ignorancia, las ganas de fastidiar…)
❖ ¿Y eso como lo hago?
¡CON EXCEPCIONES!
void CambiarClave(string usuario, string hashClave) {
if (usuario == null)
throw new ArgumentException(“El
nombre de usuario no puede ser null”);
… (Resto del código del método)
}
…
try{
CambiarClave(null, null);
catch(ArgumentException e) {
/*Haz algo para tratar el problema:
- Corregir los argumentos
- Lanzar otra excepción
- … (otra solución) */
} Se informa al llamador de que ha
metido la pata, y se le OBLIGA a tratar
su error en un bloque try-catch
adecuado
Si no, ¡EL PROGRAMA FINALIZA!
void CambiarClave(string usuario, string hashClave) {
if (usuario == null)
throw new ArgumentException(“El
nombre de usuario no puede ser null”);
if (hashClave == null)
throw new ArgumentException(“La nueva
clave no puede ser null”);
if (demasiadosIntentosErroneos())
throw new InvalidOperationException(“La
cuenta está bloqueada, inténtelo más tarde”);
if (!existeUsuario(usuario))
throw new ArgumentException(“El
nombre de usuario debe existir”);
… (más precondiciones)
ConectarSGBD();
CambiarClave(usuario, hashClave);
Se pueden comprobar
varias cosas antes de
ejecutar un código.
Estas comprobaciones
se llaman
PRECONDICIONES
❖ Una vez lanzada una excepción X, la
ejecución de un método se interrumpe
❖ Solo se reanuda si hay un bloque try-
catch rodeando la línea que la lanza, y
su tipo de excepción es compatible (por
polimorfismo) con X
❖ Si no se encuentra algo así en toda la
pila de llamadas, el SO maneja las
excepciones no tratadas
❖ Y entonces….SAYONARA, BABY
❖ Son ROBUSTAS: Los llamadores deben
tratarlas y programar acciones
correctivas para los errores detectados
❖ ¡Los errores no se silencian!
Claves
nulas
Claves
inadecuadas
Cambios de clave
maliciosos
Nombres
de usuario
nulos
¡¡La ejecución
“salta” aquí!!
Las excepciones
se lanzan cuando
TÚ detectas un
problema ANTES
de que el código
del método se
ejecute
Se detectan
argumentos erróneos
(ArgumentException)
o llamadas cuando la
clase está en un
estado incorrecto
(InvalidOperation
Exception)
Nombres de usuario
inexistentes
<-
© José Manuel Redondo López
Orgullo y asertos ☺
void AñadirUsuario(Usuario usr) {
//¿Funciona el programa correctamente?
//... (todas las precondiciones)
_AñadirUsuario(usr);
//¿Se añadió el usuario realmente?
//¿Sigue funcionando bien el programa?
} A veces, cuando programamos nos preguntamos si
nuestro programa funciona bien y las consecuencias
de lo que hacemos
void AñadirUsuario(Usuario usr) {
//¿Funciona el programa correctamente?
//... (todas las precondiciones)
_ AñadirUsuario(usr);
//¿Se añadió el usuario realmente?
//¿Sigue funcionando bien el programa?
}
Estas preguntas tienen un nombre
en el mundo de la programación
void AñadirUsuario(Usuario usr) {
// Funciona el programa correctamente? (invariante)
Debug.Assert(ExisteTablaUsuarios(), “¡La tabla de usuarios
se ha borrado o está corrupta!. Finalizando…”);
//... (todas las precondiciones)
_AñadirUsuario(usr);
//¿Se añadió el usuario reamente? (postcondición)
Debug.Assert(!ExistUser(user), “¡El usuario no se ha
añadido a la base de datos!. Finalizando…”);
//¿Sigue funcionando bien el programa? (invariante)
Debug.Assert(ExisteTablaUsuarios(), “¡La tabla de usuarios
se ha borrado o está corrupta!. Finalizando…”);
}
Los invariantes habitualmente se comprueban al principio
(salvo en constructores) y al final de CADA método
Las postcondiciones al final de su método correspondiente
❖ ¿Qué hago si detecto que mi código
está mal y el programa no funciona
como debe?
❖ El “código de orgullo del ingeniero” te
obliga a matar tu programa,
sacrificándolo al Dios del null ☺
❖ Un programa con ese tipo de error no
puede permitirse que siga en ejecución
❖ ¡Los errores pueden empeorar con el
tiempo!
❖ Este es el motivo por el que se usan
asertos en estas situaciones
❖ Un aserto no negocia, no razona, no
siente pena ni remordimientos…solo
mata tu programa ☺
Invariante
Invariante
Postcondicion
❖ Precondiciones, postcondiciones e
invariantes son la base de la
Programación por Contratos
❖ Dependen del propósito del programa y
sus métodos, así que DEBEMOS
PENSARLOS CON CUIDADO
❖ TÚ eres el responsable de detectar los
errores en tú propio código
❖ Así tus programas serán a prueba de
balas (tanto como sea posible)
❖ Los asertos deben ser eliminados en
modo Release (se quitan solos si se
compila en este modo en Visual Studio)
❖ Los programas en producción no
deberían tener errores así ;)
❖ Las postcondiciones son cosas que DEBEN
ocurrir tras la ejecución de UN MÉTODO
CONCRETO
❖ Los invariantes son cosas que DEBEN
ocurrir durante toda la ejecución de una
instancia (no importa el método)
❖ Si alguna falla, es un ERROR DE
PROGRAMACIÖN
¡¡¡ES CULPA TUYA!!!
<-
© José Manuel Redondo López
¿Por qué eres tan genérico?
void Imprime(bool [] a) {
foreach (bool n in a)
Console.Write(n + “, “);
}
void Imprime(int [] a) {
foreach (int n in a) Console.Write(n + “, “);
}
void Imprime(string [] a) {
foreach (string n in a) Console.Write(n + “, “);
}
¿No te parece que a veces
repites el mismo código una y
otra vez, cambiando solo
pequeñas cosas?
void Imprime(bool [] a) {…}
void Imprime(int [] a) {…}
void Imprime(string [] a) {…}
void Imprime <T>(T [] a) {
foreach (T n in a) Console.Write(n + “, “);
}
¡Para evitar eso se creó la
genericidad!
❖ Y ahora…¿qué?
❖ Cada vez que llamas a un método
genérico, el compilador de C# crea
una versión concreta, reemplazando T
por el tipo que uses en la llamada
❖ Ejemplo: Imprime<string>(nombres)
crea (¡automáticamente!) el ultimo
método de la primera viñeta
❖ Y si ya se creo antes, lo reutiliza
Imprime<bool>(arrayDeBool);
Imprime<int>(numeros):
Imprime<string>(nombres);
…
Imprime<int>(names); ¡NO COMPILA!
class MiLista<T> {
… //T puede usarse en todo el cuerpo de la clase
public T Get(int position) {…}
//Por favor, NO pongas <T> en cada método
}
Usar métodos
genéricos es muy
sencillo, pero ¡no
puedes engañar
al compilador!
Imprime (arrayDeBool);
Imprime (numeros):
Imprime (nombres);
Pero especificar los
parámetros de tipos en
llamadas a métodos
genéricos ¡en la mayoría de
casos no es necesario!
C# puede calcular los parámetros de tipo
automáticamente por ti
De esta forma, llamar a un método, clase
o interfaz genérico ¡no es distinto de
llamar a uno que no sea genético!
❖ Si usas una variable de tipo T en el
cuerpo de un método, ¡solo tendrás
acceso a los métodos de Object!
❖ ¡Salvo que uses bounded generics!
❖ Nos permite establecer restricciones
al tipo que reemplaza a T…¡sin
perder el tipo que estamos pasando!
Un método genérico pone parámetros de tipo
(nombrados entre <>, como <T>) en aquellos lugares
donde debería haber tipos concretos
¡Se crea así un “modelo” o “plantilla” de código!
T First(T [] a, T o) where T: IComparable<T> {
foreach (T n in a)
if (n.CompareTo(a)==0) return n;
return null;
}
Pasa a ser…
Podemos establecer condiciones a los parámetros de
tipo, y quien lo use debe cumplirlos. Esto se hace con
where y es lo que se denomina bounded generics
¡Si el que llama no las cumple, el código no compila!
Clases e Interfaces pueden ser también
genéricos, siendo sus parámetros de
tipo utilizables dentro de su cuerpo
({…})
¡NO REDECLARES EL PARÁMETRO DE
TIPO EN CADA MÉTODO!
<-
© José Manuel Redondo López
Stranger <T>hings
❖ La máquina virtual de .Net se ha modificado
para soportar genericidad de forma nativa
❖ Sin embargo, la máquina virtual de Java (JVM)
no soporta genericidad
❖ El compilador de Java traduce los tipos
genéricos a Object
❖ A nivel de la JVM se usa polimorfismo
❖ Eso hace que Java tenga limitaciones cuando
trabaja con genericidad que C# no tiene
List<int> lista = new List <int>();
Los tipos primitivos no pueden usarse en Java como
tipos genéricos
class CosaGenerica<T, T2> {
private static T atributo;
…
}
class CosaGenerica <T, T2> {
…
public void ProcesarElemento(Object param) {
…
if (typeof(T).IsInstanceOfType(param)) {
T elementoAProcesar = (T) param;
…
class CosaGenerica <T, T2> {
…
public void Metodo1 (T param) {…}
public void Metodo1 (T2 param) {…}
}
class CosaGenerica <T, T2> where T : new() {
…
public void ProcesarElemento(Object param) {
T elemento = new T();
T[] arrayGenerico = new T[10];
…
Java no puede hacer cast o usar instanceof con
tipos genéricos
Java no puede usar sobrecarga de métodos que solo usen tipos
genéricos que tengan diferente instanciación
C#
List <int> lista = new ArrayList<int>();
class CosaGenerica <T, T2> {
private static T atributo;
…
} Java no puede usar static con
tipos genéricos
class CosaGenerica <T, T2> {
…
public void ProcesarElemento(Object param) {
T elemento = new T();
T[] arrayGenerico = new T[10];
…
class CosaGenerica <T, T2> {
…
public void ProcesarElemento(Object param) {
…
if (param instanceof T) {
T elementoAProcesar = (T) param;
}
…
class CosaGenerica <T, T2> {
…
public void Metodo1 (T param) {…}
public void Metodo1 (T2 param) {…}
}
C#
C#
C#
C#
Java no puede crear nuevas instancias de tipos genéricos y
tampoco puede crear arrays de tipos genéricos
C# necesita declarar el tipo genérico con un tipo especial de
bounded generics para permitirlo, new()
<-
© José Manuel Redondo López
Yo Enumerable
using System.Linq;
…
miClase.Select(…)
miClase.Where(…)
miClase.Aggregate(…)
miClase.OrderBy(…)
miClase.GroupBy(…)
miClase.Union(…)
miClase.Zip(…)
Ser enumerable significa que “contienes” elementos, y que se
pueden obtener uno a uno desde el primero hasta el último
Básicamente, te pasas a ti mismo al IEnumerator,
y ¡dejas que haga el trabajo “sucio”!
class EnumerableThing <T> : IEnumerable<T> {
public T GetElement(int pos) { …}
public int Length { get; private set; }
//No necesitas nada más para ser IEnumerable! :O
public IEnumerator<T> GetEnumerator() {
for (int i = 0;i<this.Length;i++)
yield return GetElement(i);
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}
A no ser que ya sepas Kung-Func ☺…
La programación funcional hace esto mucho mucho más fácil!!!
class YoTeEnumero<T> : IEnumerator<T> {
public IEnumerateYou(CosaEnumerable<T> en) {…}
}
class CosaEnumerable<T> : IEnumerable<T> {
public IEnumerator<T> GetEnumerator() {
return new YoTeEnumero <T>(this);
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}
class YoTeEnumero <T> : IEnumerator<T> {
private EnumerableThing<T> myEnum;
private int currentPos = -1;
public IEnumerateYou(EnumerableThings<T> en) {
myEnum = en; }
public void Dispose() {
… //Cuando acaba el enumerable… }
public bool MoveNext() {
currentPos++;
return currentPos < myEnum.Length; // ¿Más? }
public void Reset() {
//Siempre hay que ponerlo ANTES del primer elemento!!
currentPos = -1; }
public T Current {
get { return myEnum.Get(currentPos); } }
object IEnumerator.Current => Current;
}
Debes implementar
los métodos del
interface
IEnumerator con tus
métodos
Simplemente devuelve una instancia de una clase que implementa
IEnumerator<T> que creas, encargada de recorrer TUS ELEMENTOS
Así simplemente, ¡expones un objeto que
enumera tus propios elementos!
Las hace compatibles con Linq
Permite usarlas en un
foreach
Muchas colecciones de C# lo son, pero lo puedes hacer a tus propias clases!
(Leer datos de ficheros, BBDD, la nube, conexiones de red…)
MoveNext()
:true
MoveNext()
:true
MoveNext()
:true
MoveNext()
:false
Reset() Clase IEnumerable
foreach (var elem in myClass) {
… //Procesa cada elemento
}
Para implementarlo, lo primero es hacer
que tu clase implemente IEnumerable<T>
Clase IEnumerable
Clase IEnumerator
Users
GetEnumerator
Deja que yo te
enumere!
Implementar IEnumerable le da a tus clases superpoderes!
<-
C#
© José Manuel Redondo López
© José Manuel Redondo López
Programación Funcional (en pocas palabras)
❖ Pero, ¿y si lo que yo quiero es escribir
“trozos de código” en lugar de usar
métodos que ya tenga? … ¡PUEDES!
❖ Las funciones lambda permiten insertar
“trozos de código” donde quieras
❖ Son como métodos, pero más simples
❖ Por ejemplo, en vez de:
double Mitad(double numero) {
return numero / 2;
}
❖ Escribes: numero => numero / 2
❖ ¡Las funciones lambda pueden también
tener varias líneas entre {} (si pasa eso,
también deben usar return) y varios
parámetros (entre ())!
void Metodo() {
int numero;
numero = 4;
Console.WriteLine(numero);
int[] numeros = new int[10];
numeros[0] = numero;
CalculaAlgo(numero);
}
void CalculaAlgo(int numero){<…>}
Solemos hacer esta clase de
cosas con tipos “normales”
void Metodo() {
{…} codigo;
codigo = {<el código>};
Console.WriteLine(codigo);
{…}[] montones_de_codigo = new {…}[10];
montones_de_codigo[0] = codigo;
CalculaAlgo(codigo);
}
void CalculaAlgo({…} codigo){<…>}
Pero, ¿qué pasaría si en lugar
de un tipo usásemos “trozos
de código”?
❖ Espera…¿Quieres decir que “trozos de
código” (métodos) pueden funcionar
como si fueran tipos? ¡SI!
❖ Y ¿como hago para escribir el tipo de
una variable que guarda “código”?
¡Usando sus parámetros y tipo de retorno
(su signatura) con el tipo Func!
Func<(tipo de param. 1,…, tipo de param.
N, tipo de retorno>
void Metodo() {
Func<double> codigo;
codigo = getRandomNum;
Console.WriteLine(codigo);
Func<double>[] montones_de_codigo = new
Func<double>[10];
montones_de_codigo[0] = codigo;
CalculaAlgo(codigo);
}
void CalculaAlgo(Func<double> codigo){<…>}
Por tanto, los tipos Func<..>
permiten guardar cualquier
método compatible…y
hacer cosas divertidas ☺
getRandomNum (sin los (), ¡que
no lo estamos llamando!) es un
método sin parámetros que
devuelve un double. ¡Podemos
guardarlo a él (y a cualquiera
como él) en una Func<double>!
void Metodo() {
Func<double, double> codigo;
code = numero => numero / 2;
Console.WriteLine(codigo);
Func<double, double, double>[] mucho_codigo =
new Func<double, double, double>[10];
mucho_codigo[0] = (x,y) => {var a = x * y;
return a;};
CalculaAlgo(()=>Math.PI*2);
}
void CalculaAlgo(Func<double> code) {<…>}
Por tanto, puedes usar
y guardar funciones
lambda en cualquier
parte del código
Y esto cambia la forma en la que
programarás para siempre ☺
¿Quieres saber más? ¡Matricúlate
en TPP! :D
<-
© José Manuel Redondo López
Func-iones & cosas chulas
Func<type param 1, type param 2, ..., return type> f;:
Puede guardar casi cualquier método
Action<type param 1, type param 2, ..., type param N> a;:
Solo guarda métodos que devuelven void
Predicate<type param 1, type param 2, ..., type param N>
p;: Solo guarda métodos que devuelven bool
//Ejemplos…
var result = f(…);
a(…);
if (p(…)) {}
C# tiene el tipo Func y otros dos
tipos similares (Action y Predicate,
llamados delegados genéricos) para
guardar código compatible
//Guardar un método estático
Func<double, double> mat1 = Math.Sin;
//Crear código “in situ” con una función lambda
Func<double, double> mat2 = x => Math.Sqrt(x);
var listInt = new List<int>();
//Guardar un método de instancia
Action<int> addL = listInt.Add;
mat1 (100); //Llama a Math.Sin(100)
//Añade 3 al objeto listInt
addL(3);
El código que se guarda en los delegados
genéricos puede tener distintos orígenes.
¡Puedes incluso crearlo “in situ” con las
funciones lambda!
Func<double, double> MetodoEjemplo2(int numero) {
return x => x * numero;
}
…
//Devuelve el código creado por la función llamada
Func<double, double> f1 = MetodoEjemplo2(4);
Console.WriteLine(f1(3)); // Devuelve 12 (3 * 4)
Por supuesto, puedes crear
funciones que devuelvan código
usando los parámetros que pases
//Locura Func-ional ☺
//Recibe dos funciones y un double, devuelve un double
Func<Func<double, double>, Func<double, double>,
double, double> fLocura = (fA, fB, x) =>
fA(fB(x));
…
//Devuelve 0,978 (Sin(Sqrt(PI))
Console.WriteLine(fLocura(Math.Sin, x => Math.Sqrt(x),
Math.PI));
Una vez que domines esto, puedes hacer cualquier
locura que se te ocurra: combinar llamadas a
funciones, retornar funciones que llaman a otras
funciones pasadas como parámetro, …
¡SIENTE EL PODER DE LA λ!
void MetodoEjemplo(Func<double, double> f) {…}
…
//Paso de parametros
MetodoEjemplo(Math.Sin);
MetodoEjemplo(x => Math.Sqrt(x));
Lo mismo ocurre cuando
pasas parámetros
//Estructura de datos
List<Func<double, double>> listaFunciones = new
List<Func<double,
double>>();
listaFunciones.Add(Math.Sin);
listaFunciones.Add(x => Math.Sqrt(x));
…
// Devuelve 1 (Sin(90º))
Console.WriteLine(listaFunciones[0](Math.PI / 2));
// Devuelve 2 (Sqrt(4))
Console.WriteLine(listaFunciones[1](4));
Guardar código en estructuras de datos
también es posible. Así puedes acceder a
bloques de código cuanto te hagan falta…¡o
crear diccionarios de código!
<-
© José Manuel Redondo López
λa λa λand
class Funciones{
public static bool MayorEdad (Persona p) {
return p.Edad >= 18; }
…
}
Persona[] personas =Personas.CrearPersonasAleatorias();
Persona[] masDe18 = Array.FindAll(personas,
Funciones.MayorEdad);
Pasar código como parámetro es fácil cuando ya
tienes los métodos a pasar…
Pero normalmente quieres crear el código justo en
el momento en que vas a usarlo
Persona[] masDe18 = Array.FindAll(personas,
class Funciones{
public static bool MayorEdad (Persona p) {
return p.Edad >= 18; } }
);
Vale, pero esto es muy bruto
(Si pensaste en poner solo el método sin la clase, también es muy bruto)
Ya que NO PUEDES hacer esto, las funciones lambda te cubren esta
necesidad
// Deja que el compilador infiera los tipos
masDe18 = Array.FindAll(personas,
(p) => { return p.Edad >= 18; });
Puedes evitar escribir el tipo de los parámetros y
dejar a C# inferirlos “automágicamente”
También se cambia la palabra “delegate” por =>
separando cabecera y cuerpo del método
//Varias líneas y varios parámetros
/* Escribe cada persona a un fichero usando su ToString por
defecto, o una función personalizada que convierta la
persona a un string si se proporciona */
EscribirALog(personas, (persona, toStrFunc) => {
if (toStrFunc == null)
return persona.ToString();
else
return toStrFunc(persona);
});
Por supuesto, las funciones lambda más complejas no tienen
estos atajos…pero aún así son mucho más sencillas de escribir
Y de esta forma puedes convertir CUALQUIER método a una
función lambda, ¡pasando su código solo cuando lo necesites!
masDe18 = Array.FindAll(personas,
delegate (Persona p) { return p.Edad >= 18; }
); Las funciones lambda son como
métodos “normales” pero con una
sintaxis mucho más breve
// Con solo un parámetro, los () son opcionales
masDe18 = Array.FindAll(personas,
p => { return p.Edad >= 18; });
// Con una sentencia, los {} y el return pueden
quitarse
masDe18 = Array.FindAll(personas,
p => p.Edad >= 18);
Las funciones lambda con un solo parámetro y/o una
sola línea de código pueden simplificar su sintaxis
aún más quitando (), {} y return
¡Pero aún así
se escribía
mucho!
Empezaron como tipos delegados anónimos,
permitiéndote declarar un delegado sin nombre
en cualquier parte del programa en el que se
necesite código
¡Y puedes escribir aún
menos código en
varios casos!
¡El resultado es muy similar a las λ-abstracciones del λ-
calculo clásico! (de ahí el nombre ;))
<-
© José Manuel Redondo López
Linqin’ park
Sin embargo hay 3 métodos “core” especialmente
importantes: El primero es Filter (Where en C#)
Finalmente, Reduce (Aggregate en C#) hace un cálculo con todos los
elementos del IEnumerable
Devuelve el mismo número de elementos, pero
(probablemente) con otro tipo
Devuelve un solo tipo con el resultado de ese cálculo
También tienes el Map (Select en C#), que ejecuta operaciones
sobre los elementos del IEnumerable, transformándolos
potencialmente en otros tipos (en el ejemplo, ¡se crea una clase
anónima ad-hoc!)
El código pasado al reduce opera con el valor calculado hasta
ahora y el elemento actual, para devolver el resultado del cálculo
actualizado
¡Y ahí comienza la diversión! ¡Cadenas-Linq!
Ienumerable
“Vulgaris”
+ MoveNext()
+ Reset()
+ Current
+ Dispose()
.
var s1 = personas.Where(p =>
p.Empleo.Equals( “Poke-trainer”)
&& p.Ciudad.Equals(“Rojo”));
var s2 = s1.Select(p => new {
Edad = p. Edad,
Genero = p.Genero;
});
¡Mismo tamaño!
Función Utilidad
GroupBy
Agrupa elementos por el valor de
una propiedad
OrderBy
Ordena elementos por el valor de
una propiedad
Join
Une IEnumerables usando el valor
de una propiedad como criterio de
unión
FirstOrDefault
Encuantra el primer element que
cumple con un Predicate
Max, Min,
Average, Sum
Cálculos con elementos, usando
valores de propiedades
… Mucho, mucho más ☺
Dictionary
“Menos18”: 9,
“Adultos”: 3
var dicc = s2.Aggregate(
new Dictionary<string, int> {
{“Menos18”, 0},
{“Adultos”, 0},
},
(dic, dato) => {
if (dato.Edad >= 18)
dic[“Adultos”]++;
else dic[“Menos18”]++;
return dic;
});
Siempre que una operación Linq devuelva un IEnumerable,
se puede concatenar su salida con la entrada de otra
Dictionary
“Menos18”: 9,
“Adultos”: 3
¡Combínalo con var para dejar boquiabierta a más gente! :D
1
var dicc = personas.Where( (1) ).Select( (2) ).Aggregate( (3) );
2
3
using System.Linq;
Ienumerable
“Kriptoniano”
+ MoveNext()
…
+ Dispose()
+ Select(…)
+ Where(…)
…
El espacio de nombres System.Linq incorpora 180+ métodos
nuevos a cualquier IEnumerable (¡incluso los que hayas hecho tú!)
¡Linq tiene muchas más funciones!
Linq es un conjunto de métodos de extensión para cualquier
IEnumerable que les da “superpoderes”: Permite manipular su
información de muchas formas útiles
Devuelve solo los elementos del IEnumerable que
cumplen con un Predicate pasado como parámetro
Obtienes el mismo tipo de elementos, solo que
(probablemente) menos
<-
© José Manuel Redondo López
Rebel yield
Los generadores son muy útiles porque puedes manipularlos
como IEnumerables estándar (¡admiten foreach y métodos de
Linq!) ya que…¡lo son!
Un generador es “un truco de magia” que C# hace
para hacerte creer que tienes algo IEnumerable…
De esta forma puedes construir cualquier
colección siguiendo un ciclo de calcular elemento,
guardar estado, retornarlo, calcular siguiente,
guardar estado, retornarlo...
Por ello, solo necesitan memoria para un elemento de cada
vez, ¡incluso si tienen infinitos elementos!
Pero ¡solo crean elementos cuando se le piden! (la colección
no se crea de antemano)
Usar yield return T te obliga a devolver un IEnumerable<T>
o un IEnumerator<T>
Por tanto sus elementos pueden ser potencialmente infinitos…si quieres
Los generadores siguen una política perezosa: solo trabajan
cuando deben, y nunca más
En la siguiente llamada, se continua desde la PRÖXIMA
línea de código
Siempre que se use yield return, se GUARDA EL ESTADO
actual de la ejecución (¡es una continuación!)
¡NO desde el comienzo de la función!
Realmente tienes una función “disfrazada” de
colección…y ¡no se nota la diferencia!
IEnumerable<T>
MoveNext():bool
Current: T
Reset()
Dispose()
Lists
Arrays
Strings
Generators
C
ó
d
I
g
o
{
(código que
genera cada
elemento)
…
}
∞?
MoveNext()
MoveNext()
MoveNext()
Current
Current
Current
{
(código que
genera cada
elemento)
…
}
IEnumerable<int> NumerosAleatorios(int maxValor,
int maxNumeros = -1) {
Random r = new Random();
int contador = 0;
while (true) {
yield return r.Next(maxValor);
if (maxNumeros > 0)
if (contador >= maxNumeros) yield break;
contador ++;
}
}
var randoms = NumerosAleatorios(1000000, 10);
foreach (var random in randoms)
Console.WriteLine(random);
Los generadores se crean con yield return
IEnumerable<int> NumerosAleatorios(int maxValor,
int maxNumeros = -1) {
Random r = new Random();
int contador = 0;
while (true) {
yield return r.Next(maxValor);
if (maxNumeros > 0)
if (contador >= maxNumeros) yield break;
contador ++;
}
}
Guarda
estado
Retorna valor
Continúa aquí en la
siguiente iteración
Guarda estado
Retorna valor
Continúa aquí en la
siguiente iteración
Código
Principio del método
Pedir un
elemento
¿Es el
prime
ro?
SI
NO
C
o
d
I
g
o
C
o
d
I
g
o
Código
<-
© José Manuel Redondo López
Currifícatelo un poco
Func<int, int> EcuacionRecta(int m, int b) {
return x => m * x + b;
}
//Se convierte en…
Func<int, Func<int, int>> EcuacionRectaCurrificada(int m) {
return b => x => m * x + b;
}
La currificación es un método para convertir una función de
varios parámetros en una sucesión de funciones de un
parámetro equivalentes
Ilegal en lambda cálculo
λxyz. x + y + z
Versión currificada equivalente
λx. λy. λz. x + y + z
Es una técnica usada en cálculo lambda, donde las
funciones no pueden tener más de un parámetro
IEnumerable<Citizen> GenteQuePagaMasQue(string ciudad,
double cantidad) {
return cs.Where(c => c.Ciudad.Equals(ciudad))
.Where(c=> c.Impuestos >= cantidad);
}
//Versión currificada
Func<double, IEnumerable<Citizen>>
GenteQuePagaMasQue (string ciudad) {
return cantidad => cs.Where(c =>
c.Ciudad.Equals(ciudad))
.Where(c=> c. Impuestos >=
cantidad);}
¡Pero permite usar la aplicación parcial!
Func<int, int> equacion1 = EcuacionRecta(5, 3);
Console.WriteLine(equacion1(10)); //53
Func<int, Func<int, int>> equacion2 =
EcuacionRectaCurrificada(5);
//Retorna una Func<int, int> (¡no un valor!)
Console.WriteLine(equacion2(10));
Console.WriteLine(equacion2(3)(10)); //53
La currificación puede parecer rara en un lenguaje
sin restricciones en el número de parámetros…
//IEnumerable
var grandesPagadoresDeOviedo =
GenteQuePagaMasQue("Oviedo", 10000000);
//¡Función que solo trabaja con ciudadanos de Oviedo!
var pagadoresDeOviedo =
GenteQuePagaMasQue("Oviedo");
//¡Procesa menos gente que la función de dos parámetros!
var pagadoresDeOviedoEnLaMedia =
pagadoresDeOviedo(1000);
Dado que esto es una limitación muy fuerte, ¡la
currificación emula poder pasar más de un parámetro!
Con la aplicación parcial se obtiene código “medio
ejecutado” en el que se reemplazan algunos parámetros por
sus valores
❖ Currificar es otra forma de obtener
código a partir de código existente
❖ No se crea “al vuelo” (expresiones
lambda) o se pasan funciones
existentes
❖ ¡Solo recuerda que en C# la
currificación es un REQUISITO para
hacer aplicación parcial!
return cantidad => cs.Where(c =>
c.Ciudad.Equals(“Oviedo”))
.Where(c=> c.Impuestos >= cantidad);}
Como este código “parcialmente ejecutado”
habitualmente hace menos cosas, suele ir más rápido
De este modo se puede obtener código nuevo a partir de
código existente, ¡solo reemplazando algunos parámetros!
<-
© José Manuel Redondo López
Trabajar de más no es lo más
var deWakanda = personas.Where(p => {
Console.WriteLine(“Filtrando: " + p.Nombre);
return p.Cuidad.Equals("Wakanda");
}); ¿Sabes qué significa realmente “Linq está lleno de
métodos perezosos”?
//Obliga a procesar todos los elementos
foreach (var wakandes in deWakanda)
Console.WriteLine(wakandese.Nombre);
//¡También oblige a hacerlo!
deWakanda.Last();
var listaNombres = deGotham.Aggregate(new
List<string>(),
(lista, gothames) => {
list.Add(gothames.Nombre);
return lista;
});
//¡Ahora imprime “Filtrando X" y "Procesando X"!}
Habitualmente se termina una “cadena Linq” con un Reduce
(Aggregate)
Los Reduce no pueden ser perezosos, y por tanto obliga a procesar
todos los elementos del generador: ¡Deben hacer el cálculo con
todos los elementos!
var personasPorCuidad = personas.GroupBy(p => {
Console.WriteLine(“Agrupando:" + p.Nombre);
return p.Ciudad;
});
//¡Solo ahora se procesarán las personas!
foreach (var grupo in personasPorCuidad) {
var csOrdenados = grupo.OrderBy(p => p.Nombre);
foreach (var ciudadano in csOrdenados)
Console.WriteLine(ciudadano.Nombre);
}
//Encuentra al primer Gothamés
var tipoDeGotham = personas.First(p => {
Console.WriteLine(“Encontrado: " + p.Nombre);
return p.Ciudad.Equals("Gotham");
});
var deGotham = personas.Where(p => {
Console.WriteLine(“Filtrando: " + p.Nombre);
return p.Cuidad.Equals("Gotham");
}).Select(p => {
Console.WriteLine(“Procesando: " + p.Nombre);
return new {
Nombre = p.Nombre + p.Apellidos,
Edad = p.Edad + “ d. B. (después de Batman)"
}; }); //¡No imprime nada!
Veámoslo…¿Crees que este código va a imprimir
algo?
¡NO! ¿POR QUÉ? ¡Porque nadie le ha pedido
elementos a deWakanda! (el IEnumerable devuelto
es perezoso, ¡solo trabaja si le obligas!)
¿Pero tu quieres
que imprima eso,
verdad?
Entonces, oblígale
a trabajar
Recuerda que las políticas perezosas son más efectivas
cuando no tienes que procesar todos los elementos
Esto también pasa cuando se enlazan varias
operaciones perezosas. Solo cuando realmente se
pidan elementos se ejecutará la "cadena"
Como normalmente no se pone código con efectos
laterales dentro de las funciones lambda usadas en
Linq, estos casos no son un problema típico
¡Solo asegúrate de no hacer operaciones con efectos laterales!
Linq solo procesará todos los elementos cuando sea
necesario, y no se perderá información
Usando cualquier medio para
pedirle elementos al IEnumerable
<-
© José Manuel Redondo López
La hermandad αβλ
¡Simplemente reemplazamos f
por el segundo término y
borramos λf!
Primero, no
deberíamos ejecutar
una expresión λ con
el mismo nombre de
variable ligada
(parámetro) en dos
términos distritos
¡Esta expresión λ tiene dos términos distintos con el
mismo nombre de variable ligada, aunque no son la misma
variable!
Primero comprobamos que no hace falta una conversión α
esta vez
Y luego hacemos otra reducción β con las dos expresiones
que nos quedan
Solo nos queda hacer una reducción β final
¡Si piensas detenidamente cada paso y no te precipitas,
“ejecutar” expresiones λ no debería ser un problema!
❖ En ese momento podríamos "ejecutar" la
expresión (algo llamado también reducción
β o aplicación)
❖ Las aplicaciones usan los dos términos más
a la izquierda de la expresión (aunque el
teorema de Church-Rosser demostró que
se pueden escoger en otro orden)
❖ Reemplazamos las apariciones de la
variable ligada en el cuerpo del primer
término con el segundo término
❖ Borrando la cabecera del primer término
❖ Y dejando el resto de la expresión como
está
…
Y ahora podemos seguir el mismo proceso para
ejecutar la expresión hasta el final
Finalizar la “ejecución” de la expresión λ es
ahora bastante trivial
El λ-cálculo se considera el
lenguaje de programación
universal más pequeño
(λf.λx.fx)(λx.x*x)3
Pero “ejecutar" expresiones λ requiere algo de práctica
Esta expresión λ está compuesta por 3 términos
(fíjate en los paréntesis). ¡Vamos a ejecutarla!
(λf.λx.fx)(λx.x*x)3
(λf.λx.fx)(λy.y*y)3
Es necesaria una conversión α (renombrado) a un nombre de
variable nuevo en cualquiera de los términos
(λf.λx.fx)(λy.y*y)3
(λx.(λy.y*y)x)3
(λx.(λy.y*y)x)3
(λy.y*y)3 (λy.y*y)3 3 * 3
1st term 2nd term 3rd
term Conversión α
Reducción β
Reducción β
Reducción β
<-
© José Manuel Redondo López
Haciendo Joining por la mañana
Pero puedes
relacionarlas
entre ellas
usando los
valores de
alguno de ellos
¡En estos casos, es el momento de usar el Join Linq!
El segundo parámetro es el atributo a usar de las entidades del
primer IEnumerable. El tercer parámetro es el atributo
correspondiente en el tercer IEnumerable
A veces tienes dos IEnumerables con entidades que no se
relacionan directamente usando atributos
Siempre se hace Join a IEnumerables. El primero es sobre el
que se llama el Join, el segundo es el primer parámetro del Join
Join acepta cuatro parámetros, ¡que
vamos a explicar a continuación!
Es una función que recibe una instancia del primer
IEnumerable y otra que encaja con ella del segundo y que
se puede usar para sacar solo los datos que necesites
El ultimo parámetro del Join permite elegir los
atributos con los que te quedas de entre ellos
¡Pero seguramente no necesitas tanta información!
Hecho esto, internamente tendremos objetos
de una macro-clase anónima con todos los
atributos de los objetos que se han unido
¡Hay que seguir el mismo orden que los IEnumerable usados!
¡Con los datos de la primera instancia repetidos en cada una de
ellas! (¡pero luego puedes hacer GroupBy con ellas! ☺ ☺)
Y recuerda que se comporta como un
Join de bases de datos
Si una instancia del primer encaja con 5 instancias del
Segundo, se obtienen 5 instancias de esa macro-clase anónima
Máquina
IP: string
OS: string
…
EntradaLog
IP: string
Contenido: string
…
“10.0.0.1”
“Windows“
“10.0.0.2”
“Windows“
“10.0.0.99”
“Linux“
Maquinas: IEnumerable<Maquina>
“10.0.0.1”
“Virus“
“10.0.0.1”
“Troyano“
“10.0.0.37”
“Kaiju“
Log: IEnumerable<EntradaLog>
var Unidos = Maquinas.Join (Log,
??,
??,
?? );
var Unidos = Maquinas.Join (Log,
maquina => maquina.IP,
log => log.IP,
?? );
El tipo de estos atributos ¡debe implementar un
Equals entre ellos!
Luego debes especificar los atributos cuyo valor
será usado para unir entidades
var Unidos = Maquinas.Join (Log,
maquina => maquina.IP,
log => log.IP,
(maquina, log) => new {
Origen = “[“ + maquina.IP + “]”,
log.Contenido,
}
);
“[10.0.0.1]”
“Virus“
“[10.0.0.1]”
“Troyano“
“[10.0.0.37]”
“Kaiju“
Unidos: IEnumerable<(clase anónima)>
<-
© José Manuel Redondo López
Group (By), mi villano favorito
Hay ocasiones en las que tienes muchas entidades con
algunos valores en común
Para ello ¡Es la hora de usar GroupBy!
Lo más típico en estos casos es agruparlas
considerando estos valores comunes
Empecemos diciendo que lo que se obtiene es
un IEnumerable de “grupos”
Por tanto, es muy útil para saber cual fue este valor sin consultar los
elementos, y hacer operaciones con grupos (Sort, Where…)
Esta propiedad Key ¡guarda el valor del atributo usado para
agrupar las entidades!
IGrouping es una clase hija de IEnumerable, que añade la
propiedad Key
El IEnumerable “interno” es una instancia de IGrouping
Pero este IEnumerable “interno” ¡es especial!
No te olvides de que ¡puedes agrupar
entidades usando expresiones también! (no
solo valores de propiedades)
Por tanto, GroupBy simplemente
clasifica entidades existentes
Obtienes las mismas entidades, pero
distribuidas en distintos grupos de
acuerdo al valor que especifiques
“i7 8700”
“Intel“
“Ryzen 2700”
“AMD“
“i9 9900”
“Intel“
CPUs: IEnumerable<CPU>
var porMarca = CPUs.GroupBy(cpu => cpu.Marca);
Por tanto, obtienes un IEnumerable de
IEnumerables ☺
Pero ¿que es realmente un grupo? ¡Otro
IEnumerable!
Como ves, ¡usarlo es muy simple! Solo hay que
especificar cuál es el origen (atributo, expresión…)
de los valores que quieres usar para agrupar
“Ryzen 1700”
“AMD“
(Grupo de todas las CPUs
Intel)
(Grupo de todas las CPUs
AMD)
porMarca: IEnumerable<???>
El problema es entender cuál es la salida del GroupBy (y
cómo manipularla)
“i7 8700”
“Intel“
“Ryzen 2700”
“AMD“
“i9 9900”
“Intel“
“Ryzen 1700”
“AMD“
porMarca: IEnumerable<(IEnumerable “especial”)>
“i7 8700”
“Intel“
“Ryzen 2700”
“AMD“
“i9 9900”
“Intel“
“Ryzen 1700”
“AMD“
porMarca: IEnumerable<IGrouping<string, CPU>>
var porVelocidad = CPUs.GroupBy(cpu =>
cpu.GetGhz());
Key:”AMD”
Key:”Intel” IGrouping
IGrouping
<-
C#
© José Manuel Redondo López
© José Manuel Redondo López
Protección contra Lock-uras
List<int> numeros = new List<int>();
for (int i = 0; i < 1000; i++) {
new Thread(() => {
for (int j = 0; j<10000;j++)
numeros.Add(j);
} ).Start();
//Podría (o no) lanzar una excepción
}
“Atacar" (escribir :)) sobre
un recurso no thread-safe
siempre es mala idea
BlockingCollection<int> numerosThreadSafe =
new BlockingCollection<int>();
for (int i = 0; i < 1000; i++) {
new Thread(() => {
for (int j = 0; j<10000;j++)
numerosThreadSafe.Add(j);
}).Start(); //¡No lanza excepciones!
}
Esto es muy peligroso, pero podemos resolverlo. La primera
opción es usar estructuras de datos que SI sean thread-safe
List<int> numeros = new List<int>();
for (int i = 0; i < 1000; i++) {
new Thread(() => {
for (int j = 0; j < 10000; j++) {
lock (new object()) { //¡Crimen mayor! :( :(
numeros.Add(j);
}
}
}).Start(); //Error en ejecución seguro
Pero lock solo funciona correctamente si todos los threads que
acceden a la sección crítica ¡hacen lock sobre el mismo objeto!
Si no, lock no será efectivo: algunos threads no esperarán junto
con otros, ya que no están asociados al mismo “objeto
bloqueador"!
class SeccionCriticaDeClase {
private static StreamWriter ficheroLog = new
StreamWriter(“Log.txt");
//Otra opción si no tenemos un objeto adecuado
private static object blocker = new object();
…
public void CodigoDelThread() {
lock (logFile) // lock(bloqueador) {
ficheroLog.WriteLine(txt);
//...
}
}
}
Otra implementación típica es un objeto
bloqueador a nivel de clase
Debe usarse si la sección crítica es compartida por todas las
instancias de una clase, y ninguna instancia accede a la misma
al mismo tiempo que otra
List<int> numeros = new List<int>();
for (int i = 0; i < 1000; i++) {
new Thread(() => {
for (int j = 0; j < 10000; j++) {
//¡Todos los threads hacen lock
//en el mismo objeto!
lock (numeros) {
numeros.Add(j);
}
}}).Start();
}
class SeccionCriticaDeInstancia {
private List<int> listaCritica = new List<int>();
//Otra opción si no tenemos un objeto adecuado
private object bloqueador = new object();
…
public void CodigoDelThread() {
lock (listaCritica) //lock(bloqueador) {
listaCritica.Add(datos);
//...
}
}
}
La clase List de C# NO es thread-safe: fallará de forma
aleatoria (y con diferentes errores) si se le añaden
elementos desde varios hilos (¡comportamiento errático!)
Otra opción es usar
bloques lock(objeto) {...}
para asegurarse de que
varios threads no
acceden
concurrentemente a la
estructura de datos
lock duerme todos los threads que
llegan a él, dejándoles pasar uno a
uno a su sección crítica ({...})
Esta opción se usa si todos los threads usan el
mismo objeto para acceder a la sección crítica
La concurrencia se pierde dentro del {...}, pero...¡el programa funciona!
¡NUNCA uses un tipo básico!
Los objetos bloqueadores pueden ser objetos
existentes u objetos creados ad-hoc (¡tu decides!)
Una implementación típica es usar un
objeto bloqueador a nivel de instancia
<-
© José Manuel Redondo López
Viviendo la vida Locka
Las situaciones en las que podemos encontrar secciones
críticas son variadas, pero hay escenarios muy comunes
en las que podemos encontrar una
En una sección crítica, con que solo un thread ESCRIBA en
ella, todos los demás (sin importar si escriben o leen)
deben proteger el acceso a la misma
Lock necesita un objeto de parámetro, que se comporta
como una puerta
class LoggerConcurrente {
// Solo un fichero, el mismo para cada instancia
private static StreamWriter fichero = new
StreamWriter(@"log.txt", true);
// Otra opción (util si la sección crítica no es una
// instancia)
private static object bloqueador = new object();
public void WriteToLog(string txt) {
lock (fichero) {
fichero.WriteLine(txt);
}
}
}
Si la sección crítica es LA MISMA para cada objeto de la
clase, se puede hacer lock sobre un objeto/atributo
static o sobre static object bloqueador = new object();
private List<string> listaNombres = new List<string>();
void AddNombre(string nom) { //Se llama desde thread 1
// lock porque escribe sobre la sección crítica
lock (listaNombres) {
listaNombres.Add(nom);
}}
string GetNombre(int posicion) { //Se llama desde thread 2
// lock porque thread 1 escribe sobre la sección crítica
lock (listaNombres) {
return listaNombres[posicion];
}}
Lock y
ReaderWriterLockSlim son
mecanismos de protección
class ListaNombresConcurrente {
// Uno distinto por instancia de ListaNombresConcurrente
private List<string> listaNombres = new List<string>();
// Otra opción (util si la sección crítica no es una
// instancia)
private object bloqueador = new object();
public void AddNombre(string nom) {
lock (listaNombres) {
listaNombres.Add(nom);
}
}
public string GetNombre(int posicion) {
lock (listaNombres) {
return listaNombres[posicion];
}
}
}
Y entonces
debe decidirse
el contexto del
lock…
Pero para usar lock bien, ¡hay que tener cuidado!
Para que funcione, todo hilo que se ejecute sobre la sección
crítica debe hacer lock sobre la misma “puerta”
Por puerta se entiende el objeto, no las referencias que apuntan
a ellos. ¡Dos referencias pueden apuntar al mismo objeto!
Si no es posible, se puede usar private object
bloqueador = new object();
Si la sección crítica está a nivel de instancia (cada
instancia tiene su propia sección crítica), se hará lock
sobre el objeto de instancia/atributo que la modele
¡Piensa con cuidado el
contexto de la sección
crítica y elige con
cabeza!
Threads
Estructuras de
datos de
System.Collections
Datos de 64 bits
(long, double…)
Sentencias que se
deben ejecutar sin
interrupción
Threads Threads
<Sección crítica>
Lee Lee
Escribe
Protección contra
Threads
< Sección crítica >
lock(objeto1) lock(objeto2)
¡Este thread entra
en la sección crítica
sin restricciones!
Si se usan distintas “puertas” para threads que entre en la
misma sección crítica, el programa fallará 
<-
© José Manuel Redondo López
Transfórmalo en ParaLelo
Hay operaciones que tardarían mucho tiempo si
las hacemos secuenciales ¡Pero puedes convertir el código en paralelo de forma
muy sencilla usando TPL!
Un ejemplo es esta función de cracking de passwords
SHA1 sencilla
¡En este caso el código es 3,2 veces más rápido!
PLinq también te permite hacer algo similar
¡Usa lock / ReaderWriterLockSlim / estructuras de datos thread-safe sabiamente!
¡Este código es 3,67 veces más rápido solo por usar
Parallel.ForEach!
Se puede hacer algo equivalente con bucles for
(Parallel.For) (el rendimiento que se gana es el mismo)
¡Esto es 2,5 veces más rápido que hacerlo secuencial! (y
3,5 veces si usamos CrackSHA1TPL en lugar de ésta)
¿Hay que crackear
varias claves? ¡No
hay problema! TPL
también te deja
crackear todas
concurrentemente
con Parallel.Invoke!
string CrackSHA1MonoThread(string pwdHash) {
foreach (var palabra in ListaPalabras.Español) {
var hash = CalculaHashSHA1(palabra);
if (hash.Equals(pwdHash))
return palabra; //¡Descifrada!
}
return null;
}
string CrackSHA1MonoThread2(string pwdHash) {
for (int i = 0; i < ListaPalabras.Español.Length; i++) {
… (mismo código)
}
…
string CrackSHA1TPL2(string pwdHash) {
string result = null;
Parallel.For(0, ListaPalabras.Español.Length, i => {
var palabra = ListaPalabras.Español[i];
… (mismo código)
}
…
var r1 = CrackSHA1MonoThread(pwd1);
var r2 = CrackSHA1MonoThread(pwd2);
var r3 = CrackSHA1MonoThread(pwd3);
Se convierte en:
Parallel.Invoke(
() => r1 = CrackSHA1MonoThread(pwd1),
() => r2 = CrackSHA1MonoThread(pwd2),
() => r3 = CrackSHA1MonoThread(pwd3)
);
static IEnumerable<string> CrackMultipleSHA1TPL(
IEnumerable<string> pwdHashes) {
string result = null;
List<string> clavesCrackeadas = new List<string>();
Parallel.ForEach(ListaPalabras.Español, palabra => {
var hash = CalculaHashSHA1(palabra);
foreach (var passwordHash in pwdHashes) {
if (hash.Equals(passwordHash)) {
lock (clavesCrackeadas) {
clavesCrackeadas.Add(palabra); //¡Descifrada!
}
}
}
});
return crackedPasswords;
}
var pwdLinq = ListaPalabras.Español.First(palabra =>
CalculaHashSHA1(palabra).Equals(passwordHash));
…
var pwdPLinq = ListaPalabras.Español.AsParallel()
.First(palabra =>
CalculaHashSHA1(palabra).Equals(passwordHash));
Podrían aparecer problemas de secciones críticas en el código, ¡y hay que tratarlos!
Pero ¡NO OLVIDES QUE EL CÓDIGO VA A
EJECUTARSE EN PARALELO!
Nunca debes preocuparte del número de hilos
que se crean. ¡Ambas librerías calculan el
óptimo para tu máquina automáticamente!
string CrackSHA1TPL(string pwdHash) {
string result = null;
Parallel.ForEach(ListaPalabras.Español, palabra => {
var hash = CalculaHashSHA1(palabra);
if (hash.Equals(pwdHash))
result = palabra; //¡Descifrada!
});
return result;
}
<-
© José Manuel Redondo López
Cláusulas abusivas
//Inicializar
Thread t1 = new Thread(() => HazCosas1());
Thread t2 = new Thread(() => HazCosas2());
//Arrancar
t1.Start();
t2.Start();
//Esperar a que los hilos terminen
t1.Join();
t2.Join();
Los threads son la forma más básica de
convertir código existente en concurrente
string parametro1 = "...";
string parametro2 = "...";
var t1b = new Thread((param) => HazMasCosas1(param));
var t2b = new Thread(() => {
return HazMasCosas2(); });
var t3b = new Thread((param1, param2) => {
HazMasCosas3(param1, param2); });
t1b.Start(parametro1);
var ret = t2b.Start();
t3b.Start(parametro1, parametro2);
¡Pero no pueden recibir varios
parámetros ni devolver valores!
string parametro1 = "...";
string parametro2 = "...";
string valorRetornado;
var t2c = new Thread(() => {
valorRetornado = HazMasCosas2(); });
var t3c = new Thread(() => {
HazMasCosas3(parametro1, parametro2); });
//Arrancar
t2c.Start();
t3c.Start();
//Esperar a que los hilos terminen
t2c.Join();
t3c.Join();
Se usan variables libres
(variables definidas fuera del
código concurrente) como
parámetros o para devolver
cosas desde los hilos
var calculos = new LinkedList<string>();
var t1d = new Thread(() => {
int iteraciones = 10000;
while (iteraciones-- > 0) {
lock(calculos) {
calculos.AddFirst(HazAunMasCosas2(parametro1));
}
}
});
var t2d = new Thread(() => {
int iteraciones = 10000;
while (iteraciones-- > 0) {
lock(calculos) {
calculos.AddFirst(HazAunMasCosas2(parametro2));
}
}
});
…
Console.WriteLine(calculos.Count); //¡Ahora si!
¡Recuerda usar los mecanismos de bloqueo que
necesites en estos casos si la variable no es thread-safe!
string parametro1 = "...";
string parametro2 = "...";
var t3c = new Thread(() => {
HazMasCosas3(parametro1, parametro2); });
…
string valorRetornado;
var t2c = new Thread(() => {
valorRetornado = HazMasCosas2(); });
¡Las cláusulas son la
solución a este problema!
var calculos = new LinkedList<string>();
var t1d = new Thread(() => {
int iteraciones = 10000;
while (iteraciones-- > 0) {
calculos.AddFirst(HazAunMasCosas2(parametro1));
}
});
var t2d = new Thread(() => {
int iteraciones = 10000;
while (iteraciones-- > 0) {
calculos.AddFirst(HazAunMasCosas2(parametro2));
}
});
t1d.Start();
t2d.Start();
t1d.Join();
t2d.Join();
Console.WriteLine(calculos.Count); //¡No es 20.000!
¡Pero hay que tener cuidado cuando
varios threads escriben sobre ellas!
¡Manejar mal una sección crítica siempre
es un peligro si no tenemos cuidado!
<-
© José Manuel Redondo López
ReaderWriterLagartoSpock ☺
string Traduce(string clave) {
lock(Cache) {
return Cache[clave];
}
}
void AñadeTraduccion(string clave, string valor) {
lock(Cache) {
Cache.Add(clave, valor);
}
}
Si declaramos ReaderWriterLockSlim CacheLock = …:
❖ CacheLock.EnterReadLock(): múltiples threads pueden hacer
un bloqueo de lectura sobre CacheLock sin ser
bloqueados
❖ CacheLock.EnterWriteLock(): Si un thread entra en un
bloqueo de escritura sobre CacheLock, ningún thread
puede entrar en otro modo sobre él (espera a que el
proceso salga)
❖ CacheLock.EnterUpgradeableReadLock(): Tipo especial de
bloqueo de lectura que promociona a escritura sin falta
de salir del modo lectura
¡ReaderWriterLockSlim es nuestro salvador!
void AñadeTraduccion(string clave, string valor) {
CacheLock.EnterWriteLock();
try {
Cache.Add(clave, valor);
}
finally {
//Siempre libera un bloqueo de escritura, evitando deadlocks
CacheLock.ExitWriteLock();
}
}
bool TraduceConTimeout(string clave, string valor, int timeout){
if (CacheLock.TryEnterWriteLock(timeout)) {
try {
Cache.Add(clave, valor);
}
finally {
//Siempre libera un bloqueo de escritura, evitando deadlocks
CacheLock.ExitWriteLock();
}
return true;
}
else {
return false;
}
}
¿Y si no podemos esperar?
string Traduce(string clave) {
CacheLock.EnterReadLock();
try {
return Cache[clave];
}
finally {
//Siempre acaba liberando el recurso
CacheLock.ExitReadLock();
}
} Ahora si los threads llaman a Translate la mayor
parte del tiempo ¡no se bloquean! (solo hay lecturas)
void AñadeOActualiza (string clave, string valor) {
CacheLock.EnterUpgradeableReadLock();
try {
string resul = null;
if (!Cache.TryGetValue(key, out resul)) {
//¡Promociona a escritura sin salir de lectura 1º!
CacheLock.EnterWriteLock();
try {
Cache.Add(clave, valor);
}
finally {
CacheLock.ExitWriteLock();
}
}
}
finally {
CacheLock.ExitUpgradeableReadLock();
}
}
¿Y si solo escribimos cuando se cumpla una
condición? ¡Es el momento de usar un
bloqueo de lectura promocionable!
Si la mayor parte del tiempo solo leemos, ¡estamos eliminando
la concurrencia innecesariamente todo ese tiempo!
¡Lock es muy radical! ¿No hay algo más preciso?
Segundo, adquiere la propiedad exclusiva de la
variable (Si un thread intenta entrar en cualquier
modo, tendrá que esperar) hasta que salga
Primero, espera hasta que pueda adquirir propiedad exclusiva
de la variable (no hay threads en ningún modo dentro)
Las variables tipo
ReaderWriterLockSlim
hacen Enter y Exit en
uno de esos tres modos
¿Y qué pasa cuando escribimos?
Así ¡podemos entrar en modo escritura
rápidamente y el menor tiempo posible!
Podemos entrar en el lock con un
timeout: si se acaba el tiempo, no se
adquiere y la operación no puede
realizarse en el tiempo indicado
Por tanto, ¡ReaderWriterLockSlim es más eficiente, más
preciso y tiene más opciones de uso que lock!
Si ambos métodos se ejecutan en hilos
diferentes y Cache no es thread-safe,
necesitamos proteger escrituras y
también lecturas (¡ya que escribimos!)
<-
© José Manuel Redondo López
El maestro marionetista
Luego el Master solo tiene que esperar hasta que todos
los hilos que ejecutan código de los Workers terminen
(método Join)
¡Habitualmente los Workers se ejecutan solo sobre su
propia sección de los datos!
Y, finalmente, devolver el resultado final de la operación
agregando adecuadamente todos los resultados parciales
Los Workers son habitualmente bastante sencillos: ejecutan
una operación sobre su propia porción de los datos
No hay que olvidarse que si se necesitan parámetros o variables
para devolver los resultados de los cálculos ¡hay que usar los
atributos de la clase Worker!
Los accesos a esta estructura deben protegerse con lock si
varios Workers escriben en ella y no es tread-safe
❖ El esquema Master-Worker es una forma
manual de obtener paralelización por
división de datos
❖ Una clase (Master), crea, inicializa, ejecuta
y espera por varios hilos
❖ Los datos necesarios para ejecutar el
código en esos hilos se guardan en
instancias de otra clase: los Workers
❖ Hay varios Workers que hacen la misma
operación sobre distintas partes de una
colección de datos
Worker[] workers = new Worker[this.numHilos];
int porHilo = this.datos.Length / numHilos;
for (int i=0; i < this.numHilos; i++)
workers[i] = new Worker (this.datos, i * porHilo,
(i < this.numHilos - 1) ? (i+1) * porHilo - 1:
this.datos.Length-1); // Último worker en los límites!
var threads = new Thread[workers.Length];
for (int i=0;i<workers.Length;i++) {
threads[i] = new Thread(workers[i].Run);
threads[i].Start(); }
}
foreach (Thread t in threads) {
t.Join(); //Espera a que acaben todos los hilos
}
foreach (Worker worker in workers) {
<obtiene los resultados parciales>
}
return <resultado final>;
Solo entonces el Master puede obtener cada resultado
parcial calculado por cada Worker de forma fiable
void Run() {
…. (código del worker para inicializar los cálculos)
for (int i= this.indiceDesde; i<=this.indiceHasta; i++)
<hacer cálculos>
}
Worker(<datos>, int indiceDesde, int indiceHasta,
<estructura de datos que recoge resultados>) {
… (código del constructor)
}
void Run() {
…. (código del worker para inicializar los cálculos)
for (int i= this.indiceDesde; i<=this.indiceHasta; i++) {
<hacer cálculos>
lock (<estructura de datos que recoge resultados>) {
<añadir resultado parcial>
}
}
}
El Master primero inicializa los objetos Worker con los datos y
los límites del trozo de datos sobre el que se ejecutan. Luego,
crea y arranca un hilo asociado con cada Worker
M
W
W
W
<datos>
El código de los hilos no puede devolver nada ni aceptar
parámetros, así que hay que usar este “truco”
¡No te olvides de inicializar estos datos en el
constructor del Worker!
Algunas veces los Workers guardan su resultado parcial
en una estructura de datos común pasada por el Master
<-
© José Manuel Redondo López
Consumiendo productividad
Por tanto, dos clases trabajan en
paralelo con una estructura de
datos compartida
Pero si la estructura no es
thread-safe, ¡podemos tener
un problema!
¡Nunca esperes dentro del lock a que se inserten
elementos! (¡deadlock!)
Si no hay elementos, sal del lock y deja que un Producer
pueda insertar alguno
Luego, espera si no se ha podido sacar un elemento, pero siempre
fuera del lock (o haz otra cosa antes de volver a intentarlo)
¡Usar lock así (o una estructura de datos thread-
safe) nos asegura que funcionarán sin problemas!
❖ El esquema Productor-Consumidor es una
forma típica de trabajar con código
concurrente donde unas tareas
(Producers) añaden elementos a una
estructura de datos para que los usen
otras (Consumers)
❖ Tanto Producers como Consumers
comparten las estructuras de datos donde
se añaden y extraen
❖ ¡Y esto puede ser un problema si no
sabemos lidiar con ello!
void HiloDelProducer() {
…
while (true) {
var producto = new Producto(…);
lock (cola)
cola.Enqueue(producto);
}
}
… (rest de código del producer)
}
void HiloDelConsumer() {
…
while (true) {
Producto producto = null;
lock (cola) {
while (cola.Count == 0)
Thread.Sleep(100); //¡Nadie puede acceder a cola!
producto = cola.Dequeue();
}
… (resto del código del consumer)
}
}
Sin embargo, debemos tener cuidado en los Consumers si la
estructura de datos está vacía…
void HiloDelConsumer() {
…
while (true) {
Producto producto = null;
lock (cola) {
if (cola.Count != 0)
producto = cola.Dequeue();
}
… (resto del código del consumer o esperar si
producto es null)
}
}
Comprueba solo el nº de elementos de la estructura
en vez de eso
Dependiendo del problema, puede haber
varios Producers o Consumers
<estructura de datos>
<estructura de datos>
Sabemos como solucionar esto: ¡lock!
Supón que un Producer y un Consumer comparten
una Queue de C# (que no es thread-safe)…
<-
C#
© José Manuel Redondo López
© José Manuel Redondo López
Pongámonos C#erios ;)
static class ObjectExtensions {
static ConditionalWeakTable<object, ExpandoObject>
miembrosDinamicos = new ConditionalWeakTable<object,
ExpandoObject>();
public static dynamic __this__(this object obj) {
//Obtiene un ExpandoObject asociado con obj
//o crea uno nuevo si no existe
return miembrosDinamicos.GetOrCreateValue(obj);
} ¿Recuerdas los métodos de extensión? ¿Qué pasa si
extendemos object?
Persona p = new Persona();
//Añade dinámicamente atributos al objeto p
p.__this__().Nombre = "Geralt";
p.__this__().Apellidos = “de Rivia";
//¡Añade código solo a p! (lo siento, el cast es necesario)
p.__this__().ToString = (Func<string>)(() =>
p.__this__().Nombre + " " + p.__this__().Apellidos);
// ¡Imprime “Geralt de Rivia”!
Console.WriteLine(p.__this__().ToString());
Los ExpandoObjects asociados nos permiten
¡añadir cualquier cosa a cualquier objeto!
❖ Los lenguajes dinámicos son FLEXIBLES
❖ Habitualmente permiten añadir atributos o
métodos a cualquier clase u objeto durante la
ejecución
❖ Los objetos pueden tener miembros propios
(¡libertad de la tiranía de las clases!)
❖ El precio a pagar es rendimiento…y una
posibilidad más elevada de errores en ejecución 
❖ C# no puede hacer esto….pero…¿puede imitarlo?
(<Intro CSI>Yeeeah!!</Intro CSI>)
static class ObjectExtensions {
… (código de __this__)
public static dynamic __class__(this object obj) {
return miembrosDinamicos.GetOrCreateValue(
obj.GetType());
}
}
¿quieres miembros de clase en lugar de
los de instancia? Sin problema
La ConditionalWeakTable se asegura que el
recolector de basura elimina los miembros dinámicos
cuando sus instancias asociadas dejan de usarse
//Atributo dinámico de clase
Persona p2 = new Persona();
p2.__class__().Raza = "Humano";
//Imprime “Humano” (¡Raza es de clase!)
Console.WriteLine(p.__class__().Raza);
La semántica de
una clase se emula
de esta forma
Añadir propiedades dinámicas a las clases que se comporten
como propiedades de instancia es un poco más complejo,
pero se puede hacer con una aproximación similar
❖ Recuerda que es una IMITACIÓN
❖ Pero se puede usar para cosas “poderosas”
❖ Emular clases y objetos mutables
❖ Añadir dinámicamente código leído de
textos a cualquier objeto (¿recuerdas
CodeDOM del último tema de TPP?)
❖ Borrar o modificar miembros añadidos
dinámicamente a voluntad
❖ No permite manipular miembros declarados
en el código fuente
❖ ¡Pero es un juguete potente! ☺
❖ ¡Que puedes extender con tus ideas!
❖ Solo hay que hacer using ObjectExtensions;
en tus programas y ¡a divertirse! :D
❖ ¡Será aún más divertido cuando C#
implemente “extension everything”!
¡CUALQUIER objeto tendrá ahora el método __this__()!
Las llamadas a __this__ deberían, por ejemplo, comprobar el
ExpandoObject asociado a obj.GetType y crear clones de las
propiedades nuevas añadidas a la clase antes de devolver su
ExpandoObject asociado
<-
© José Manuel Redondo López
El incidente Roslyn
Pero ¡no podemos usar las variables declaradas directamente
en el programa dentro de los strings con código!
Se puede ejecutar código arbitrario siempre que incluyas las
referencias a las librerías adecuadas e imports
Este ejemplo ejecuta consultas Linq sobre variables declaras
pasando referencias, imports y globals a nuestra función Eval!
❖ Roslyn es un Compiler as a Service
❖ Permite crear analizadores de código
que suministren errores y warnings
dentro de Visual Studio
❖ Y construir extensiones de Visual
Studio que procesen código fuente…
❖ …o construir otras herramientas que
entiendan o ejecuten código fuente
❖ Y por supuesto…¡evaluar código bajo
demanda contenido en strings!
… (continúa de la viñeta anterior)
var result = await CSharpScript.EvaluateAsync (codigo,
opciones, globals: globals);
//Devuelve el resultado una vez compilado y ejecutado codigo
return result;
}
catch (CompilationErrorException e) {
return string.Join(Environment.NewLine, e.Diagnostics);
}
}
…
//Podemos hacer eval como en Python!
Console.WriteLine(Eval("2+2").Result); // Devuelve 4
var sampleNums = new int [] {1, 2, 3, 4, 5, 6};
//ERROR: sampleNums está fuera del ámbito del string
//Si, incluso si la declaramos antes
Console.WriteLine(Eval("sampleNums.Where(n=>n<3)")
.Result);
Con este método podemos evaluar fácilmente código C# que
esté en strings (leído de teclado, fichero, internet…)
var globals = new Globals { nos = sampleNums };
var references = new string[] {"System.Linq"};
IEnumerable r = (IEnumerable) Eval(“nos.Where(n=>n<3)",
globals: globals,
references: references,
imports: "System.Linq").Result;
foreach (var elem in r)
Console.WriteLine(elem);
}
El parámetro globals resuelve esto ¡permitiendo añadir
elementos declarados en el código para que podamos usarlos!
Hecho esto, podernos evaluar código desde un string
usando estas opciones y la variable “globals” pasada
async Task<object> Eval(string codigo, object globals, string[]
refs, string imports) {
try {
ScriptOptions opciones = null;
if (refs != null)
opciones = ScriptOptions.Default.WithReferences(refs);
if (imports != null)
if (opciones == null)
opciones = ScriptOptions.Default.WithImports(imports);
else opciones = opciones.WithImports(imports);
…
Lo primero que necesitamos es una función que maneje las
referencias a ensamblados y los imports del código a ejecutar
❖ Para usar Roslyn se necesitan
descargar los paquetes
Microsoft.CodeAnalysis de NuGet
❖ ¿Quieres saber más? ¡Mira esto!
❖ https://joshvarty.wordpress.com/learn-
roslyn-now/
❖ https://github.com/dotnet/roslyn/wiki/Sampl
es-and-Walkthroughs
❖ https://github.com/dotnet/roslyn/wiki/Scrip
ting-API-Samples#addref
La evaluación de Roslyn es asíncrona (¡podemos hacer
otras cosas mientras termina de ejecutarse el código!)
El código evaluado será más lento que el compilado…¡pero
nos da mucha más flexibilidad!
<-
© José Manuel Redondo López
Jugando con la voz
Solo necesitas añadir la referencia System.Speech a tu
proyecto
Al crear una instancia de SpeechRecognizer de ejecuta la
funcionalidad integrada de reconocimiento de voz de tu SO
Puedes suministrarle una gramática de comandos o valores
al reconocedor
De esta forma, ¡es muy fácil reconocer lo que se dice!
Combinándolo con los comandos, ¡se pueden crear
aplicaciones dirigidas por voz muy fácilmente!
❖ Convertir texto a voz y viceversa es
parte de las funcionalidades del
Framework WPF
❖ Este framework es parte del .Net
Framework, y se usa para construir
GUIs
❖ Se puede consultar un tutorial
completo de sus funcionalidades en:
http://www.wpf-tutorial.com/about-
wpf/what-is-wpf/
var pb = new PromptBuilder();
pb.AppendText(“Oh Dios, ");
var estilo = new PromptStyle();
estilo.Volume = PromptVolume.Soft; // Habla bajo
estilo.Rate = PromptRate.Slow; // Habla lento
pb.StartStyle(estilo); //Aplica estilo de habla
pb.AppendText(“Soy un Elcor ");
pb.EndStyle();
pb.AppendText(“Hoy es: ");
pb.AppendTextWithHint(DateTime.Now.
ToShortDateString(), SayAs.Date); // Pronuncia como fecha
narrador.Speak(pb);
…
var reconocedor = new SpeechRecognizer();
Reconocer lo que dices y convertirlo a texto es también muy
sencillo con WPF
var gBuilder = new GrammarBuilder();
var comandos = new Choices(“walk", “draw");
gBuilder.Append(comandos);
var valores = new Choices();
valores.Add(“up", “down“, “left”, “right”);
valores.Add(“sword", “potion", “knife");
gBuilder.Append(valores);
reconocedor.LoadGrammar(new Grammar (gBuilder));
reconocedor.SetInputToDefaultAudioDevice();
//Event fired when speech is recognized
void speechRecognizer_SpeechRecognized(object
sender, SpeechRecognizedEventArgs e){
… (resto del código del evento)
var comando = e.Result.Words[0].Text.ToLower();
var valor = e.Result.Words[1].Text.ToLower();
switch(comando) {
case “walk":
… (haz algo con el valor (“up”, “down”,…) )
case “draw":
… (haz algo con el valor (“sword”, …) )
}
Luego se lanza el reconocimiento de voz con
speechRecognizer.RecognizeAsync(RecognizeMode.Multiple) y
se para con speechRecognizer.RecognizeAsyncStop()
Puedes crear efectos
interesantes, como
formas especiales de
decir ciertos tipos de
datos
using System;
using System.Speech.Synthesis;
var narrador = new SpeechSynthesizer();
narrador.Speak(“Oh Dios, ¡estoy hablando!");
¡Convertir texto a voz es muy simple!
De nuevo es necesario añadir la referencia
System.Speech al proyecto
Si tenemos un GUI, es muy fácil lanzar un evento
cuando se reconoce voz
<-
© José Manuel Redondo López
Referencias
Num Referencia
[1]
J. M. Redondo, «Admin-zines: Understand Infrastructure Administration concepts the easy way,» 8 2019. [En línea]. Available:
https://www.researchgate.net/publication/335023411_Admin-zines_Understand_Infrastructure_Administration_concepts_the_easy_way.
[2]
J. M. Redondo, «New Features of C Sharp 5 and 6,» 1 2019. [En línea]. Available:
https://www.researchgate.net/publication/330223681_New_Features_of_C_Sharp_5_and_6.
[3] J. M. Redondo, «New Features of C Sharp 7,» 1 2019. [En línea]. Available: https://www.researchgate.net/publication/330358763_New_Features_of_C_Sharp_7.
[4]
J. M. Redondo, «New Features of C Sharp 8 and beyond,» 1 2019. [En línea]. Available:
https://www.researchgate.net/publication/330514620_New_Features_of_C_Sharp_8_and_beyond.
[5] F. Ortin, J. M. Redondo y J. Quiroga, «Design and evaluation of an alternative programming paradigms course,» Telematics and Informatics, vol. 34, nº 6, pp. 813-823, 2017.
[6]
F. Ortin, M. García and J. M. Q. J. Redondo, "Combining static and dynamic typing to achieve multiple dispatch," Information–An International Interdisciplinary Journal, vol.
16, no. 12, pp. 8731-8750, 2013.
[7]
J. M. Redondo, «C#aracterísticas Fantásticas y Dónde Encontrarlas: La Pythonización del C#,» 2 2020. [En línea]. Available:
https://www.researchgate.net/publication/338584100_Caracteristicas_Fantasticas_y_Donde_Encontrarlas_La_Pythonizacion_del_C
© José Manuel Redondo López
Version 1.3, 3 November 2008
Copyright © 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
0. PREAMBLE
The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to
assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially.
Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible
for modifications made by others.
This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It
complements the GNU General Public License, which is a copyleft license designed for free software.
We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free
program should come with manuals providing the same freedoms that the software does. But this License is not limited to software
manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend
this License principally for works whose purpose is instruction or reference.
1. APPLICABILITY AND DEFINITIONS
This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be
distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work
under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee,
and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright
law.
A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with
modifications and/or translated into another language.
A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the
publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall
directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any
mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal,
commercial, philosophical, ethical or political position regarding them.
The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that
says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to
be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections
then there are none.
The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that
the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.
A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the
general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels)
generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for
automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format
whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent.
An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque".
Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML
or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification.
Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and
edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the
machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only.
The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this
License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text
near the most prominent appearance of the work's title, preceding the beginning of the body of the text.
The "publisher" means any person or entity that distributes copies of the Document to the public.
A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses
following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as
"Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the
Document means that it remains a section "Entitled XYZ" according to this definition.
The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These
Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other
implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.
2. VERBATIM COPYING
You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the
copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no
other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further
copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large
enough number of copies you must also follow the conditions in section 3.
You may also lend copies, under the same conditions stated above, and you may publicly display copies.
3. COPYING IN QUANTITY
If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and
the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover
Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you
as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You
may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the
Document and satisfy these conditions, can be treated as verbatim copying in other respects.
GNU Free Documentation License (1/3)
© José Manuel Redondo López
If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the
actual cover, and continue the rest onto adjacent pages.
If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable
Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the
general network-using public has access to download using public-standard network protocols a complete Transparent copy of the
Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of
Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year
after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public.
It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to
give them a chance to provide you with an updated version of the Document.
4. MODIFICATIONS
You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you
release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing
distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the
Modified Version:
A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which
should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original
publisher of that version gives permission.
B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified
Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless
they release you from this requirement.
C. State on the Title page the name of the publisher of the Modified Version, as the publisher.
D. Preserve all the copyright notices of the Document.
E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices.
F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the
terms of this License, in the form shown in the Addendum below.
G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice.
H. Include an unaltered copy of this License.
I. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and
publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating
the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as
stated in the previous sentence.
J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise
the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may
omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the
version it refers to gives permission.
K. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the
substance and tone of each of the contributor acknowledgements and/or dedications given therein.
L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are
not considered part of the section titles
M. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version.
N. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section.
O. Preserve any Warranty Disclaimers.
If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material
copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the
list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles.
You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various
parties—for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a
standard.
You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the
list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through
arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by
arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit
permission from the previous publisher that added the old one.
The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or
imply endorsement of any Modified Version.
5. COMBINING DOCUMENTS
You may combine the Document with other documents released under this License, under the terms defined in section 4 above for
modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified,
and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.
The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single
copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by
adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number.
Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.
In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled
"History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all
sections Entitled "Endorsements".
6. COLLECTIONS OF DOCUMENTS
You may make a collection consisting of the Document and other documents released under this License, and replace the individual
copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of
this License for verbatim copying of each of the documents in all other respects.
You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of
this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.
GNU Free Documentation License (2/3)
© José Manuel Redondo López
7. AGGREGATION WITH INDEPENDENT WORKS
A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a
storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights
of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does
not apply to the other works in the aggregate which are not themselves derivative works of the Document.
If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the
entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic
equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole
aggregate.
8. TRANSLATION
Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4.
Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations
of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this
License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English
version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and
the original version of this License or a notice or disclaimer, the original version will prevail.
If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title
(section 1) will typically require changing the actual title.
9. TERMINATION
You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise
to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License.
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally,
unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to
notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by
some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright
holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material
does not give you any rights to use it.
GNU Free Documentation License (3/3)
10. FUTURE REVISIONS OF THIS LICENSE
The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new
versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See
https://www.gnu.org/licenses/.
Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this
License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of
any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version
number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document
specifies that a proxy can decide which future versions of this License can be used, that proxy's public statement of acceptance of a
version permanently authorizes you to choose that version for the Document.
11. RELICENSING
"Massive Multiauthor Collaboration Site" (or "MMC Site") means any World Wide Web server that publishes copyrightable works and also
provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A
"Massive Multiauthor Collaboration" (or "MMC") contained in the site means any set of copyrightable works thus published on the MMC
site.
"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit
corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by
that same organization.
"Incorporate" means to publish or republish a Document, in whole or in part, as part of another Document.
An MMC is "eligible for relicensing" if it is licensed under this License, and if all works that were first published under this License
somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant
sections, and (2) were thus incorporated prior to November 1, 2008.
The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1,
2009, provided the MMC is eligible for relicensing.
Diseño del avatar: Inmaculada Martínez Lobo (@inmmastar)
Diseño el buho: Vanessa Redondo López (@creative_vanesa)
¡Y eso es todo!
¡Espero que os
hayan sido útiles!
¡Nos vemos en la
siguiente serie acerca de
seguridad!

Más contenido relacionado

Similar a zines.esp.v12.pdf

Programacion Orientada a Objetos en php
Programacion Orientada a Objetos en phpProgramacion Orientada a Objetos en php
Programacion Orientada a Objetos en php
Samuel Piñon Garcia
 

Similar a zines.esp.v12.pdf (20)

Guia poo php
Guia poo phpGuia poo php
Guia poo php
 
Programacion Orientada a Objetos en php
Programacion Orientada a Objetos en phpProgramacion Orientada a Objetos en php
Programacion Orientada a Objetos en php
 
PHP mode on
PHP mode onPHP mode on
PHP mode on
 
Zen Scaffolding - Programador PHP
Zen Scaffolding - Programador PHPZen Scaffolding - Programador PHP
Zen Scaffolding - Programador PHP
 
Clean code 10-11
Clean code 10-11Clean code 10-11
Clean code 10-11
 
Javascript OOP
Javascript OOPJavascript OOP
Javascript OOP
 
Taller completo
Taller completoTaller completo
Taller completo
 
56874982 curso-de-delphi-7
56874982 curso-de-delphi-756874982 curso-de-delphi-7
56874982 curso-de-delphi-7
 
Developing for Android (The movie)
Developing for Android (The movie)Developing for Android (The movie)
Developing for Android (The movie)
 
Guía PHP Orientado a Objeto con MVC
Guía PHP Orientado a Objeto con MVC Guía PHP Orientado a Objeto con MVC
Guía PHP Orientado a Objeto con MVC
 
Manual de php basico
Manual de php basicoManual de php basico
Manual de php basico
 
Presentacion de clases en c#
Presentacion de clases en c#Presentacion de clases en c#
Presentacion de clases en c#
 
♬♪♬..I'm too sexy... ♫♪ catwalk... Como modelar el dominio efectivamente
♬♪♬..I'm too sexy... ♫♪ catwalk... Como modelar el dominio efectivamente ♬♪♬..I'm too sexy... ♫♪ catwalk... Como modelar el dominio efectivamente
♬♪♬..I'm too sexy... ♫♪ catwalk... Como modelar el dominio efectivamente
 
Php
PhpPhp
Php
 
Android Superstar - Buenas Prácticas
Android Superstar - Buenas PrácticasAndroid Superstar - Buenas Prácticas
Android Superstar - Buenas Prácticas
 
Trabajo practico de lenguaje java.Conceptos. Estructuras. Programacion.
Trabajo practico de lenguaje java.Conceptos. Estructuras. Programacion.Trabajo practico de lenguaje java.Conceptos. Estructuras. Programacion.
Trabajo practico de lenguaje java.Conceptos. Estructuras. Programacion.
 
modularidad de programación 2da parte (3) (1).pptx
modularidad de programación 2da parte (3) (1).pptxmodularidad de programación 2da parte (3) (1).pptx
modularidad de programación 2da parte (3) (1).pptx
 
Manual basico de PHP
Manual basico de PHPManual basico de PHP
Manual basico de PHP
 
JAVA ORIENTADO A OBJETOS - INTERFACES
JAVA ORIENTADO A OBJETOS - INTERFACESJAVA ORIENTADO A OBJETOS - INTERFACES
JAVA ORIENTADO A OBJETOS - INTERFACES
 
Ensayo php
Ensayo phpEnsayo php
Ensayo php
 

zines.esp.v12.pdf

  • 1. Diseño del avatar: Inmaculada Martínez Lobo (@inmmastar) Diseño el buho: Vanessa Redondo López (@creative_vanesa) Fan-C#ines Entendiendo conceptos de C# de forma fácil ☺
  • 2. © José Manuel Redondo López LICENCIA Copyright (C) 2020 José Manuel Redondo López. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License"
  • 3. © José Manuel Redondo López V1.2 (29 zines) Historial de cambios V1.0 (22/05/2019): * Recopilación de todos los zines originales en un PDF de alta calidad * Estilo visual completamente renovado siguiendo el feedback de los usuarios * Cinco zines adicionales: 1) Polimorfismo y enlace dinámico 2) Master-Worker 3) Producer-Consumer 4) Evaluación dinámica de código con Roslyn CaaS 5) Conversión de texto a voz y de voz a texto V1.1 (23/05/2019): * Publicación en abierto * Nuevo zine: “ejecución” de expresiones en lambda cálculo V1.2 (03/02/2020): * Añadido un nuevo índice por temática navegable * 3 nuevos zines: Equals, Downcasting y Genericidad en C# vs Java * Licencia de la FSF incluida explícitamente
  • 4. © José Manuel Redondo López Índice de Zines PROGRAMACIÓN ORIENTADA A OBJETOS Polimorfismo y enlace dinámico Ver Equals Ver Downcast y Upcast Ver Paso de parámetros, ref y out Ver Excepciones Ver Asertos Ver Genericidad Ver Genericidad en C# vs Java Ver IEnumerable Ver PROGRAMACIÓN FUNCIONAL Introducción a la programación funcional Ver Usando funciones Ver Funciones lambda Ver Linq Ver Generadores Ver Currificación Ver Operaciones lazy Ver "Ejecución" de cálculo lambda Ver Join (Linq) Ver GroupBy (Linq) Ver PROGRAMACIÓN CONCURRENTE Uso de lock Ver Contextos en los que usar lock Ver Convirtiendo el código a paralelo (TPL/PLinq) Ver Claúsulas e hilos Ver ReaderWriterLockSlim Ver Master-Worker Ver Consumer-Producer Ver METAPROGRAMACIÓN & OTROS TEMAS Imitando a los lenguajes dinámicos Ver Roslyn Compiler-As-A-Service Ver API de Voz Ver
  • 5. C# © José Manuel Redondo López
  • 6. © José Manuel Redondo López Yo os declaro virtual y override Viniendo de Java, probablemente contestarás que al de Manager Esto implica que referencias a Empleado pueden contener instancias de Manager Y Manager puede especializar (crear nuevo comportamiento) los métodos de Empleado Por tanto, esto es posible De hecho usa la política contraria: enlace dinámico solo si lo autorizas expresamente Primero, en la clase padre hay que marcar los métodos como redefinibles con la palabra reservada virtual Esto abre la posibilidad de redefinir estos métodos en las clases hijas (¡si se quiere hacer!) Y así se obtiene el mismo comportamiento que Java ¡Mira bien los métodos virtual del API de C# en caso de que necesites crear tu propia versión de alguno en tus clases hijas! ❖ El polimorfismo es un mecanismo de generalización: una abstracción general puede representar otras más específicas ❖ Las referencias de clases derivadas (especificas) promocionan (se convierten implícitamente) a referencias de clases base (generales) ❖ El enlace dinámico es un mecanismo de especialización: la implementación de un método puede especializarse en clases derivadas class Empleado { double getSalario() { return 1000.0; } } class Manager : Empleado { double getSalario() { return 2000.0; } } Empleado empleado = new Manager(); Console.WriteLine(empleado.getSalario()); Empleado -Nombre + GetSalario () … -Apellido … Manager -… + GetSalario () … class Empleado { double getSalario() { return 1000.0; } } class Manager : Empleado { double getSalario() { return 2000.0; } } ¡Pero no! C#, a diferencia de Java, ¡no tiene enlace dinámico por defecto! class Empleado { virtual double getSalario() { return 1000.0; } } class Manager : Empleado { double getSalario() { return 2000.0; } } class Empleado { virtual double getSalario() { return 1000.0; } } class Manager : Empleado { override double getSalario() { return 2000.0; } } ¿Y cómo se autoriza? Segundo, se marca el método que redefine al del padre con override Pero ¿a qué método getSalario() se llama? <-
  • 7. © José Manuel Redondo López Toss a call to your Equals ❖ Ahora podemos controlar que hacen Equals y el operador == ❖ Implementar == requiere implementar también != ❖ Redefinir Equals normalmente también requiere redefinir GetHashCode ❖ Por qué? Para permitir a los objectos trabajar normalmente en una Hashtable o similar ❖ https://docs.microsoft.com/es- es/visualstudio/ide/reference/generate-equals- gethashcode-methods?view=vs-2019 p1 y p2 son objetos diferentes porque se han creado con dos new diferentes, aunque sus contenidos sean los mismos Pero ¿qué pasa si necesito comparar por referencia y he modificado tanto el == como Equals? C# ha pensado en ello; ¡puedes usar el método estático Object.ReferenceEquals! ❖ Todo objeto puede comprobar si es igual a otro ❖ Pero si no implementa un Equals, se usa Object.Equals ❖ Esto se llamada comparación de igualdad por referencia ❖ Y solo es true cuando los dos operandos se refieren AL MISMO objeto var p1 = new Persona { Nombre = "Jhon"}; var p2 = new Persona { Nombre = "Jhon" }; Console.WriteLine(p1.Equals(p2)); // False Console.WriteLine(p1 == p2); // False p2 = p1; Console.WriteLine(p1.Equals(p2)); // True Console.WriteLine(p1 == p2); // True class Persona { public string Nombre { get; set; } public override bool Equals(object obj) { return this.Nombre.Equals(((Person) obj). Nombre); } … p1 = new Persona { Name = "Jhon" }; p2 = new Persona { Name = "Jhon" }; Console.WriteLine(p1.Equals(p2)); // True Console.WriteLine(p1 == p2); // False class Persona { … public static bool operator==(Person p1, Person p2) { return p1.Nombre.Equals(p2.Nombre); } public static bool operator!=(Person p1, Person p2) { return !p1.Nombre.Equals(p2.Nombre); } … p1 = new Persona { Name = "Jhon" }; p2 = new Persona { Name = "Jhon" }; Console.WriteLine(p1.Equals(p2)); // True Console.WriteLine(p1 == p2); // True p1 = new Persona { Nombre = "Jhon" }; p2 = new Persona { Nombre = "Jhon" }; Console.WriteLine(p1.Equals(p2)); // True Console.WriteLine(Object.ReferenceEquals(p1, p2)); // False Solo cuando se refieren al mismo objeto, Object.Equals devuelve true! Pero ¿qué pasa si implementamos nuestro propio Equals (por nombre)? ¡El operador == no funciona! Porque nuestro Equals no se usa, aún usa las referencias ¿Qué puedo hacer para cambiar el comportamiento del operador ==?... ¡Sobrecargarlo! <-
  • 8. © José Manuel Redondo López AasC/DisC ❖ Cada método puede combinarse y/o usarse para tratar varios tipos de objetos de forma diferente cuando se haga downcasting! ❖ Cada uno tiene sus particularidades ❖ El de la excepción es lento, propenso a fallos (se te olvida el try-catch) y no recomendable ❖ El de la reflexión es lento y te obliga a programar un if nuevo para cada tipo (no es mantenible) ❖ is te obliga a comprobar antes ❖ as hace el cast si puede, pero te obliga a comprobar después try { r = (Rectangulo) f; // Error! //Rectangulo r2 = (Rectangulo) c; // No compila :) } catch (InvalidCastException ex) { c = (Circulo) f; // La otra opción … // Más tipos } if (f is Rectangulo) { r = (Rectangulo)f; } else { if (f is Circulo) { c = (Circulo) f; … // Más tipos } } r = f as Rectangulo; if (r == null) { c = f as Circulo; … // Más tipos } if (f.GetType().Name.Equals("Rectangulo")) r = f as Rectangulo; // O un simple cast if (f.GetType().Name.Equals("Circulo")) c = f as Circulo; // O un simple cast … // Más tipos ¡Hacer el cast y rezar! :) Si es el esperado, ¡se puede hacer el cast sin problemas! Usar el operador safe-cast (as) Hace el cast si puede...y, si no, devuelve null. ¡Puedes comprobar si vale null para evitar errores de ejecución! class Figura {…} class Circulo : Figura {…} class Rectangulo : Figura {…} … Figura f = new Circulo(); Circulo c = (Circulo) f; Rectangulo r; En una jerarquía de clases, tanto el upcasting (de hijo a padre) como el downcasting (de padre hijo) están permitidos Upcasting es seguro, pero el downcasting ¡puede fallar! ¿Por qué?, porque aunque una referencia de un tipo padre pueda contener objetos de cualquiera de sus hijos, ¡se puede hacer cast al hijo incorrecto! ¿Qué hacemos? Rezar no es muy “ingenieril", por lo que deberías capturar la excepción adecuada Preguntar a la referencia por su tipo actual de lo que guarda (operador is) Puedes preguntar al objeto por el nombre de su clase, y usarlo para decidir si hacer o no el cast ¡Es la opción más lenta! Usar reflexión (tema avanzado) <-
  • 9. © José Manuel Redondo López Ref-rescate Mira el ejemplo anterior: en la llamada a MiMetodo, n es una variable nueva y distinta en memoria que recibe una copia del valor pasado void MiMetodo(int n) { n = 3 } El paso de parámetros de C# o Java no es complejo: la política estándar es COPIAR lo que se le pasa a un método Pasar una referencia ¡implica hacerle una copia también! out funciona como ref, pero no obliga a inicializar la variable pasada (¡ref sí!) Persona p = new Persona(); Salvo que uses ref Las referencias ocupan un espacio en memoria, diferente del que ocupa el objeto al que apuntan Y así, tenemos dos referencias ¡apuntando al mismo objeto! El método trabaja con la variable local n, que se destruye cuando el método acaba Así a no se modifica, porque hemos modificado una copia independiente Pero la gente habitualmente se confunde con este mecanismo  int a = 5; MiMetodo(a); Console.Write(a); //Imprime 5 Las referencias son variables que me permiten acceder (usando .) al sitio donde verdaderamente está guardado un objeto Ambas pueden usarse para modificar los contenidos de un objeto. Cambiar el valor de una solo rompe el enlace con el objeto Usar ref implica que no se hacen copias: el parámetro y la variable pasada son como ¡dos nombres asociados a la misma localización de memoria! Y, por tanto, los cambios hechos en los métodos ¡afectan al llamador! Se usaba para emular el retorno de varios valores desde un método (hasta que llegaron las tuplas…☺) 5 5 3 a n RAM RAM p (Person instance) . void MiMetodo(Persona n) { n.Nombre = “Paco”; //Solo rompe el enlace n = null; } Persona p = new Persona(); MiMetodo(p); // Imprime “Paco” Console.Write(p.Nombre); RAM p (objeto Persona) . . n void MiMetodo(ref Persona n) { n.Nombre = “Paco”; //¡Ahora p es también null! :O n = null; } Persona p = new Persona(); MiMetodo(ref p); //¡NullReferenceException! Console.Write(p.Nombre); RAM (objeto Persona) . p, n void MiMetodo(out Persona n) { n = new Persona(); n.Nombre = “Paco”; } Persona p; MiMetodo(out p); // Imprime “Paco” Console.Write(p.Nombre); RAM p, n (objeto Persona) . ¡El compilador te obliga a darle un valor a todas las variables out antes de que el método termine! <-
  • 10. © José Manuel Redondo López Errores fantásticos y cómo capturarlos void CambiarClave(string usuario, string hashClave) { ConectarSGBD(); CambiarClave(usuario, hashClave); } En un mundo ideal, el código solo debería preocuparse de implementar una funcionalidad void CambiarClave(…) Pero, por desgracia, ¡¡hay un montón de problemas que pueden romper nuestro código!! ❖ Puede ejecutarse con parámetros erróneos o en momentos inadecuados por error…o ¡a propósito! ❖ ¡Debemos proteger nuestro código contra el mal! (o la pereza, la ignorancia, las ganas de fastidiar…) ❖ ¿Y eso como lo hago? ¡CON EXCEPCIONES! void CambiarClave(string usuario, string hashClave) { if (usuario == null) throw new ArgumentException(“El nombre de usuario no puede ser null”); … (Resto del código del método) } … try{ CambiarClave(null, null); catch(ArgumentException e) { /*Haz algo para tratar el problema: - Corregir los argumentos - Lanzar otra excepción - … (otra solución) */ } Se informa al llamador de que ha metido la pata, y se le OBLIGA a tratar su error en un bloque try-catch adecuado Si no, ¡EL PROGRAMA FINALIZA! void CambiarClave(string usuario, string hashClave) { if (usuario == null) throw new ArgumentException(“El nombre de usuario no puede ser null”); if (hashClave == null) throw new ArgumentException(“La nueva clave no puede ser null”); if (demasiadosIntentosErroneos()) throw new InvalidOperationException(“La cuenta está bloqueada, inténtelo más tarde”); if (!existeUsuario(usuario)) throw new ArgumentException(“El nombre de usuario debe existir”); … (más precondiciones) ConectarSGBD(); CambiarClave(usuario, hashClave); Se pueden comprobar varias cosas antes de ejecutar un código. Estas comprobaciones se llaman PRECONDICIONES ❖ Una vez lanzada una excepción X, la ejecución de un método se interrumpe ❖ Solo se reanuda si hay un bloque try- catch rodeando la línea que la lanza, y su tipo de excepción es compatible (por polimorfismo) con X ❖ Si no se encuentra algo así en toda la pila de llamadas, el SO maneja las excepciones no tratadas ❖ Y entonces….SAYONARA, BABY ❖ Son ROBUSTAS: Los llamadores deben tratarlas y programar acciones correctivas para los errores detectados ❖ ¡Los errores no se silencian! Claves nulas Claves inadecuadas Cambios de clave maliciosos Nombres de usuario nulos ¡¡La ejecución “salta” aquí!! Las excepciones se lanzan cuando TÚ detectas un problema ANTES de que el código del método se ejecute Se detectan argumentos erróneos (ArgumentException) o llamadas cuando la clase está en un estado incorrecto (InvalidOperation Exception) Nombres de usuario inexistentes <-
  • 11. © José Manuel Redondo López Orgullo y asertos ☺ void AñadirUsuario(Usuario usr) { //¿Funciona el programa correctamente? //... (todas las precondiciones) _AñadirUsuario(usr); //¿Se añadió el usuario realmente? //¿Sigue funcionando bien el programa? } A veces, cuando programamos nos preguntamos si nuestro programa funciona bien y las consecuencias de lo que hacemos void AñadirUsuario(Usuario usr) { //¿Funciona el programa correctamente? //... (todas las precondiciones) _ AñadirUsuario(usr); //¿Se añadió el usuario realmente? //¿Sigue funcionando bien el programa? } Estas preguntas tienen un nombre en el mundo de la programación void AñadirUsuario(Usuario usr) { // Funciona el programa correctamente? (invariante) Debug.Assert(ExisteTablaUsuarios(), “¡La tabla de usuarios se ha borrado o está corrupta!. Finalizando…”); //... (todas las precondiciones) _AñadirUsuario(usr); //¿Se añadió el usuario reamente? (postcondición) Debug.Assert(!ExistUser(user), “¡El usuario no se ha añadido a la base de datos!. Finalizando…”); //¿Sigue funcionando bien el programa? (invariante) Debug.Assert(ExisteTablaUsuarios(), “¡La tabla de usuarios se ha borrado o está corrupta!. Finalizando…”); } Los invariantes habitualmente se comprueban al principio (salvo en constructores) y al final de CADA método Las postcondiciones al final de su método correspondiente ❖ ¿Qué hago si detecto que mi código está mal y el programa no funciona como debe? ❖ El “código de orgullo del ingeniero” te obliga a matar tu programa, sacrificándolo al Dios del null ☺ ❖ Un programa con ese tipo de error no puede permitirse que siga en ejecución ❖ ¡Los errores pueden empeorar con el tiempo! ❖ Este es el motivo por el que se usan asertos en estas situaciones ❖ Un aserto no negocia, no razona, no siente pena ni remordimientos…solo mata tu programa ☺ Invariante Invariante Postcondicion ❖ Precondiciones, postcondiciones e invariantes son la base de la Programación por Contratos ❖ Dependen del propósito del programa y sus métodos, así que DEBEMOS PENSARLOS CON CUIDADO ❖ TÚ eres el responsable de detectar los errores en tú propio código ❖ Así tus programas serán a prueba de balas (tanto como sea posible) ❖ Los asertos deben ser eliminados en modo Release (se quitan solos si se compila en este modo en Visual Studio) ❖ Los programas en producción no deberían tener errores así ;) ❖ Las postcondiciones son cosas que DEBEN ocurrir tras la ejecución de UN MÉTODO CONCRETO ❖ Los invariantes son cosas que DEBEN ocurrir durante toda la ejecución de una instancia (no importa el método) ❖ Si alguna falla, es un ERROR DE PROGRAMACIÖN ¡¡¡ES CULPA TUYA!!! <-
  • 12. © José Manuel Redondo López ¿Por qué eres tan genérico? void Imprime(bool [] a) { foreach (bool n in a) Console.Write(n + “, “); } void Imprime(int [] a) { foreach (int n in a) Console.Write(n + “, “); } void Imprime(string [] a) { foreach (string n in a) Console.Write(n + “, “); } ¿No te parece que a veces repites el mismo código una y otra vez, cambiando solo pequeñas cosas? void Imprime(bool [] a) {…} void Imprime(int [] a) {…} void Imprime(string [] a) {…} void Imprime <T>(T [] a) { foreach (T n in a) Console.Write(n + “, “); } ¡Para evitar eso se creó la genericidad! ❖ Y ahora…¿qué? ❖ Cada vez que llamas a un método genérico, el compilador de C# crea una versión concreta, reemplazando T por el tipo que uses en la llamada ❖ Ejemplo: Imprime<string>(nombres) crea (¡automáticamente!) el ultimo método de la primera viñeta ❖ Y si ya se creo antes, lo reutiliza Imprime<bool>(arrayDeBool); Imprime<int>(numeros): Imprime<string>(nombres); … Imprime<int>(names); ¡NO COMPILA! class MiLista<T> { … //T puede usarse en todo el cuerpo de la clase public T Get(int position) {…} //Por favor, NO pongas <T> en cada método } Usar métodos genéricos es muy sencillo, pero ¡no puedes engañar al compilador! Imprime (arrayDeBool); Imprime (numeros): Imprime (nombres); Pero especificar los parámetros de tipos en llamadas a métodos genéricos ¡en la mayoría de casos no es necesario! C# puede calcular los parámetros de tipo automáticamente por ti De esta forma, llamar a un método, clase o interfaz genérico ¡no es distinto de llamar a uno que no sea genético! ❖ Si usas una variable de tipo T en el cuerpo de un método, ¡solo tendrás acceso a los métodos de Object! ❖ ¡Salvo que uses bounded generics! ❖ Nos permite establecer restricciones al tipo que reemplaza a T…¡sin perder el tipo que estamos pasando! Un método genérico pone parámetros de tipo (nombrados entre <>, como <T>) en aquellos lugares donde debería haber tipos concretos ¡Se crea así un “modelo” o “plantilla” de código! T First(T [] a, T o) where T: IComparable<T> { foreach (T n in a) if (n.CompareTo(a)==0) return n; return null; } Pasa a ser… Podemos establecer condiciones a los parámetros de tipo, y quien lo use debe cumplirlos. Esto se hace con where y es lo que se denomina bounded generics ¡Si el que llama no las cumple, el código no compila! Clases e Interfaces pueden ser también genéricos, siendo sus parámetros de tipo utilizables dentro de su cuerpo ({…}) ¡NO REDECLARES EL PARÁMETRO DE TIPO EN CADA MÉTODO! <-
  • 13. © José Manuel Redondo López Stranger <T>hings ❖ La máquina virtual de .Net se ha modificado para soportar genericidad de forma nativa ❖ Sin embargo, la máquina virtual de Java (JVM) no soporta genericidad ❖ El compilador de Java traduce los tipos genéricos a Object ❖ A nivel de la JVM se usa polimorfismo ❖ Eso hace que Java tenga limitaciones cuando trabaja con genericidad que C# no tiene List<int> lista = new List <int>(); Los tipos primitivos no pueden usarse en Java como tipos genéricos class CosaGenerica<T, T2> { private static T atributo; … } class CosaGenerica <T, T2> { … public void ProcesarElemento(Object param) { … if (typeof(T).IsInstanceOfType(param)) { T elementoAProcesar = (T) param; … class CosaGenerica <T, T2> { … public void Metodo1 (T param) {…} public void Metodo1 (T2 param) {…} } class CosaGenerica <T, T2> where T : new() { … public void ProcesarElemento(Object param) { T elemento = new T(); T[] arrayGenerico = new T[10]; … Java no puede hacer cast o usar instanceof con tipos genéricos Java no puede usar sobrecarga de métodos que solo usen tipos genéricos que tengan diferente instanciación C# List <int> lista = new ArrayList<int>(); class CosaGenerica <T, T2> { private static T atributo; … } Java no puede usar static con tipos genéricos class CosaGenerica <T, T2> { … public void ProcesarElemento(Object param) { T elemento = new T(); T[] arrayGenerico = new T[10]; … class CosaGenerica <T, T2> { … public void ProcesarElemento(Object param) { … if (param instanceof T) { T elementoAProcesar = (T) param; } … class CosaGenerica <T, T2> { … public void Metodo1 (T param) {…} public void Metodo1 (T2 param) {…} } C# C# C# C# Java no puede crear nuevas instancias de tipos genéricos y tampoco puede crear arrays de tipos genéricos C# necesita declarar el tipo genérico con un tipo especial de bounded generics para permitirlo, new() <-
  • 14. © José Manuel Redondo López Yo Enumerable using System.Linq; … miClase.Select(…) miClase.Where(…) miClase.Aggregate(…) miClase.OrderBy(…) miClase.GroupBy(…) miClase.Union(…) miClase.Zip(…) Ser enumerable significa que “contienes” elementos, y que se pueden obtener uno a uno desde el primero hasta el último Básicamente, te pasas a ti mismo al IEnumerator, y ¡dejas que haga el trabajo “sucio”! class EnumerableThing <T> : IEnumerable<T> { public T GetElement(int pos) { …} public int Length { get; private set; } //No necesitas nada más para ser IEnumerable! :O public IEnumerator<T> GetEnumerator() { for (int i = 0;i<this.Length;i++) yield return GetElement(i); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } A no ser que ya sepas Kung-Func ☺… La programación funcional hace esto mucho mucho más fácil!!! class YoTeEnumero<T> : IEnumerator<T> { public IEnumerateYou(CosaEnumerable<T> en) {…} } class CosaEnumerable<T> : IEnumerable<T> { public IEnumerator<T> GetEnumerator() { return new YoTeEnumero <T>(this); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } class YoTeEnumero <T> : IEnumerator<T> { private EnumerableThing<T> myEnum; private int currentPos = -1; public IEnumerateYou(EnumerableThings<T> en) { myEnum = en; } public void Dispose() { … //Cuando acaba el enumerable… } public bool MoveNext() { currentPos++; return currentPos < myEnum.Length; // ¿Más? } public void Reset() { //Siempre hay que ponerlo ANTES del primer elemento!! currentPos = -1; } public T Current { get { return myEnum.Get(currentPos); } } object IEnumerator.Current => Current; } Debes implementar los métodos del interface IEnumerator con tus métodos Simplemente devuelve una instancia de una clase que implementa IEnumerator<T> que creas, encargada de recorrer TUS ELEMENTOS Así simplemente, ¡expones un objeto que enumera tus propios elementos! Las hace compatibles con Linq Permite usarlas en un foreach Muchas colecciones de C# lo son, pero lo puedes hacer a tus propias clases! (Leer datos de ficheros, BBDD, la nube, conexiones de red…) MoveNext() :true MoveNext() :true MoveNext() :true MoveNext() :false Reset() Clase IEnumerable foreach (var elem in myClass) { … //Procesa cada elemento } Para implementarlo, lo primero es hacer que tu clase implemente IEnumerable<T> Clase IEnumerable Clase IEnumerator Users GetEnumerator Deja que yo te enumere! Implementar IEnumerable le da a tus clases superpoderes! <-
  • 15. C# © José Manuel Redondo López
  • 16. © José Manuel Redondo López Programación Funcional (en pocas palabras) ❖ Pero, ¿y si lo que yo quiero es escribir “trozos de código” en lugar de usar métodos que ya tenga? … ¡PUEDES! ❖ Las funciones lambda permiten insertar “trozos de código” donde quieras ❖ Son como métodos, pero más simples ❖ Por ejemplo, en vez de: double Mitad(double numero) { return numero / 2; } ❖ Escribes: numero => numero / 2 ❖ ¡Las funciones lambda pueden también tener varias líneas entre {} (si pasa eso, también deben usar return) y varios parámetros (entre ())! void Metodo() { int numero; numero = 4; Console.WriteLine(numero); int[] numeros = new int[10]; numeros[0] = numero; CalculaAlgo(numero); } void CalculaAlgo(int numero){<…>} Solemos hacer esta clase de cosas con tipos “normales” void Metodo() { {…} codigo; codigo = {<el código>}; Console.WriteLine(codigo); {…}[] montones_de_codigo = new {…}[10]; montones_de_codigo[0] = codigo; CalculaAlgo(codigo); } void CalculaAlgo({…} codigo){<…>} Pero, ¿qué pasaría si en lugar de un tipo usásemos “trozos de código”? ❖ Espera…¿Quieres decir que “trozos de código” (métodos) pueden funcionar como si fueran tipos? ¡SI! ❖ Y ¿como hago para escribir el tipo de una variable que guarda “código”? ¡Usando sus parámetros y tipo de retorno (su signatura) con el tipo Func! Func<(tipo de param. 1,…, tipo de param. N, tipo de retorno> void Metodo() { Func<double> codigo; codigo = getRandomNum; Console.WriteLine(codigo); Func<double>[] montones_de_codigo = new Func<double>[10]; montones_de_codigo[0] = codigo; CalculaAlgo(codigo); } void CalculaAlgo(Func<double> codigo){<…>} Por tanto, los tipos Func<..> permiten guardar cualquier método compatible…y hacer cosas divertidas ☺ getRandomNum (sin los (), ¡que no lo estamos llamando!) es un método sin parámetros que devuelve un double. ¡Podemos guardarlo a él (y a cualquiera como él) en una Func<double>! void Metodo() { Func<double, double> codigo; code = numero => numero / 2; Console.WriteLine(codigo); Func<double, double, double>[] mucho_codigo = new Func<double, double, double>[10]; mucho_codigo[0] = (x,y) => {var a = x * y; return a;}; CalculaAlgo(()=>Math.PI*2); } void CalculaAlgo(Func<double> code) {<…>} Por tanto, puedes usar y guardar funciones lambda en cualquier parte del código Y esto cambia la forma en la que programarás para siempre ☺ ¿Quieres saber más? ¡Matricúlate en TPP! :D <-
  • 17. © José Manuel Redondo López Func-iones & cosas chulas Func<type param 1, type param 2, ..., return type> f;: Puede guardar casi cualquier método Action<type param 1, type param 2, ..., type param N> a;: Solo guarda métodos que devuelven void Predicate<type param 1, type param 2, ..., type param N> p;: Solo guarda métodos que devuelven bool //Ejemplos… var result = f(…); a(…); if (p(…)) {} C# tiene el tipo Func y otros dos tipos similares (Action y Predicate, llamados delegados genéricos) para guardar código compatible //Guardar un método estático Func<double, double> mat1 = Math.Sin; //Crear código “in situ” con una función lambda Func<double, double> mat2 = x => Math.Sqrt(x); var listInt = new List<int>(); //Guardar un método de instancia Action<int> addL = listInt.Add; mat1 (100); //Llama a Math.Sin(100) //Añade 3 al objeto listInt addL(3); El código que se guarda en los delegados genéricos puede tener distintos orígenes. ¡Puedes incluso crearlo “in situ” con las funciones lambda! Func<double, double> MetodoEjemplo2(int numero) { return x => x * numero; } … //Devuelve el código creado por la función llamada Func<double, double> f1 = MetodoEjemplo2(4); Console.WriteLine(f1(3)); // Devuelve 12 (3 * 4) Por supuesto, puedes crear funciones que devuelvan código usando los parámetros que pases //Locura Func-ional ☺ //Recibe dos funciones y un double, devuelve un double Func<Func<double, double>, Func<double, double>, double, double> fLocura = (fA, fB, x) => fA(fB(x)); … //Devuelve 0,978 (Sin(Sqrt(PI)) Console.WriteLine(fLocura(Math.Sin, x => Math.Sqrt(x), Math.PI)); Una vez que domines esto, puedes hacer cualquier locura que se te ocurra: combinar llamadas a funciones, retornar funciones que llaman a otras funciones pasadas como parámetro, … ¡SIENTE EL PODER DE LA λ! void MetodoEjemplo(Func<double, double> f) {…} … //Paso de parametros MetodoEjemplo(Math.Sin); MetodoEjemplo(x => Math.Sqrt(x)); Lo mismo ocurre cuando pasas parámetros //Estructura de datos List<Func<double, double>> listaFunciones = new List<Func<double, double>>(); listaFunciones.Add(Math.Sin); listaFunciones.Add(x => Math.Sqrt(x)); … // Devuelve 1 (Sin(90º)) Console.WriteLine(listaFunciones[0](Math.PI / 2)); // Devuelve 2 (Sqrt(4)) Console.WriteLine(listaFunciones[1](4)); Guardar código en estructuras de datos también es posible. Así puedes acceder a bloques de código cuanto te hagan falta…¡o crear diccionarios de código! <-
  • 18. © José Manuel Redondo López λa λa λand class Funciones{ public static bool MayorEdad (Persona p) { return p.Edad >= 18; } … } Persona[] personas =Personas.CrearPersonasAleatorias(); Persona[] masDe18 = Array.FindAll(personas, Funciones.MayorEdad); Pasar código como parámetro es fácil cuando ya tienes los métodos a pasar… Pero normalmente quieres crear el código justo en el momento en que vas a usarlo Persona[] masDe18 = Array.FindAll(personas, class Funciones{ public static bool MayorEdad (Persona p) { return p.Edad >= 18; } } ); Vale, pero esto es muy bruto (Si pensaste en poner solo el método sin la clase, también es muy bruto) Ya que NO PUEDES hacer esto, las funciones lambda te cubren esta necesidad // Deja que el compilador infiera los tipos masDe18 = Array.FindAll(personas, (p) => { return p.Edad >= 18; }); Puedes evitar escribir el tipo de los parámetros y dejar a C# inferirlos “automágicamente” También se cambia la palabra “delegate” por => separando cabecera y cuerpo del método //Varias líneas y varios parámetros /* Escribe cada persona a un fichero usando su ToString por defecto, o una función personalizada que convierta la persona a un string si se proporciona */ EscribirALog(personas, (persona, toStrFunc) => { if (toStrFunc == null) return persona.ToString(); else return toStrFunc(persona); }); Por supuesto, las funciones lambda más complejas no tienen estos atajos…pero aún así son mucho más sencillas de escribir Y de esta forma puedes convertir CUALQUIER método a una función lambda, ¡pasando su código solo cuando lo necesites! masDe18 = Array.FindAll(personas, delegate (Persona p) { return p.Edad >= 18; } ); Las funciones lambda son como métodos “normales” pero con una sintaxis mucho más breve // Con solo un parámetro, los () son opcionales masDe18 = Array.FindAll(personas, p => { return p.Edad >= 18; }); // Con una sentencia, los {} y el return pueden quitarse masDe18 = Array.FindAll(personas, p => p.Edad >= 18); Las funciones lambda con un solo parámetro y/o una sola línea de código pueden simplificar su sintaxis aún más quitando (), {} y return ¡Pero aún así se escribía mucho! Empezaron como tipos delegados anónimos, permitiéndote declarar un delegado sin nombre en cualquier parte del programa en el que se necesite código ¡Y puedes escribir aún menos código en varios casos! ¡El resultado es muy similar a las λ-abstracciones del λ- calculo clásico! (de ahí el nombre ;)) <-
  • 19. © José Manuel Redondo López Linqin’ park Sin embargo hay 3 métodos “core” especialmente importantes: El primero es Filter (Where en C#) Finalmente, Reduce (Aggregate en C#) hace un cálculo con todos los elementos del IEnumerable Devuelve el mismo número de elementos, pero (probablemente) con otro tipo Devuelve un solo tipo con el resultado de ese cálculo También tienes el Map (Select en C#), que ejecuta operaciones sobre los elementos del IEnumerable, transformándolos potencialmente en otros tipos (en el ejemplo, ¡se crea una clase anónima ad-hoc!) El código pasado al reduce opera con el valor calculado hasta ahora y el elemento actual, para devolver el resultado del cálculo actualizado ¡Y ahí comienza la diversión! ¡Cadenas-Linq! Ienumerable “Vulgaris” + MoveNext() + Reset() + Current + Dispose() . var s1 = personas.Where(p => p.Empleo.Equals( “Poke-trainer”) && p.Ciudad.Equals(“Rojo”)); var s2 = s1.Select(p => new { Edad = p. Edad, Genero = p.Genero; }); ¡Mismo tamaño! Función Utilidad GroupBy Agrupa elementos por el valor de una propiedad OrderBy Ordena elementos por el valor de una propiedad Join Une IEnumerables usando el valor de una propiedad como criterio de unión FirstOrDefault Encuantra el primer element que cumple con un Predicate Max, Min, Average, Sum Cálculos con elementos, usando valores de propiedades … Mucho, mucho más ☺ Dictionary “Menos18”: 9, “Adultos”: 3 var dicc = s2.Aggregate( new Dictionary<string, int> { {“Menos18”, 0}, {“Adultos”, 0}, }, (dic, dato) => { if (dato.Edad >= 18) dic[“Adultos”]++; else dic[“Menos18”]++; return dic; }); Siempre que una operación Linq devuelva un IEnumerable, se puede concatenar su salida con la entrada de otra Dictionary “Menos18”: 9, “Adultos”: 3 ¡Combínalo con var para dejar boquiabierta a más gente! :D 1 var dicc = personas.Where( (1) ).Select( (2) ).Aggregate( (3) ); 2 3 using System.Linq; Ienumerable “Kriptoniano” + MoveNext() … + Dispose() + Select(…) + Where(…) … El espacio de nombres System.Linq incorpora 180+ métodos nuevos a cualquier IEnumerable (¡incluso los que hayas hecho tú!) ¡Linq tiene muchas más funciones! Linq es un conjunto de métodos de extensión para cualquier IEnumerable que les da “superpoderes”: Permite manipular su información de muchas formas útiles Devuelve solo los elementos del IEnumerable que cumplen con un Predicate pasado como parámetro Obtienes el mismo tipo de elementos, solo que (probablemente) menos <-
  • 20. © José Manuel Redondo López Rebel yield Los generadores son muy útiles porque puedes manipularlos como IEnumerables estándar (¡admiten foreach y métodos de Linq!) ya que…¡lo son! Un generador es “un truco de magia” que C# hace para hacerte creer que tienes algo IEnumerable… De esta forma puedes construir cualquier colección siguiendo un ciclo de calcular elemento, guardar estado, retornarlo, calcular siguiente, guardar estado, retornarlo... Por ello, solo necesitan memoria para un elemento de cada vez, ¡incluso si tienen infinitos elementos! Pero ¡solo crean elementos cuando se le piden! (la colección no se crea de antemano) Usar yield return T te obliga a devolver un IEnumerable<T> o un IEnumerator<T> Por tanto sus elementos pueden ser potencialmente infinitos…si quieres Los generadores siguen una política perezosa: solo trabajan cuando deben, y nunca más En la siguiente llamada, se continua desde la PRÖXIMA línea de código Siempre que se use yield return, se GUARDA EL ESTADO actual de la ejecución (¡es una continuación!) ¡NO desde el comienzo de la función! Realmente tienes una función “disfrazada” de colección…y ¡no se nota la diferencia! IEnumerable<T> MoveNext():bool Current: T Reset() Dispose() Lists Arrays Strings Generators C ó d I g o { (código que genera cada elemento) … } ∞? MoveNext() MoveNext() MoveNext() Current Current Current { (código que genera cada elemento) … } IEnumerable<int> NumerosAleatorios(int maxValor, int maxNumeros = -1) { Random r = new Random(); int contador = 0; while (true) { yield return r.Next(maxValor); if (maxNumeros > 0) if (contador >= maxNumeros) yield break; contador ++; } } var randoms = NumerosAleatorios(1000000, 10); foreach (var random in randoms) Console.WriteLine(random); Los generadores se crean con yield return IEnumerable<int> NumerosAleatorios(int maxValor, int maxNumeros = -1) { Random r = new Random(); int contador = 0; while (true) { yield return r.Next(maxValor); if (maxNumeros > 0) if (contador >= maxNumeros) yield break; contador ++; } } Guarda estado Retorna valor Continúa aquí en la siguiente iteración Guarda estado Retorna valor Continúa aquí en la siguiente iteración Código Principio del método Pedir un elemento ¿Es el prime ro? SI NO C o d I g o C o d I g o Código <-
  • 21. © José Manuel Redondo López Currifícatelo un poco Func<int, int> EcuacionRecta(int m, int b) { return x => m * x + b; } //Se convierte en… Func<int, Func<int, int>> EcuacionRectaCurrificada(int m) { return b => x => m * x + b; } La currificación es un método para convertir una función de varios parámetros en una sucesión de funciones de un parámetro equivalentes Ilegal en lambda cálculo λxyz. x + y + z Versión currificada equivalente λx. λy. λz. x + y + z Es una técnica usada en cálculo lambda, donde las funciones no pueden tener más de un parámetro IEnumerable<Citizen> GenteQuePagaMasQue(string ciudad, double cantidad) { return cs.Where(c => c.Ciudad.Equals(ciudad)) .Where(c=> c.Impuestos >= cantidad); } //Versión currificada Func<double, IEnumerable<Citizen>> GenteQuePagaMasQue (string ciudad) { return cantidad => cs.Where(c => c.Ciudad.Equals(ciudad)) .Where(c=> c. Impuestos >= cantidad);} ¡Pero permite usar la aplicación parcial! Func<int, int> equacion1 = EcuacionRecta(5, 3); Console.WriteLine(equacion1(10)); //53 Func<int, Func<int, int>> equacion2 = EcuacionRectaCurrificada(5); //Retorna una Func<int, int> (¡no un valor!) Console.WriteLine(equacion2(10)); Console.WriteLine(equacion2(3)(10)); //53 La currificación puede parecer rara en un lenguaje sin restricciones en el número de parámetros… //IEnumerable var grandesPagadoresDeOviedo = GenteQuePagaMasQue("Oviedo", 10000000); //¡Función que solo trabaja con ciudadanos de Oviedo! var pagadoresDeOviedo = GenteQuePagaMasQue("Oviedo"); //¡Procesa menos gente que la función de dos parámetros! var pagadoresDeOviedoEnLaMedia = pagadoresDeOviedo(1000); Dado que esto es una limitación muy fuerte, ¡la currificación emula poder pasar más de un parámetro! Con la aplicación parcial se obtiene código “medio ejecutado” en el que se reemplazan algunos parámetros por sus valores ❖ Currificar es otra forma de obtener código a partir de código existente ❖ No se crea “al vuelo” (expresiones lambda) o se pasan funciones existentes ❖ ¡Solo recuerda que en C# la currificación es un REQUISITO para hacer aplicación parcial! return cantidad => cs.Where(c => c.Ciudad.Equals(“Oviedo”)) .Where(c=> c.Impuestos >= cantidad);} Como este código “parcialmente ejecutado” habitualmente hace menos cosas, suele ir más rápido De este modo se puede obtener código nuevo a partir de código existente, ¡solo reemplazando algunos parámetros! <-
  • 22. © José Manuel Redondo López Trabajar de más no es lo más var deWakanda = personas.Where(p => { Console.WriteLine(“Filtrando: " + p.Nombre); return p.Cuidad.Equals("Wakanda"); }); ¿Sabes qué significa realmente “Linq está lleno de métodos perezosos”? //Obliga a procesar todos los elementos foreach (var wakandes in deWakanda) Console.WriteLine(wakandese.Nombre); //¡También oblige a hacerlo! deWakanda.Last(); var listaNombres = deGotham.Aggregate(new List<string>(), (lista, gothames) => { list.Add(gothames.Nombre); return lista; }); //¡Ahora imprime “Filtrando X" y "Procesando X"!} Habitualmente se termina una “cadena Linq” con un Reduce (Aggregate) Los Reduce no pueden ser perezosos, y por tanto obliga a procesar todos los elementos del generador: ¡Deben hacer el cálculo con todos los elementos! var personasPorCuidad = personas.GroupBy(p => { Console.WriteLine(“Agrupando:" + p.Nombre); return p.Ciudad; }); //¡Solo ahora se procesarán las personas! foreach (var grupo in personasPorCuidad) { var csOrdenados = grupo.OrderBy(p => p.Nombre); foreach (var ciudadano in csOrdenados) Console.WriteLine(ciudadano.Nombre); } //Encuentra al primer Gothamés var tipoDeGotham = personas.First(p => { Console.WriteLine(“Encontrado: " + p.Nombre); return p.Ciudad.Equals("Gotham"); }); var deGotham = personas.Where(p => { Console.WriteLine(“Filtrando: " + p.Nombre); return p.Cuidad.Equals("Gotham"); }).Select(p => { Console.WriteLine(“Procesando: " + p.Nombre); return new { Nombre = p.Nombre + p.Apellidos, Edad = p.Edad + “ d. B. (después de Batman)" }; }); //¡No imprime nada! Veámoslo…¿Crees que este código va a imprimir algo? ¡NO! ¿POR QUÉ? ¡Porque nadie le ha pedido elementos a deWakanda! (el IEnumerable devuelto es perezoso, ¡solo trabaja si le obligas!) ¿Pero tu quieres que imprima eso, verdad? Entonces, oblígale a trabajar Recuerda que las políticas perezosas son más efectivas cuando no tienes que procesar todos los elementos Esto también pasa cuando se enlazan varias operaciones perezosas. Solo cuando realmente se pidan elementos se ejecutará la "cadena" Como normalmente no se pone código con efectos laterales dentro de las funciones lambda usadas en Linq, estos casos no son un problema típico ¡Solo asegúrate de no hacer operaciones con efectos laterales! Linq solo procesará todos los elementos cuando sea necesario, y no se perderá información Usando cualquier medio para pedirle elementos al IEnumerable <-
  • 23. © José Manuel Redondo López La hermandad αβλ ¡Simplemente reemplazamos f por el segundo término y borramos λf! Primero, no deberíamos ejecutar una expresión λ con el mismo nombre de variable ligada (parámetro) en dos términos distritos ¡Esta expresión λ tiene dos términos distintos con el mismo nombre de variable ligada, aunque no son la misma variable! Primero comprobamos que no hace falta una conversión α esta vez Y luego hacemos otra reducción β con las dos expresiones que nos quedan Solo nos queda hacer una reducción β final ¡Si piensas detenidamente cada paso y no te precipitas, “ejecutar” expresiones λ no debería ser un problema! ❖ En ese momento podríamos "ejecutar" la expresión (algo llamado también reducción β o aplicación) ❖ Las aplicaciones usan los dos términos más a la izquierda de la expresión (aunque el teorema de Church-Rosser demostró que se pueden escoger en otro orden) ❖ Reemplazamos las apariciones de la variable ligada en el cuerpo del primer término con el segundo término ❖ Borrando la cabecera del primer término ❖ Y dejando el resto de la expresión como está … Y ahora podemos seguir el mismo proceso para ejecutar la expresión hasta el final Finalizar la “ejecución” de la expresión λ es ahora bastante trivial El λ-cálculo se considera el lenguaje de programación universal más pequeño (λf.λx.fx)(λx.x*x)3 Pero “ejecutar" expresiones λ requiere algo de práctica Esta expresión λ está compuesta por 3 términos (fíjate en los paréntesis). ¡Vamos a ejecutarla! (λf.λx.fx)(λx.x*x)3 (λf.λx.fx)(λy.y*y)3 Es necesaria una conversión α (renombrado) a un nombre de variable nuevo en cualquiera de los términos (λf.λx.fx)(λy.y*y)3 (λx.(λy.y*y)x)3 (λx.(λy.y*y)x)3 (λy.y*y)3 (λy.y*y)3 3 * 3 1st term 2nd term 3rd term Conversión α Reducción β Reducción β Reducción β <-
  • 24. © José Manuel Redondo López Haciendo Joining por la mañana Pero puedes relacionarlas entre ellas usando los valores de alguno de ellos ¡En estos casos, es el momento de usar el Join Linq! El segundo parámetro es el atributo a usar de las entidades del primer IEnumerable. El tercer parámetro es el atributo correspondiente en el tercer IEnumerable A veces tienes dos IEnumerables con entidades que no se relacionan directamente usando atributos Siempre se hace Join a IEnumerables. El primero es sobre el que se llama el Join, el segundo es el primer parámetro del Join Join acepta cuatro parámetros, ¡que vamos a explicar a continuación! Es una función que recibe una instancia del primer IEnumerable y otra que encaja con ella del segundo y que se puede usar para sacar solo los datos que necesites El ultimo parámetro del Join permite elegir los atributos con los que te quedas de entre ellos ¡Pero seguramente no necesitas tanta información! Hecho esto, internamente tendremos objetos de una macro-clase anónima con todos los atributos de los objetos que se han unido ¡Hay que seguir el mismo orden que los IEnumerable usados! ¡Con los datos de la primera instancia repetidos en cada una de ellas! (¡pero luego puedes hacer GroupBy con ellas! ☺ ☺) Y recuerda que se comporta como un Join de bases de datos Si una instancia del primer encaja con 5 instancias del Segundo, se obtienen 5 instancias de esa macro-clase anónima Máquina IP: string OS: string … EntradaLog IP: string Contenido: string … “10.0.0.1” “Windows“ “10.0.0.2” “Windows“ “10.0.0.99” “Linux“ Maquinas: IEnumerable<Maquina> “10.0.0.1” “Virus“ “10.0.0.1” “Troyano“ “10.0.0.37” “Kaiju“ Log: IEnumerable<EntradaLog> var Unidos = Maquinas.Join (Log, ??, ??, ?? ); var Unidos = Maquinas.Join (Log, maquina => maquina.IP, log => log.IP, ?? ); El tipo de estos atributos ¡debe implementar un Equals entre ellos! Luego debes especificar los atributos cuyo valor será usado para unir entidades var Unidos = Maquinas.Join (Log, maquina => maquina.IP, log => log.IP, (maquina, log) => new { Origen = “[“ + maquina.IP + “]”, log.Contenido, } ); “[10.0.0.1]” “Virus“ “[10.0.0.1]” “Troyano“ “[10.0.0.37]” “Kaiju“ Unidos: IEnumerable<(clase anónima)> <-
  • 25. © José Manuel Redondo López Group (By), mi villano favorito Hay ocasiones en las que tienes muchas entidades con algunos valores en común Para ello ¡Es la hora de usar GroupBy! Lo más típico en estos casos es agruparlas considerando estos valores comunes Empecemos diciendo que lo que se obtiene es un IEnumerable de “grupos” Por tanto, es muy útil para saber cual fue este valor sin consultar los elementos, y hacer operaciones con grupos (Sort, Where…) Esta propiedad Key ¡guarda el valor del atributo usado para agrupar las entidades! IGrouping es una clase hija de IEnumerable, que añade la propiedad Key El IEnumerable “interno” es una instancia de IGrouping Pero este IEnumerable “interno” ¡es especial! No te olvides de que ¡puedes agrupar entidades usando expresiones también! (no solo valores de propiedades) Por tanto, GroupBy simplemente clasifica entidades existentes Obtienes las mismas entidades, pero distribuidas en distintos grupos de acuerdo al valor que especifiques “i7 8700” “Intel“ “Ryzen 2700” “AMD“ “i9 9900” “Intel“ CPUs: IEnumerable<CPU> var porMarca = CPUs.GroupBy(cpu => cpu.Marca); Por tanto, obtienes un IEnumerable de IEnumerables ☺ Pero ¿que es realmente un grupo? ¡Otro IEnumerable! Como ves, ¡usarlo es muy simple! Solo hay que especificar cuál es el origen (atributo, expresión…) de los valores que quieres usar para agrupar “Ryzen 1700” “AMD“ (Grupo de todas las CPUs Intel) (Grupo de todas las CPUs AMD) porMarca: IEnumerable<???> El problema es entender cuál es la salida del GroupBy (y cómo manipularla) “i7 8700” “Intel“ “Ryzen 2700” “AMD“ “i9 9900” “Intel“ “Ryzen 1700” “AMD“ porMarca: IEnumerable<(IEnumerable “especial”)> “i7 8700” “Intel“ “Ryzen 2700” “AMD“ “i9 9900” “Intel“ “Ryzen 1700” “AMD“ porMarca: IEnumerable<IGrouping<string, CPU>> var porVelocidad = CPUs.GroupBy(cpu => cpu.GetGhz()); Key:”AMD” Key:”Intel” IGrouping IGrouping <-
  • 26. C# © José Manuel Redondo López
  • 27. © José Manuel Redondo López Protección contra Lock-uras List<int> numeros = new List<int>(); for (int i = 0; i < 1000; i++) { new Thread(() => { for (int j = 0; j<10000;j++) numeros.Add(j); } ).Start(); //Podría (o no) lanzar una excepción } “Atacar" (escribir :)) sobre un recurso no thread-safe siempre es mala idea BlockingCollection<int> numerosThreadSafe = new BlockingCollection<int>(); for (int i = 0; i < 1000; i++) { new Thread(() => { for (int j = 0; j<10000;j++) numerosThreadSafe.Add(j); }).Start(); //¡No lanza excepciones! } Esto es muy peligroso, pero podemos resolverlo. La primera opción es usar estructuras de datos que SI sean thread-safe List<int> numeros = new List<int>(); for (int i = 0; i < 1000; i++) { new Thread(() => { for (int j = 0; j < 10000; j++) { lock (new object()) { //¡Crimen mayor! :( :( numeros.Add(j); } } }).Start(); //Error en ejecución seguro Pero lock solo funciona correctamente si todos los threads que acceden a la sección crítica ¡hacen lock sobre el mismo objeto! Si no, lock no será efectivo: algunos threads no esperarán junto con otros, ya que no están asociados al mismo “objeto bloqueador"! class SeccionCriticaDeClase { private static StreamWriter ficheroLog = new StreamWriter(“Log.txt"); //Otra opción si no tenemos un objeto adecuado private static object blocker = new object(); … public void CodigoDelThread() { lock (logFile) // lock(bloqueador) { ficheroLog.WriteLine(txt); //... } } } Otra implementación típica es un objeto bloqueador a nivel de clase Debe usarse si la sección crítica es compartida por todas las instancias de una clase, y ninguna instancia accede a la misma al mismo tiempo que otra List<int> numeros = new List<int>(); for (int i = 0; i < 1000; i++) { new Thread(() => { for (int j = 0; j < 10000; j++) { //¡Todos los threads hacen lock //en el mismo objeto! lock (numeros) { numeros.Add(j); } }}).Start(); } class SeccionCriticaDeInstancia { private List<int> listaCritica = new List<int>(); //Otra opción si no tenemos un objeto adecuado private object bloqueador = new object(); … public void CodigoDelThread() { lock (listaCritica) //lock(bloqueador) { listaCritica.Add(datos); //... } } } La clase List de C# NO es thread-safe: fallará de forma aleatoria (y con diferentes errores) si se le añaden elementos desde varios hilos (¡comportamiento errático!) Otra opción es usar bloques lock(objeto) {...} para asegurarse de que varios threads no acceden concurrentemente a la estructura de datos lock duerme todos los threads que llegan a él, dejándoles pasar uno a uno a su sección crítica ({...}) Esta opción se usa si todos los threads usan el mismo objeto para acceder a la sección crítica La concurrencia se pierde dentro del {...}, pero...¡el programa funciona! ¡NUNCA uses un tipo básico! Los objetos bloqueadores pueden ser objetos existentes u objetos creados ad-hoc (¡tu decides!) Una implementación típica es usar un objeto bloqueador a nivel de instancia <-
  • 28. © José Manuel Redondo López Viviendo la vida Locka Las situaciones en las que podemos encontrar secciones críticas son variadas, pero hay escenarios muy comunes en las que podemos encontrar una En una sección crítica, con que solo un thread ESCRIBA en ella, todos los demás (sin importar si escriben o leen) deben proteger el acceso a la misma Lock necesita un objeto de parámetro, que se comporta como una puerta class LoggerConcurrente { // Solo un fichero, el mismo para cada instancia private static StreamWriter fichero = new StreamWriter(@"log.txt", true); // Otra opción (util si la sección crítica no es una // instancia) private static object bloqueador = new object(); public void WriteToLog(string txt) { lock (fichero) { fichero.WriteLine(txt); } } } Si la sección crítica es LA MISMA para cada objeto de la clase, se puede hacer lock sobre un objeto/atributo static o sobre static object bloqueador = new object(); private List<string> listaNombres = new List<string>(); void AddNombre(string nom) { //Se llama desde thread 1 // lock porque escribe sobre la sección crítica lock (listaNombres) { listaNombres.Add(nom); }} string GetNombre(int posicion) { //Se llama desde thread 2 // lock porque thread 1 escribe sobre la sección crítica lock (listaNombres) { return listaNombres[posicion]; }} Lock y ReaderWriterLockSlim son mecanismos de protección class ListaNombresConcurrente { // Uno distinto por instancia de ListaNombresConcurrente private List<string> listaNombres = new List<string>(); // Otra opción (util si la sección crítica no es una // instancia) private object bloqueador = new object(); public void AddNombre(string nom) { lock (listaNombres) { listaNombres.Add(nom); } } public string GetNombre(int posicion) { lock (listaNombres) { return listaNombres[posicion]; } } } Y entonces debe decidirse el contexto del lock… Pero para usar lock bien, ¡hay que tener cuidado! Para que funcione, todo hilo que se ejecute sobre la sección crítica debe hacer lock sobre la misma “puerta” Por puerta se entiende el objeto, no las referencias que apuntan a ellos. ¡Dos referencias pueden apuntar al mismo objeto! Si no es posible, se puede usar private object bloqueador = new object(); Si la sección crítica está a nivel de instancia (cada instancia tiene su propia sección crítica), se hará lock sobre el objeto de instancia/atributo que la modele ¡Piensa con cuidado el contexto de la sección crítica y elige con cabeza! Threads Estructuras de datos de System.Collections Datos de 64 bits (long, double…) Sentencias que se deben ejecutar sin interrupción Threads Threads <Sección crítica> Lee Lee Escribe Protección contra Threads < Sección crítica > lock(objeto1) lock(objeto2) ¡Este thread entra en la sección crítica sin restricciones! Si se usan distintas “puertas” para threads que entre en la misma sección crítica, el programa fallará  <-
  • 29. © José Manuel Redondo López Transfórmalo en ParaLelo Hay operaciones que tardarían mucho tiempo si las hacemos secuenciales ¡Pero puedes convertir el código en paralelo de forma muy sencilla usando TPL! Un ejemplo es esta función de cracking de passwords SHA1 sencilla ¡En este caso el código es 3,2 veces más rápido! PLinq también te permite hacer algo similar ¡Usa lock / ReaderWriterLockSlim / estructuras de datos thread-safe sabiamente! ¡Este código es 3,67 veces más rápido solo por usar Parallel.ForEach! Se puede hacer algo equivalente con bucles for (Parallel.For) (el rendimiento que se gana es el mismo) ¡Esto es 2,5 veces más rápido que hacerlo secuencial! (y 3,5 veces si usamos CrackSHA1TPL en lugar de ésta) ¿Hay que crackear varias claves? ¡No hay problema! TPL también te deja crackear todas concurrentemente con Parallel.Invoke! string CrackSHA1MonoThread(string pwdHash) { foreach (var palabra in ListaPalabras.Español) { var hash = CalculaHashSHA1(palabra); if (hash.Equals(pwdHash)) return palabra; //¡Descifrada! } return null; } string CrackSHA1MonoThread2(string pwdHash) { for (int i = 0; i < ListaPalabras.Español.Length; i++) { … (mismo código) } … string CrackSHA1TPL2(string pwdHash) { string result = null; Parallel.For(0, ListaPalabras.Español.Length, i => { var palabra = ListaPalabras.Español[i]; … (mismo código) } … var r1 = CrackSHA1MonoThread(pwd1); var r2 = CrackSHA1MonoThread(pwd2); var r3 = CrackSHA1MonoThread(pwd3); Se convierte en: Parallel.Invoke( () => r1 = CrackSHA1MonoThread(pwd1), () => r2 = CrackSHA1MonoThread(pwd2), () => r3 = CrackSHA1MonoThread(pwd3) ); static IEnumerable<string> CrackMultipleSHA1TPL( IEnumerable<string> pwdHashes) { string result = null; List<string> clavesCrackeadas = new List<string>(); Parallel.ForEach(ListaPalabras.Español, palabra => { var hash = CalculaHashSHA1(palabra); foreach (var passwordHash in pwdHashes) { if (hash.Equals(passwordHash)) { lock (clavesCrackeadas) { clavesCrackeadas.Add(palabra); //¡Descifrada! } } } }); return crackedPasswords; } var pwdLinq = ListaPalabras.Español.First(palabra => CalculaHashSHA1(palabra).Equals(passwordHash)); … var pwdPLinq = ListaPalabras.Español.AsParallel() .First(palabra => CalculaHashSHA1(palabra).Equals(passwordHash)); Podrían aparecer problemas de secciones críticas en el código, ¡y hay que tratarlos! Pero ¡NO OLVIDES QUE EL CÓDIGO VA A EJECUTARSE EN PARALELO! Nunca debes preocuparte del número de hilos que se crean. ¡Ambas librerías calculan el óptimo para tu máquina automáticamente! string CrackSHA1TPL(string pwdHash) { string result = null; Parallel.ForEach(ListaPalabras.Español, palabra => { var hash = CalculaHashSHA1(palabra); if (hash.Equals(pwdHash)) result = palabra; //¡Descifrada! }); return result; } <-
  • 30. © José Manuel Redondo López Cláusulas abusivas //Inicializar Thread t1 = new Thread(() => HazCosas1()); Thread t2 = new Thread(() => HazCosas2()); //Arrancar t1.Start(); t2.Start(); //Esperar a que los hilos terminen t1.Join(); t2.Join(); Los threads son la forma más básica de convertir código existente en concurrente string parametro1 = "..."; string parametro2 = "..."; var t1b = new Thread((param) => HazMasCosas1(param)); var t2b = new Thread(() => { return HazMasCosas2(); }); var t3b = new Thread((param1, param2) => { HazMasCosas3(param1, param2); }); t1b.Start(parametro1); var ret = t2b.Start(); t3b.Start(parametro1, parametro2); ¡Pero no pueden recibir varios parámetros ni devolver valores! string parametro1 = "..."; string parametro2 = "..."; string valorRetornado; var t2c = new Thread(() => { valorRetornado = HazMasCosas2(); }); var t3c = new Thread(() => { HazMasCosas3(parametro1, parametro2); }); //Arrancar t2c.Start(); t3c.Start(); //Esperar a que los hilos terminen t2c.Join(); t3c.Join(); Se usan variables libres (variables definidas fuera del código concurrente) como parámetros o para devolver cosas desde los hilos var calculos = new LinkedList<string>(); var t1d = new Thread(() => { int iteraciones = 10000; while (iteraciones-- > 0) { lock(calculos) { calculos.AddFirst(HazAunMasCosas2(parametro1)); } } }); var t2d = new Thread(() => { int iteraciones = 10000; while (iteraciones-- > 0) { lock(calculos) { calculos.AddFirst(HazAunMasCosas2(parametro2)); } } }); … Console.WriteLine(calculos.Count); //¡Ahora si! ¡Recuerda usar los mecanismos de bloqueo que necesites en estos casos si la variable no es thread-safe! string parametro1 = "..."; string parametro2 = "..."; var t3c = new Thread(() => { HazMasCosas3(parametro1, parametro2); }); … string valorRetornado; var t2c = new Thread(() => { valorRetornado = HazMasCosas2(); }); ¡Las cláusulas son la solución a este problema! var calculos = new LinkedList<string>(); var t1d = new Thread(() => { int iteraciones = 10000; while (iteraciones-- > 0) { calculos.AddFirst(HazAunMasCosas2(parametro1)); } }); var t2d = new Thread(() => { int iteraciones = 10000; while (iteraciones-- > 0) { calculos.AddFirst(HazAunMasCosas2(parametro2)); } }); t1d.Start(); t2d.Start(); t1d.Join(); t2d.Join(); Console.WriteLine(calculos.Count); //¡No es 20.000! ¡Pero hay que tener cuidado cuando varios threads escriben sobre ellas! ¡Manejar mal una sección crítica siempre es un peligro si no tenemos cuidado! <-
  • 31. © José Manuel Redondo López ReaderWriterLagartoSpock ☺ string Traduce(string clave) { lock(Cache) { return Cache[clave]; } } void AñadeTraduccion(string clave, string valor) { lock(Cache) { Cache.Add(clave, valor); } } Si declaramos ReaderWriterLockSlim CacheLock = …: ❖ CacheLock.EnterReadLock(): múltiples threads pueden hacer un bloqueo de lectura sobre CacheLock sin ser bloqueados ❖ CacheLock.EnterWriteLock(): Si un thread entra en un bloqueo de escritura sobre CacheLock, ningún thread puede entrar en otro modo sobre él (espera a que el proceso salga) ❖ CacheLock.EnterUpgradeableReadLock(): Tipo especial de bloqueo de lectura que promociona a escritura sin falta de salir del modo lectura ¡ReaderWriterLockSlim es nuestro salvador! void AñadeTraduccion(string clave, string valor) { CacheLock.EnterWriteLock(); try { Cache.Add(clave, valor); } finally { //Siempre libera un bloqueo de escritura, evitando deadlocks CacheLock.ExitWriteLock(); } } bool TraduceConTimeout(string clave, string valor, int timeout){ if (CacheLock.TryEnterWriteLock(timeout)) { try { Cache.Add(clave, valor); } finally { //Siempre libera un bloqueo de escritura, evitando deadlocks CacheLock.ExitWriteLock(); } return true; } else { return false; } } ¿Y si no podemos esperar? string Traduce(string clave) { CacheLock.EnterReadLock(); try { return Cache[clave]; } finally { //Siempre acaba liberando el recurso CacheLock.ExitReadLock(); } } Ahora si los threads llaman a Translate la mayor parte del tiempo ¡no se bloquean! (solo hay lecturas) void AñadeOActualiza (string clave, string valor) { CacheLock.EnterUpgradeableReadLock(); try { string resul = null; if (!Cache.TryGetValue(key, out resul)) { //¡Promociona a escritura sin salir de lectura 1º! CacheLock.EnterWriteLock(); try { Cache.Add(clave, valor); } finally { CacheLock.ExitWriteLock(); } } } finally { CacheLock.ExitUpgradeableReadLock(); } } ¿Y si solo escribimos cuando se cumpla una condición? ¡Es el momento de usar un bloqueo de lectura promocionable! Si la mayor parte del tiempo solo leemos, ¡estamos eliminando la concurrencia innecesariamente todo ese tiempo! ¡Lock es muy radical! ¿No hay algo más preciso? Segundo, adquiere la propiedad exclusiva de la variable (Si un thread intenta entrar en cualquier modo, tendrá que esperar) hasta que salga Primero, espera hasta que pueda adquirir propiedad exclusiva de la variable (no hay threads en ningún modo dentro) Las variables tipo ReaderWriterLockSlim hacen Enter y Exit en uno de esos tres modos ¿Y qué pasa cuando escribimos? Así ¡podemos entrar en modo escritura rápidamente y el menor tiempo posible! Podemos entrar en el lock con un timeout: si se acaba el tiempo, no se adquiere y la operación no puede realizarse en el tiempo indicado Por tanto, ¡ReaderWriterLockSlim es más eficiente, más preciso y tiene más opciones de uso que lock! Si ambos métodos se ejecutan en hilos diferentes y Cache no es thread-safe, necesitamos proteger escrituras y también lecturas (¡ya que escribimos!) <-
  • 32. © José Manuel Redondo López El maestro marionetista Luego el Master solo tiene que esperar hasta que todos los hilos que ejecutan código de los Workers terminen (método Join) ¡Habitualmente los Workers se ejecutan solo sobre su propia sección de los datos! Y, finalmente, devolver el resultado final de la operación agregando adecuadamente todos los resultados parciales Los Workers son habitualmente bastante sencillos: ejecutan una operación sobre su propia porción de los datos No hay que olvidarse que si se necesitan parámetros o variables para devolver los resultados de los cálculos ¡hay que usar los atributos de la clase Worker! Los accesos a esta estructura deben protegerse con lock si varios Workers escriben en ella y no es tread-safe ❖ El esquema Master-Worker es una forma manual de obtener paralelización por división de datos ❖ Una clase (Master), crea, inicializa, ejecuta y espera por varios hilos ❖ Los datos necesarios para ejecutar el código en esos hilos se guardan en instancias de otra clase: los Workers ❖ Hay varios Workers que hacen la misma operación sobre distintas partes de una colección de datos Worker[] workers = new Worker[this.numHilos]; int porHilo = this.datos.Length / numHilos; for (int i=0; i < this.numHilos; i++) workers[i] = new Worker (this.datos, i * porHilo, (i < this.numHilos - 1) ? (i+1) * porHilo - 1: this.datos.Length-1); // Último worker en los límites! var threads = new Thread[workers.Length]; for (int i=0;i<workers.Length;i++) { threads[i] = new Thread(workers[i].Run); threads[i].Start(); } } foreach (Thread t in threads) { t.Join(); //Espera a que acaben todos los hilos } foreach (Worker worker in workers) { <obtiene los resultados parciales> } return <resultado final>; Solo entonces el Master puede obtener cada resultado parcial calculado por cada Worker de forma fiable void Run() { …. (código del worker para inicializar los cálculos) for (int i= this.indiceDesde; i<=this.indiceHasta; i++) <hacer cálculos> } Worker(<datos>, int indiceDesde, int indiceHasta, <estructura de datos que recoge resultados>) { … (código del constructor) } void Run() { …. (código del worker para inicializar los cálculos) for (int i= this.indiceDesde; i<=this.indiceHasta; i++) { <hacer cálculos> lock (<estructura de datos que recoge resultados>) { <añadir resultado parcial> } } } El Master primero inicializa los objetos Worker con los datos y los límites del trozo de datos sobre el que se ejecutan. Luego, crea y arranca un hilo asociado con cada Worker M W W W <datos> El código de los hilos no puede devolver nada ni aceptar parámetros, así que hay que usar este “truco” ¡No te olvides de inicializar estos datos en el constructor del Worker! Algunas veces los Workers guardan su resultado parcial en una estructura de datos común pasada por el Master <-
  • 33. © José Manuel Redondo López Consumiendo productividad Por tanto, dos clases trabajan en paralelo con una estructura de datos compartida Pero si la estructura no es thread-safe, ¡podemos tener un problema! ¡Nunca esperes dentro del lock a que se inserten elementos! (¡deadlock!) Si no hay elementos, sal del lock y deja que un Producer pueda insertar alguno Luego, espera si no se ha podido sacar un elemento, pero siempre fuera del lock (o haz otra cosa antes de volver a intentarlo) ¡Usar lock así (o una estructura de datos thread- safe) nos asegura que funcionarán sin problemas! ❖ El esquema Productor-Consumidor es una forma típica de trabajar con código concurrente donde unas tareas (Producers) añaden elementos a una estructura de datos para que los usen otras (Consumers) ❖ Tanto Producers como Consumers comparten las estructuras de datos donde se añaden y extraen ❖ ¡Y esto puede ser un problema si no sabemos lidiar con ello! void HiloDelProducer() { … while (true) { var producto = new Producto(…); lock (cola) cola.Enqueue(producto); } } … (rest de código del producer) } void HiloDelConsumer() { … while (true) { Producto producto = null; lock (cola) { while (cola.Count == 0) Thread.Sleep(100); //¡Nadie puede acceder a cola! producto = cola.Dequeue(); } … (resto del código del consumer) } } Sin embargo, debemos tener cuidado en los Consumers si la estructura de datos está vacía… void HiloDelConsumer() { … while (true) { Producto producto = null; lock (cola) { if (cola.Count != 0) producto = cola.Dequeue(); } … (resto del código del consumer o esperar si producto es null) } } Comprueba solo el nº de elementos de la estructura en vez de eso Dependiendo del problema, puede haber varios Producers o Consumers <estructura de datos> <estructura de datos> Sabemos como solucionar esto: ¡lock! Supón que un Producer y un Consumer comparten una Queue de C# (que no es thread-safe)… <-
  • 34. C# © José Manuel Redondo López
  • 35. © José Manuel Redondo López Pongámonos C#erios ;) static class ObjectExtensions { static ConditionalWeakTable<object, ExpandoObject> miembrosDinamicos = new ConditionalWeakTable<object, ExpandoObject>(); public static dynamic __this__(this object obj) { //Obtiene un ExpandoObject asociado con obj //o crea uno nuevo si no existe return miembrosDinamicos.GetOrCreateValue(obj); } ¿Recuerdas los métodos de extensión? ¿Qué pasa si extendemos object? Persona p = new Persona(); //Añade dinámicamente atributos al objeto p p.__this__().Nombre = "Geralt"; p.__this__().Apellidos = “de Rivia"; //¡Añade código solo a p! (lo siento, el cast es necesario) p.__this__().ToString = (Func<string>)(() => p.__this__().Nombre + " " + p.__this__().Apellidos); // ¡Imprime “Geralt de Rivia”! Console.WriteLine(p.__this__().ToString()); Los ExpandoObjects asociados nos permiten ¡añadir cualquier cosa a cualquier objeto! ❖ Los lenguajes dinámicos son FLEXIBLES ❖ Habitualmente permiten añadir atributos o métodos a cualquier clase u objeto durante la ejecución ❖ Los objetos pueden tener miembros propios (¡libertad de la tiranía de las clases!) ❖ El precio a pagar es rendimiento…y una posibilidad más elevada de errores en ejecución  ❖ C# no puede hacer esto….pero…¿puede imitarlo? (<Intro CSI>Yeeeah!!</Intro CSI>) static class ObjectExtensions { … (código de __this__) public static dynamic __class__(this object obj) { return miembrosDinamicos.GetOrCreateValue( obj.GetType()); } } ¿quieres miembros de clase en lugar de los de instancia? Sin problema La ConditionalWeakTable se asegura que el recolector de basura elimina los miembros dinámicos cuando sus instancias asociadas dejan de usarse //Atributo dinámico de clase Persona p2 = new Persona(); p2.__class__().Raza = "Humano"; //Imprime “Humano” (¡Raza es de clase!) Console.WriteLine(p.__class__().Raza); La semántica de una clase se emula de esta forma Añadir propiedades dinámicas a las clases que se comporten como propiedades de instancia es un poco más complejo, pero se puede hacer con una aproximación similar ❖ Recuerda que es una IMITACIÓN ❖ Pero se puede usar para cosas “poderosas” ❖ Emular clases y objetos mutables ❖ Añadir dinámicamente código leído de textos a cualquier objeto (¿recuerdas CodeDOM del último tema de TPP?) ❖ Borrar o modificar miembros añadidos dinámicamente a voluntad ❖ No permite manipular miembros declarados en el código fuente ❖ ¡Pero es un juguete potente! ☺ ❖ ¡Que puedes extender con tus ideas! ❖ Solo hay que hacer using ObjectExtensions; en tus programas y ¡a divertirse! :D ❖ ¡Será aún más divertido cuando C# implemente “extension everything”! ¡CUALQUIER objeto tendrá ahora el método __this__()! Las llamadas a __this__ deberían, por ejemplo, comprobar el ExpandoObject asociado a obj.GetType y crear clones de las propiedades nuevas añadidas a la clase antes de devolver su ExpandoObject asociado <-
  • 36. © José Manuel Redondo López El incidente Roslyn Pero ¡no podemos usar las variables declaradas directamente en el programa dentro de los strings con código! Se puede ejecutar código arbitrario siempre que incluyas las referencias a las librerías adecuadas e imports Este ejemplo ejecuta consultas Linq sobre variables declaras pasando referencias, imports y globals a nuestra función Eval! ❖ Roslyn es un Compiler as a Service ❖ Permite crear analizadores de código que suministren errores y warnings dentro de Visual Studio ❖ Y construir extensiones de Visual Studio que procesen código fuente… ❖ …o construir otras herramientas que entiendan o ejecuten código fuente ❖ Y por supuesto…¡evaluar código bajo demanda contenido en strings! … (continúa de la viñeta anterior) var result = await CSharpScript.EvaluateAsync (codigo, opciones, globals: globals); //Devuelve el resultado una vez compilado y ejecutado codigo return result; } catch (CompilationErrorException e) { return string.Join(Environment.NewLine, e.Diagnostics); } } … //Podemos hacer eval como en Python! Console.WriteLine(Eval("2+2").Result); // Devuelve 4 var sampleNums = new int [] {1, 2, 3, 4, 5, 6}; //ERROR: sampleNums está fuera del ámbito del string //Si, incluso si la declaramos antes Console.WriteLine(Eval("sampleNums.Where(n=>n<3)") .Result); Con este método podemos evaluar fácilmente código C# que esté en strings (leído de teclado, fichero, internet…) var globals = new Globals { nos = sampleNums }; var references = new string[] {"System.Linq"}; IEnumerable r = (IEnumerable) Eval(“nos.Where(n=>n<3)", globals: globals, references: references, imports: "System.Linq").Result; foreach (var elem in r) Console.WriteLine(elem); } El parámetro globals resuelve esto ¡permitiendo añadir elementos declarados en el código para que podamos usarlos! Hecho esto, podernos evaluar código desde un string usando estas opciones y la variable “globals” pasada async Task<object> Eval(string codigo, object globals, string[] refs, string imports) { try { ScriptOptions opciones = null; if (refs != null) opciones = ScriptOptions.Default.WithReferences(refs); if (imports != null) if (opciones == null) opciones = ScriptOptions.Default.WithImports(imports); else opciones = opciones.WithImports(imports); … Lo primero que necesitamos es una función que maneje las referencias a ensamblados y los imports del código a ejecutar ❖ Para usar Roslyn se necesitan descargar los paquetes Microsoft.CodeAnalysis de NuGet ❖ ¿Quieres saber más? ¡Mira esto! ❖ https://joshvarty.wordpress.com/learn- roslyn-now/ ❖ https://github.com/dotnet/roslyn/wiki/Sampl es-and-Walkthroughs ❖ https://github.com/dotnet/roslyn/wiki/Scrip ting-API-Samples#addref La evaluación de Roslyn es asíncrona (¡podemos hacer otras cosas mientras termina de ejecutarse el código!) El código evaluado será más lento que el compilado…¡pero nos da mucha más flexibilidad! <-
  • 37. © José Manuel Redondo López Jugando con la voz Solo necesitas añadir la referencia System.Speech a tu proyecto Al crear una instancia de SpeechRecognizer de ejecuta la funcionalidad integrada de reconocimiento de voz de tu SO Puedes suministrarle una gramática de comandos o valores al reconocedor De esta forma, ¡es muy fácil reconocer lo que se dice! Combinándolo con los comandos, ¡se pueden crear aplicaciones dirigidas por voz muy fácilmente! ❖ Convertir texto a voz y viceversa es parte de las funcionalidades del Framework WPF ❖ Este framework es parte del .Net Framework, y se usa para construir GUIs ❖ Se puede consultar un tutorial completo de sus funcionalidades en: http://www.wpf-tutorial.com/about- wpf/what-is-wpf/ var pb = new PromptBuilder(); pb.AppendText(“Oh Dios, "); var estilo = new PromptStyle(); estilo.Volume = PromptVolume.Soft; // Habla bajo estilo.Rate = PromptRate.Slow; // Habla lento pb.StartStyle(estilo); //Aplica estilo de habla pb.AppendText(“Soy un Elcor "); pb.EndStyle(); pb.AppendText(“Hoy es: "); pb.AppendTextWithHint(DateTime.Now. ToShortDateString(), SayAs.Date); // Pronuncia como fecha narrador.Speak(pb); … var reconocedor = new SpeechRecognizer(); Reconocer lo que dices y convertirlo a texto es también muy sencillo con WPF var gBuilder = new GrammarBuilder(); var comandos = new Choices(“walk", “draw"); gBuilder.Append(comandos); var valores = new Choices(); valores.Add(“up", “down“, “left”, “right”); valores.Add(“sword", “potion", “knife"); gBuilder.Append(valores); reconocedor.LoadGrammar(new Grammar (gBuilder)); reconocedor.SetInputToDefaultAudioDevice(); //Event fired when speech is recognized void speechRecognizer_SpeechRecognized(object sender, SpeechRecognizedEventArgs e){ … (resto del código del evento) var comando = e.Result.Words[0].Text.ToLower(); var valor = e.Result.Words[1].Text.ToLower(); switch(comando) { case “walk": … (haz algo con el valor (“up”, “down”,…) ) case “draw": … (haz algo con el valor (“sword”, …) ) } Luego se lanza el reconocimiento de voz con speechRecognizer.RecognizeAsync(RecognizeMode.Multiple) y se para con speechRecognizer.RecognizeAsyncStop() Puedes crear efectos interesantes, como formas especiales de decir ciertos tipos de datos using System; using System.Speech.Synthesis; var narrador = new SpeechSynthesizer(); narrador.Speak(“Oh Dios, ¡estoy hablando!"); ¡Convertir texto a voz es muy simple! De nuevo es necesario añadir la referencia System.Speech al proyecto Si tenemos un GUI, es muy fácil lanzar un evento cuando se reconoce voz <-
  • 38. © José Manuel Redondo López Referencias Num Referencia [1] J. M. Redondo, «Admin-zines: Understand Infrastructure Administration concepts the easy way,» 8 2019. [En línea]. Available: https://www.researchgate.net/publication/335023411_Admin-zines_Understand_Infrastructure_Administration_concepts_the_easy_way. [2] J. M. Redondo, «New Features of C Sharp 5 and 6,» 1 2019. [En línea]. Available: https://www.researchgate.net/publication/330223681_New_Features_of_C_Sharp_5_and_6. [3] J. M. Redondo, «New Features of C Sharp 7,» 1 2019. [En línea]. Available: https://www.researchgate.net/publication/330358763_New_Features_of_C_Sharp_7. [4] J. M. Redondo, «New Features of C Sharp 8 and beyond,» 1 2019. [En línea]. Available: https://www.researchgate.net/publication/330514620_New_Features_of_C_Sharp_8_and_beyond. [5] F. Ortin, J. M. Redondo y J. Quiroga, «Design and evaluation of an alternative programming paradigms course,» Telematics and Informatics, vol. 34, nº 6, pp. 813-823, 2017. [6] F. Ortin, M. García and J. M. Q. J. Redondo, "Combining static and dynamic typing to achieve multiple dispatch," Information–An International Interdisciplinary Journal, vol. 16, no. 12, pp. 8731-8750, 2013. [7] J. M. Redondo, «C#aracterísticas Fantásticas y Dónde Encontrarlas: La Pythonización del C#,» 2 2020. [En línea]. Available: https://www.researchgate.net/publication/338584100_Caracteristicas_Fantasticas_y_Donde_Encontrarlas_La_Pythonizacion_del_C
  • 39. © José Manuel Redondo López Version 1.3, 3 November 2008 Copyright © 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. <https://fsf.org/> Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 0. PREAMBLE The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others. This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference. 1. APPLICABILITY AND DEFINITIONS This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law. A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language. A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none. The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words. A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque". Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only. The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text. The "publisher" means any person or entity that distributes copies of the Document to the public. A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the Document means that it remains a section "Entitled XYZ" according to this definition. The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License. 2. VERBATIM COPYING You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies. 3. COPYING IN QUANTITY If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. GNU Free Documentation License (1/3)
  • 40. © José Manuel Redondo López If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document. 4. MODIFICATIONS You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version: A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement. C. State on the Title page the name of the publisher of the Modified Version, as the publisher. D. Preserve all the copyright notices of the Document. E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices. F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below. G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice. H. Include an unaltered copy of this License. I. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence. J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. K. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles M. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version. N. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section. O. Preserve any Warranty Disclaimers. If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles. You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties—for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version. 5. COMBINING DOCUMENTS You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled "History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all sections Entitled "Endorsements". 6. COLLECTIONS OF DOCUMENTS You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document. GNU Free Documentation License (2/3)
  • 41. © José Manuel Redondo López 7. AGGREGATION WITH INDEPENDENT WORKS A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate. 8. TRANSLATION Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail. If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title. 9. TERMINATION You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License. However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material does not give you any rights to use it. GNU Free Documentation License (3/3) 10. FUTURE REVISIONS OF THIS LICENSE The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See https://www.gnu.org/licenses/. Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Document. 11. RELICENSING "Massive Multiauthor Collaboration Site" (or "MMC Site") means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A "Massive Multiauthor Collaboration" (or "MMC") contained in the site means any set of copyrightable works thus published on the MMC site. "CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization. "Incorporate" means to publish or republish a Document, in whole or in part, as part of another Document. An MMC is "eligible for relicensing" if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008. The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing.
  • 42. Diseño del avatar: Inmaculada Martínez Lobo (@inmmastar) Diseño el buho: Vanessa Redondo López (@creative_vanesa) ¡Y eso es todo! ¡Espero que os hayan sido útiles! ¡Nos vemos en la siguiente serie acerca de seguridad!