miércoles, 30 de septiembre de 2015

Predicates de manera fácil

De acuerdo a la documentación del .Net framework, el delegado genérico Predicate es algo que “representa un método que define un conjunto de criterios y determina si el objeto especificado cumple con esos criterios”. Pero ¿que quiere decir eso?. Leer la documentación que escriben los de Microsoft es fácil, pero no siempre se comprende todo lo que dice. Así que vamos a desmenuzar esa definición antes de mostrar algo de código para ver si se entiende mejor.

La definición dice que un Predicate “representa un método…”, y como los Predicates son un tipo de delegado, debemos entender que el Predicate es más bien un puntero a una función que realiza el trabajo.

Después dice que ese método al que apunta el Predicate “define un conjunto de criterios…” así que, la función contiene en su interior un conjunto de condiciones que se compararán con ciertas propiedades del objeto que se recibe como parámetro en la función.

Y para finalizar el análisis de la definición, ésta dice que “determina si el objeto especificado cumple con esos criterios”, es decir, que una vez que se hagan las comparaciones necesarias en el código interno de la función a la que apunta el Predicate, se debe informar si el objeto que se recibe como parámetro cumple o no con dichas condiciones. Es por eso que si se fijan, me he referido al método en estos párrafos como “función”, ya que se debe devolver un valor booleano que informe si el objeto que se está evaluando cumple o no con los criterios especificados dentro del método.

Pero ¿de qué objeto habla?, ¿que objeto es el que se recibe como parámetro?, ¿que objeto se evalúa?.

Más adelante veremos que cuando se pasa la dirección de la función que es utilizada como Predicate, en ningún momento se pasa algún argumento, entonces empiezan a aparecer las “cochinas” dudas y empieza uno a enredarse todito. Así que vamos aclarando eso del objeto que se recibe como parámetro de una vez.

Los delegados genéricos conocidos como Predicates, hasta donde yo sé, solo se pueden usar como argumentos en métodos de colecciones, listas, arrays, etc., es decir, en métodos de colecciones de objetos como por ejemplo Find, FindAll, FindLast, Exists, etc.. Por lo que es fácil deducir que los Predicates son como “callbacks”, es decir, funciones que se ejecutan y vuelven al punto donde fueron llamadas una y otra vez. No como funciones recursivas, sino que éstas son llamadas como en un ciclo foreach que se ejecuta tantas veces como elementos tenga la colección y que a su vez, al ir recorriendo dicha colección, va  pasando el elemento en turno como argumento a la función a la que apunta el Predicate para que revise si cumple con las condiciones en el interior de dicho método. Este bucle foreach del que hablo es creado cuando nuestro código es compilado y por lo tanto es prácticamente invisible para nosotros a menos que nos pongamos a visualizar el código IL que se genera cuando se compila nuestro programa.

Ahora bien, creo que es suficiente de tanta palabrería, así que veamos algo de código para terminar de entender todo este rollo que acabamos de leer. Así que supongamos que tenemos una clase Empleado definida de la siguiente forma:

  1. Public Class Empleado
  2.     Public Property Nombre As String
  3.     Public Property ApellidoPaterno As String
  4.     Public Property ApellidoMaterno As String
  5.     Public Property Edad As Byte
  6.     Public Property Salario As Decimal
  7.  
  8. End Class

Y que cargamos una lista con objetos de este tipo, por ejemplo:

  1. Sub CargaLista(ByVal lista As List(Of Empleado))
  2.     lista.Add(New Empleado() With {.Nombre = "Noé", .ApellidoPaterno = "García", .ApellidoMaterno = "Urías", .Edad = 30, .Salario = 15000.0})
  3.     lista.Add(New Empleado() With {.Nombre = "Enrique", .ApellidoPaterno = "Chávez", .ApellidoMaterno = "Castillo", .Edad = 42, .Salario = 16000.0})
  4.     lista.Add(New Empleado() With {.Nombre = "José", .ApellidoPaterno = "Escobedo", .ApellidoMaterno = "López", .Edad = 50, .Salario = 10000.0})
  5.     lista.Add(New Empleado() With {.Nombre = "Julio César", .ApellidoPaterno = "Salazar", .ApellidoMaterno = "Lindoro", .Edad = 45, .Salario = 8000.0})
  6.     lista.Add(New Empleado() With {.Nombre = "Arturo", .ApellidoPaterno = "Escobedo", .ApellidoMaterno = "López", .Edad = 37, .Salario = 12000.0})
  7. End Sub

Si tuviéramos que hacer búsquedas en esta lista utilizando por ejemplo el método Find de la misma, tendríamos que crear un delegado de tipo Predicate para identificar el elemento que coincide con la condición de búsqueda. He aquí una muestra:

  1. Private Function BuscaPorSalario(ByVal e As Empleado) As Boolean
  2.     If e.Salario = varSalario Then
  3.         Return True
  4.     Else
  5.         Return False
  6.     End If
  7. End Function

Y en el método Find pasar la dirección de la función que será utilizada como Predicate, por ejemplo:

  1. Dim oEmpleado As Empleado = lista.Find(AddressOf BuscaPorSalario)

Al pasar la dirección de memoria al método Find de la lista, el compilador crea un bucle interno en el código IL para utilizar la función de delegado BuscaPorSalario con cada elemento de la lista para ver si la condición de búsqueda se cumple. Cuando el salario de uno de los empleados en la lista coincida con el del valor de la variable varSalario, la función Predicate devolverá Verdadero y el método Find seleccionará ese objeto para ser asignado como resultado a la variable oEmpleado.^

Para finalizar voy a decir que este tipo de delegados es fácil de sustituír por expresiones lambda, ya que con ellas se reduce la cantidad de código y la legibilidad del mismo. Les recomiendo leer esta otra entrada del blog para entender mejor qué son y cómo se usan.

Y eso es todo, espero que en verdad haya sido fácil entender qué son y cómo funcionan los Predicates. Hasta pronto.

0 comentarios:

Publicar un comentario

 
;