Diario del desarrollador #1 - LINQ: «Hizome la Divina Potestad, el Saber Supremo»
Buenas a todos comunidad de Steemit.
Hoy daré inicio a otra categoría de contenido que compartiré en la plataforma, enfocada en el desarrollo de juegos y aplicaciones, más enfocado en Unity. Éstas no tendrán un orden de aprendizaje particular, sino que tratarán temas diversos desde la programación o el diseño general.
Si desean que a próxima semana hable de algun tema en particular pueden dejar su pedido en la casilla de comentarios (mis conocimientos pasan por programación en C# con Unity, modelado y animación con 3ds Max, texturizado con Substance Painter y Photoshop, animación con huesos, ilustración vectorial, un poco de pintura digital y game design).
El tema del capítulo de hoy es Language Itegrated Query, también conocido por sus siglas como LINQ. Éste es un tema de dificultad intermedio/avanzado en programación con C#, no es recomendable para iniciados.
Conocimientos previos: nociones de C#, extensiones, IEnumerable, Generics, Tuple (opcional), labda y delegados.
¿Qué es LINQ?
En principio LINQ es una extención del lenguaje desarrollada por Microsoft para los lenguajes .NET (entre ellos C#) para ser usados en cualquier tipo de colección (clases que implementan la interface IEnumerable) que puedan ser iteradas como lo pueden ser el array, las listas, los diccionarios, etc.
Los métodos LINQ nos permiten desde filtrar una colección en base a alguna condición, convertir cada elemento en algo diferente, combinar varias colecciones en una o transformarla en algo completamente diferente.
Sin embargo, la verdadera ventaja que nos otorga la extención no es sólo la gran variedad de opciones de forma rápida que nos ofrece, sino que maneja los elementos de las colecciones de forma Lazy.
¿Qué es Lazyness?
De forma simple y resumida es la forma de manejar un elemento a la vez según se necesite, lo cual disminuye el consumo de memoria de nuestra aplicación.
Cuando realizamos una llamada de una función Lazy y la guardamos en una variable, ésta realmente no es guardada en memoria sino hasta el momento en que es implementada y sólo trabaja lo justo y necesario.
Ej: Tomamos una colección de enemigos que vamos a filtrar los que se encuentren a cierta distancia, luego vamos a obtener el componente «Brain» y ejecutar un método particular.
LINQ funcionaría de la siguiente manera: Itero la colección y pido un elemento al método que castea, éste le pide uno al que filtra y allí ese elemento pasa todo el proceso. Ésto se repite individualmente por cada uno de los elementos en la colección, uno por uno. En el caso de que pidieramos el primer elemento únicamente, el proceso sólo se repetiría una vez.
¿Qué métodos tiene LINQ y cómo implementarlo?
Primero que nada para utilizar LINQ debemos importar la librería:
using System.LINQ;
Los mencionados a continuación no son todos los métodos que cuenta LINQ, pero sí los que más utilizo diariamente.
Select
IEnumerable<TResult> Select<TKey, TResult>(this IEnumerable< TKey > collection, Func< TKey, TResult> selector)
El método Select nos permite castear cada elemento de la colección en otro tipo de dato, como el ejemplo siguiente.
//Los Human asignados fueron creados e inicializados con anterioridad.
List<Human> allHumans = new List<Human> { Marco, Polo, Dante, Virgilio };
var animals = allHumans.Select(human => human as Animal);
foreach (Animal item in animals)
{
//Do something.
}
En este ejemplo la variable animals será una colección de los mismos elementos que allHumans pero ahora casteados como la clase Animal. Sin embargo, tener en cuenta que a diferencia que allHumans, animals no es una lista, sino un IEnumerable y ésta no existirá en memoria hasta el instante en que entra en la iteración del foreach.
Básicamente los métodos de LINQ que funcionen con Lazyness se las puede determinar como «promesas» que se ven cumplidas al momento de necesitarlas.
Where
IEnumerable< TKey > Where< TKey >(this IEnumerable< TKey >, Func<TKey, bool> predicate)
El Where nos permite filtrar cada elemento de una colección en base a un predicado (una lambda o un delegado que nos devuelva un booleano).
List<AnimalClass> animals = new List<AnimalClass> { Perro, Gato, Lobo, Serpiente, Aguila };
var cuadrupedos = animals.Where(animal => animal.patas == 4);
//Obtendrémos al momento de usarla un IEnumerable que contenga «Perro, gato y lobo».
SelectMany
Nos permite crear una colección en base a colecciones internas de cada elemento.
IEnumerable<TResult> SelectMany<TKey, TResult(this IEnumerable< TKey > collection, Func<TKey, IEnumerable<TResult>> selector)
En el siguiente ejemplo podemos ver cómo de cada objeto obtendrémos una lista de otros objetos. Particularmente, obtendrémos una lista con todos los items que tienen todos los soldados de la lista.
List<Soldier> soldiers = new List<Soldier>() { Juan, Perez, Jorge, Julio };
IEnumerable<Item> items = soldiers.SelectMany( soldier => soldier.myItems);
Take y TakeWhile
IEnumerable< TKey > Take< TKey >(this IEnumerable< TKey > collection, int amount)
IEnumerable< TKey > TakeWhile< TKey >(this IEnumerable< TKey > collection, Func<TKey, bool> predicate)
Ambos métodos nos permiten obtener una colección de valores que puede ser de igual o menor tamaño al que le otorgamos. Take se encarga de tomar tanta cantidad de elementos como el número entero que le pasemos, mientras que TakeWhile toma elementos hasta que la condición del predicado sea falsa y corte el proceso.
NOTA: Si se le pide un número mayor al tamaño de la colección cortará una vez llega al final de ésta, sin dar ningún error.
Skip y SkipWhile
IEnumerable< TKey > Skip< TKey >(this IEnumerable< TKey > collection, int amount)
IEnumerable< TKey > SkipWhile< TKey >(this IEnumerable< TKey > collection, Func<TKey, bool> predicate)
Los métodos Skip y SkipWhile se encargan de «saltar» una cierta cantidad de elementos y luego devolver todos los restantes, el caso contrario que el de los Take. Sin embargo, el SkipWhile descarta elementos hasta encontrarse con uno cuyo predicado sea falso y devuelve el resto.
NOTA: De forma similar a los Take, puedes colocar un número mayor al tamaño de la colección, lo que resultará en una colección vacía.
OrderBy, OrderByDescending y ThenBy
Estos métodos se encargan de ordenar la colección que le pasemos bajo un criterio perticular.
IOrderedEnumerable<TValue> OrderBy<TValue, TKey>(this IEnumerable<TValue> collection, Func<TValue, TKey> keySelector)
IOrdererEnumerable<TValue> OrderByDescending<TValue, TKey>(this IEnumerable<TValue> collection, Func<TValue, TKey> keySelector)
IOrderedEnumerable<TValue> ThenBy<TValue, TKey>(this IOrdererEnumerable<TValue> ordererCollection, Func<TValue, TKey> keySelector)
El ThenBy nos permite otorgarle otra capa de ordenamiento a nuestra colección. Por ejemplo si ordenamos una lista de A a Z con un OrderBy, con ThenBy podemos ordenar cada grupo que empiece por la misma letra por la edad.
Zip
En palabras sencillas, el método Zip nos permite combinar dos colecciones en una nueva de un tipo diferente a sus originales.
IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> firstCollection, IEnumerable<TSecond> secondCollection, Func<TFrist, TSecond, TResult> resultSelector)
NOTA: Hay que tener en cuenta que si una de las colecciones es más corta que la otra (bastante probable), devuelve una colección con el largo de la más corta (descartando los restantes de la otra colección).
Por ejemplo:
List<int> identificators = new List<int> { 1, 2, 3, 4, 5 };
List<string> names = new List<string> { Marcelo, Julio, Marco, Polo }
IEnumerable<Tuple<string, int>> parejas = names.Zip(identificators, (name, id) => Tuple.Create(name, id));
//El resultado sería:
// -> Marcelo, 1
// -> Julio, 2
// -> Marco, 3
// -> Polo, 4
En el ejemplo mostrado terminaríamos con una lista de Tuples con el resultado mostrado. Como se puede apreciar, el último elemento de la colección de identificators lo descartó ya que no tenía un elemento para emparejarlo.
Reverse
No necesita mucha explicación, invierte los elementos de la colección (el primero pasa a ser el último, el ateúltimo el segundo, etc.).
IEnumerable< TKey > Reverse< TKey >(this IEnumerable< TKey > collection)
Count, Contains y Any
Aquí ya nos salimos de los métodos Lazy (exceptuando el Any), pero no por ello menos útiles o importantes.
int Count< TKey >(this IEnumerable< TKey > collection)
bool Contains< TKey >(this IEnumerable< TKey > collection, TKey element)
bool Any< TKey >(this IEnumerable< TKey > collection)
Any nos permite saber si la colección cuenta con algún elemento. Lo performante de este método es debido a que pide el primer objeto, si recibe algo sabe que no está vacía y retorna true. También existe una optativa que recive un predicado cuya firma sería bool Any< TKey >(this IEnumerable< TKey > collection, Func<TKey, bool> predicate), pero perdería su practicidad ya que debe recorrer la colección hasta hayar el elemento.
Contains funciona de la misma forma que el método de la listas en C#, devuelve true si el elemento pasado por parámetro se encuentra dentro de la colección.
Count nos devuelve el número de elementos de la colección. Por si se los preguntan, es menos performante que el parámetro predefinido de las listas, debido a que debe recorrer por completo todo los elementos de la colección.
First, FirstOrDefault, Last y LastOrDefault
TKey First< TKey >(this IEnumerable< TKey > collection)
TKey FirstOrDefault< TKey >(this IEnumerable< TKey > collection)
TKey Last< TKey >(this IEnumerable< TKey > collection)
TKey LastOrDefault< TKey >(this IEnumerable< TKey > collection)
Los métodos First y Last nos devuelven el primer y último valor de la colección respectivamente, como dicen sus nombres. Sin embargo, tienen la contra de que si la colección está vacía lanzará un error al encontrarse con un elemento nulo.
El caso contrario es FirstOrDefault y LastOrDefault que si no encuentran ningún valor nos devuelven el valor predeterminado del tipo.
Aggregate
Dejado uno de los mejores para casi el final, aunque no utiliza Lazyness nos permite convertir nuestra colección en otra cosa distinta.
TAccumulate Aggregate<TKey, TAccumulate>(this IEnumerable< TKey > collection, TAccumulate seed, Func<TAccumulate, TKey, TAccumulate> func)
Nuestra seed será la inicialización del elemento al que queremos convertir nuestra colección durante el proceso del Aggregate. Como es un método que necesita pasar por todos los elementos de la colección, nos es más conveniente implementar otro método de LINQ si nos es posible y utilizarlo sólo cuando sea necesario.
ToList, ToArray y ToDictionary
Estos serán los últimos que mencionaré incluidos en la librería de LINQ, aunque eso no quiere decir que no hay métodos interesantes. Aquellos que compartí son los que considero más importantes y útiles a la hora de programar sus proyetos en Unity, y de necesidad de saber primordial para la optimización del código.
List< TKey > ToList< TKey >(this IEnumerable< TKey > collection)
TKey[] ToArray< TKey >(this IEnumerable< TKey > collectio)
Dictionary<TKey, TValue> ToDictionary<TValue, TKey>(this IEnumerable<TValue> collection, Func<TValue, TKey> keySelector)
Estos métodos permiten convertir nuestras colecciones, recordemos que la gran mayoría de métodos de LINQ nos devuelven un IEnumerable, en algo concreto como una Lista, un array o un Diccionario respecticamente.
BONUS
Como bono extra por llegar hasta aquí sin terminar con un dolor de cabeza, dejaré unos métodos que realicé que sirven como extenciones de funcionalidad para colecciones que, al menos a mí, me son de bastante utilidad.
Para usarlos coloquenlos dentro de una clase de acceso público estática.
public static int IndexOf< TKey >(this IEnumerable< TKey > collection, TKey element)
{
int index = 0;
foreach (TKey item in collection)
{
if (item.Equals(element))
return index;
index ++;
}
return -1;
}
public static int IndexOf< TKey >(this IEnumerable< TKey > collection, Func<TKey, bool> predicate)
{
int index = 0;
foreach (TKey item in collection)
{
if (predicate(item))
return index;
index ++;
}
return -1;
}
Este método IndexOf funciona de forma similar al método del mismo nombre propio de las listas, nos devuelve el índice del elemento que le pasemos por parámetro o que cumpla la condición del predicado.
public static TKey GetValue< TKey >(this IEnumerable< TKey > collection, int index)
{
int temp = 0;
foreach (TKey item in collection)
{
if (temp == index)
return item;
temp++;
}
return default;
}
public static TValue GetValue<TKey, TValue>(this IEnumerable<Tuple<TKey, TValue> collection, TKey element)
{
foreach (Tuple<TKey, TValue> item in collection)
{
if (item.Item1.Equals(element))
return item.Item2;
}
return default;
}
public static TValue GetValue<TKey, TValue>(this IEnumerable<Tuple<TKey, TValue> collection, Func<TKey, bool> predicate)
{
foreach (Tuple<TKey, TValue> item in collection)
{
if (predicate(item.Item1)
return item.Item2;
}
return default;
}
Este último cuenta con varias variante, aunque en definitiva te permite obtener un valor determinado dentro de una colección sin necesidad de castearlo a otro tipo de colección que use indexer.
En la primera variante nos devuelve el valor en un index determinada. La segunda y tercera son de usos exclusivos para Tuples de dos elementos, donde podríamos utilizar una colección Lazy de Tuples con el primer valor como Key y el segundo valor como Value. Siguiendo esa idea, la segunda optativa nos devuelve el Item2 de una Tupla determinada al pasarle su Item1, y la tercera retorna el Item2 cuyo Item1 cumpla un predicado.
También comparto estas dos últimas extenciones específicas para su uso en Unity:
public static void Print(this object log)
{
UnityEngine.Debug.Log(log.ToString());
}
El pequeño método Print nos permite realizar un print directo a la consola de Unity. Algunos ejemplos de uso:
public void Start()
{
//Printear strings directos.
"Este es un buen print".Print();
//Printear strings más complejas.
("Este es un " + "buen ejemplo " + "de un print: " + 5).Print();
//O printear un objeto cualquiera, como una clase.
//Si en la misma sobreescribimos el método **ToString()**, podremos personalizarlo mejor.
UnityEngine.MonoBehaviour.Print();
}
Y el último método que compartiré es para el guardado rápido de ScriptableObjects, ya sea por su uso en el editor o en runtime.
public static void SaveAsset(this UnityEngine.ScriptableObject asset)
{
UnityEditor.EditorUtility.SetDirty(asset);
UnityEditor.AssetDatabase.SaveAssets();
UnityEditor.AssetDatabase.Refresh();
}
Simplemente se usa de la siguiente manera y listo, asset guardado. myAsset.SaveAsset() y listo.
Esto es todo por hoy comunidad, espero que les haya servido y la traerá la próxima otro tema igual de interesante e importante. Saludos.
Congratulations @deadlysmile! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :
Click here to view your Board
If you no longer want to receive notifications, reply to this comment with the word
STOP
Hello @deadlysmile! This is a friendly reminder that you have 3000 Partiko Points unclaimed in your Partiko account!
Partiko is a fast and beautiful mobile app for Steem, and it’s the most popular Steem mobile app out there! Download Partiko using the link below and login using SteemConnect to claim your 3000 Partiko points! You can easily convert them into Steem token!
https://partiko.app/referral/partiko
Congratulations @deadlysmile! You received a personal award!
You can view your badges on your Steem Board and compare to others on the Steem Ranking
Vote for @Steemitboard as a witness to get one more award and increased upvotes!