Documentar el código

Documentar el código

El código además de ser limpio, legible, entendible o estructurado entre otras características, debe estar muy bien documentado con el fin de efectuar un mantenimiento, modificación o ampliación de nuestra fuente. Además, es posible generar documentación en base a estos comentarios como es el caso de JavaDoc.

Existen muchas formas de comentar el código, por ejemplo:

C#, Java, PHP, etc.

  • Solo C#. Podemos hacerlo con tres barras /// en la que al crear una nueva línea, la siguiente línea queda también comentada con ///. Si las escribimos antes de una clase o un método, genera etiquetas de comentario que expondré a continuación.
  • Dos barras, crea una línea de código comentado
  • Comentario multilínea. Una barra y un asterisco /* provoca que el código esté comentado hasta que se cierre el comentario con asterisco barra */

Visual basic.NET

  • Comillas simples para comentar línea. ‘Esto es un comentario en VB.NET
  • Usar la etiqueta REM. REM Esto es un comentario en VB.NET
  • Si queremos crear varias líneas, debemos usar el concatenador guión bajo
    REM Esto es un comentario_ 
    en VB.NET

HTML

  • para cerrar el comentario

Comentarios de clases y métodos

Cuando estamos escribiendo código e instanciamos una clase o seleccionamos un método de una clase, los IDE,s nos muestran una ayuda que nos muestra que hace el método, que parámetros tiene o que sobrecargas y esto se consigue del siguiente modo:

C#

    • Escribimos /// precediendo a una clase o un método y se generan automáticamente una serie de etiquetas xml. La documentación de las etiquetas la pueden encontrar en el siguiente enlace.
      /// <summary>
      /// Obtiene el nombre completo del objeto Persona
      /// </summary>
      /// <param name="dni">DNI del objeto persona</param>
      /// <returns>Cadena de texto con el nombre completo</returns>
      public string GetFullName(string dni)
      {
      	return "Joaquín Martínez;
      }
      
      /// <summary>
      /// Obtiene el nombre completo del objeto Persona
      /// </summary>
      /// <param name="id">Identificador del objeto persona</param>
      /// <returns>Cadena de texto con el nombre completo</returns>
      public string GetFullName(int id)
      {
      	return "Joaquín Martínez;
      }
      

Java

    • Escribimos encima de un método /** e INTRO y se genera las etiquetas para documentar el código
      /**
      * Obtiene el nombre completo del objeto Persona
      * @param dni DNI del objeto persona
      * @return Cadena de texto con el nombre completo
      */
      public String getFullName(String dni){
      	return "Joaquín Martínez";
      }
      
      /**
      * Obtiene el nombre completo del objeto Persona
      * @param id Identificador del objeto persona
      * @return Cadena de texto con el nombre completo
      */
      public String getFullName(int id){
      	return "Joaquín Martínez";
      }
      

      coment04coment05

Regiones

Otra forma de mantener el código ordenado es mediante regiones, esto nos va a permitir por ejemplo mantener en una clase de negocio los métodos de actualización, adición y eliminación separados de los de selección, mantener propiedades en una región, campos en otra, métodos públicos en otra, privados en otra y así cuantas regiones queramos tener con el fin de mantener el código ordenado. Yo personalmente en una clase, generalizando, siempre tengo una región para constructores y destructores, una para enumeraciones, una para campos, una para métodos públicos y protected, otra para privados y si la clase es un form o window, añado una para controles y dentro de esta, por tipo de control, añado métodos de evento de cada uno, por ejemplo, Buttons, DataGrid, etc.

C#

#region Métodos públicos
        // aquí podemos poner todos los
        // miembros de clase que queramos
#endregion

VB.NET

#Region "Métodos Públicos"
' aquí podemos poner todos los
' miembros de clase que queramos
#End Region

def45-7-2-1
Java

// <editor-fold desc="Métodos públicos" defaultstate="collapsed">
        // aquí podemos poner todos los
        // miembros de clase que queramos
// </editor-fold>

coment06.png

Tareas

Visual Studio tiene comentarios de tareas, es decir si incluimos una serie de comentarios específicos (TOKEN) en alguna parte del código con un comentario adicional, en la lista de tareas, podemos acceder a esta localización de código para continuar con la tarea pendiente. Estos Token pueden personalizarse en el Menú Herramientas, Opciones, Lista de tareas. En este ejemplo, he colocado un TOKEN para revisar el contenido del método y en la lista de tareas, al hacer doble click, nos dirigiremos al código en cuestión.

coment07.png

Hay que añadir y es una buena costumbre, documentar las clases con autoría, fechas de creación y modificación, que hace la clase, que función tiene, las modificaciones que se han realizado, etc.

Hacedme caso, un poco tiempo perdido en documentar, nos hará ganar muchísimo tiempo en un futuro.

coment08.png

 

Anuncios

Atajos Netbeans

Atajos Netbeans

Todos los IDE,s tienen una serie de mejoras que nos ayudan a escribir código de manera fácil y evitando errores, en este caso Netbeans no podía ser menos.

¿Que son los atajos de Netbeans? Son palabras que nos permiten escribir bloques de código de una manera rápida evitando repetir estructuras continuamente. Lo primero de todo que son muy prácticos y lo segundo que evitan muchos errores, (causan otros), pero creo que las ventajas son muy superiores a los pocos inconvenientes que pudiera tener. Para el que no esté familiarizado con el código, lo mejor sería teclear todas las instrucciones letra por letra y olvidarse de los atajos por una temporada hasta que controléis la mayoría de las instrucciones. Hay dos tipos, uno mediante combinación de teclas y otros escribiendo palabras + TAB.

Las combinaciones de teclas, pueden visualizarse en los menús y menús contextuales. Alguno de los ejemplos de combinación de teclas:

  • CTRL + SPACE→ o completa la palabra que estamos intentando escribir o nos muestra una lista con las sugerencias.
  • ALT + ENTER→ Si nuestro código se encuentra subrayado en rojo, nos muestra que podemos hacer. Si instanciamos Scanner y no tenemos importado java.util.Scanner, al combinar sobre este nos mostrará lo mismo que si pulsamos sobre la bombilla de sugerencias.
  • CTRL + R→Imaginad que tenemos una variable en 20 sitios distintos, pues si nos situamos sobre ella y usamos este atajo, nos resalta todas las variables en el código pudiendo detectarlas con más facilidad.
  • ALT + SHIFT+ F →Da formato al código
  • CTRL + E → Borra líneas de código CTRL+ SHIFT+ Flecha Arriba o Abajo →Copia líneas de código
  • ALT + SHIFT+ Flecha Arriba o Abajo →Sube o baja líneas de código
  • CTRL + SHIFT + B. Navega hasta la fuente del código.
  • CTRL + B. Navega hasta la declaración
  • ALT + F7. Busca todas las referencias usadas en los proyectos abiertos
  • F6. Ejecuta el proyecto
  • CTRL + F5. Ejecuta en modo depuración

Ejemplos de palabras + TAB.

atajos01También es posible usar CTRL + SPACE cuando estemos escribiendo alguna de estas palabras y nos mostrará las sugerencias. Todos estos atajos están al principio de la lista marcados con un icono cuando pusalmos CTRL + SPACE

  • sout + TAB → System.out.println(“”)
  • soutv + TAB → System.out.println(“this = ” + this);
  • serr + TAB → System.err.println(“”);
  • for +TAB → for (int i = 0; i < 10; i++) { }
  • forc + TAB → for (Iterator iterator = col.iterator(); iterator.hasNext();) {Object next = iterator.next();}
  • fore + TAB → for (String arg : args) { }
  • whilexp +TAB → while (true) { }
  • do +TAB → do { } while (true);
  • if +TAB → if (true) { }
  • ifelse +TAB → if (true) { } else { }
  • sw +TAB → switch (var) { case val: break; default: throw new AssertionError(); }
  • trycatch → try { } catch (Exception e) { }

una vez que los escribimos, hay que tener en cuenta que algunos de ellos debemos completarlos (de ahí los posibles errores). Por ejemplo, sw que genera el bloque de código switch hay que escribir el valor de la variable y los valores de cada caso, si no el código nos daría error. El bloque de código fore, se debe indicar la colección o forc hay que indicar el iterator.

Espero que os sirva de ayuda aunque reitero que si no estáis familiarizados con el código hay que escribirlo mil veces hasta que nos suene antes de usar atajos.

Distancia de Levenshtein

Distancia de Levenshtein

La distancia de Levenshtein es el número mínimo que necesitamos para convertir una palabra en otra, un ejemplo pelo y perro tienen tienen una distancia de 2 porque tendríamos que sustituir la l por una r y añadir una r, dos movimientos, distancia 2. Otro ejemplo, murcielago y muerdago que tienen una distancia de 5, ya que a murciélago le tenemos que quitar c,i,e,l y a muerdago le quitamos la d, por tanto necesitamos 5 movimientos para convertir una palabra en otra. Resumiendo, cuanto más pequeña sea la distancia, más parecidas son las dos palabras.

¿Esto para que sirve?, pues muy fácil, para obtener palabras similares en los buscadores o mostrar palabras alternativas por ejemplo. Vamos a ver el algoritmo y como funciona.

Este algoritmo es una matriz de n filas por m columnas donde n y m son el número de caracteres + 1 de cada palabra. El primer paso que se realiza es rellenar la primera fila y la primera columna con números secuenciales de 0 hasta n+1 y desde 0 hasta m+1 , quedando del siguiente modo para el caso del ejemplo inicial la matriz de 11×9:

levensthein0Una vez tenemos la matriz inicial, empezaremos a realizar comparaciones por filas de cada caracter con el caracter de cada columna. En cada operación, obtenemos primero el peso y tres valores de los cuales deduciremos el menor de ellos como valor final y con un ejemplo lo veremos mejor.

Comenzamos comparando los caracteres, la letra “m” de murcielago (en la columna), lo comparamos con la “m” de muerdago (filas), si es el mismo caracter se le asigna un peso de 0 y si es diferente, le asignamos 1, en este caso como son iguales, le asignamos un 0. A continuación obtenemos los tres valores, el primero con la casilla superior sumándole al valor de esta un 1, en este caso 1+1=2, el siguiente con la casilla que está situada a su izquierda haciéndolo de igual modo, 1+1=2 y por último a la casilla superior izquierda, la que está situada en la diagonal a la que le sumaremos el valor del peso, 0+0=0; de estos tres valores, cogeremos el menor que en este caso es el 0. levensthein1

Vamos a por el siguiente, peso igual a 0, porque u es igual a u, por la izquierda, 0+1=1, la diagonal 1+0=1 y la superior 1+2=2, el mínimo de los tres es 1

levensthein2

continuando con las comparaciones y obtención del mínimo, quedaría nuestra matriz completa del siguiente modo:

levensthein3

El último valor, es la distancia de Levenshtein, en este caso es 5, si quisieramos ver la proporción de acierto, deberíamos obtener la la diferencia con la unidad de la relación entre la distancia y el número de caracteres del mayor en este caso, 1 – (5/10)=0,5 o 50%. La distancia de “murcielago” y “cerro muriano” es de 10, por tanto 1-(10/13)=0.2308 o del 23,08%.

El algoritmo implementado con lenguajes de programación no tiene ningún misterio para el que esté acostumbrado a las matrices, además en Wikipedia, podéis encontrar su implementación en diferentes lenguajes, por ejemplo en php ya tiene una función como tal.

vladimir-levenshtein¿Que utilidades podemos darle? por ejemplo palabras sugerentes en base a un diccionario o aproximaciones, corrección de ortografía, etc. y además, existen variantes del algoritmo como la de Damerau-Levenshtein, otros más complejos, algoritmos fonéticos.

Este algoritmo se lo debemos a Vladimir Levenshtein, un matemático ruso que lo desarrolló en 1965.

He hecho en C# una app de consola como muestra para obtener la matriz y en base a un diccionario, nos muestre sugerencia de palabra.  levensthein4

levensthein5

Semáforo II

Semáforo II
En esta entrega vamos a crear el semáforo. Aunque no lo parezca, pero un semáforo a pesar de su simplicidad visual, tiene aspectos bastante complejos y sobre todo cuando tienen que estar sincronizados o comunicados con otros, existen muchas matemáticas detrás de los modelos de tráfico de las grandes ciudades.

Sin darle más complejidad que la que necesitamos para nuestro proyecto final, que es un sistema de control de semáforos en una avenida para poder cruzarla de una sola vez sin pararnos, le asignaremos las siguientes características:

  • Tiempo en verde
  • Tiempo en amarillo
  • Tiempos en rojo
  • Tiempo de retardo para sincronziación de semáforos
  • Estado en el que se encuentra
  • Función de funcionamiento en manual o automático
  • Función de inicio automático
  • Función de parpadeo

En el diseño XAML, creamos unos objetos rectangle y le incluimos los controles de usuario creados en el post Semáforo I. El cambio de color lo podiamos haber hecho con converter, con propiedades, pero esta vez he optado por hacerlo con estilos y DataTrigger. A cada objeto LEDLight (luz del semáforo) le asigno un estilo genérico con algún efecto de fundido para que parezca más real; luego este estilo es heredado por otros tres estilos, uno por cada color donde los DataTrigger hacen su trabajo comprobando el estado del semáforo y activando la propiedad LightIsEnabled a Truesi cumple con la condición del DataTrigger. (Código al final del post)

Luego en el código de clase, le añado un DispatcherTime que nos hará las funciones de temporizador, creamos unas cuantas propiedades de dependencia para cumplir con nuestras características.
Tiene un método privado que será llamado desde el temporizador y este asignará el estado al semáforo y como consecuencia activando el color correspondiente.

        void changingState()
        {
            // Retardo inicial
            if (DelayTime > 0)
            {
                timer.Interval = new TimeSpan(0, 0, (int)DelayTime);
                DelayTime = 0;
                return;
            }

            // Parpadeando
            if (IsFlashingAmberLight)
            {
                if (State.Equals(LEDTrafficLightState.Yellow))
                {
                    State = LEDTrafficLightState.None;
                    timer.Interval = new TimeSpan(0, 0, 0, 0, 500);
                }
                else
                {
                    State = LEDTrafficLightState.Yellow;
                    timer.Interval = new TimeSpan(0, 0, 0, 1);
                }

                return;
            }

            // Cambio de color
            switch (State)
            {
                case LEDTrafficLightState.Red:
                    // SEMÁFORO EN VERDE
                    State = LEDTrafficLightState.Green;
                    
                    timer.Interval = new TimeSpan(0, 0, GreenTime);
                    break;
                case LEDTrafficLightState.Yellow:
                    // SEMÁFORO EN VERDE
                    State = LEDTrafficLightState.Red;
                    timer.Interval = new TimeSpan(0, 0, RedTime);
                    break;
                case LEDTrafficLightState.Green:
                    // SEMÁFORO EN AMBAR
                    State = LEDTrafficLightState.Yellow;
                    timer.Interval = new TimeSpan(0, 0, YellowTime);
                    break;
                default:
                    break;
            }
        }

y este el resultado instanciando varios semáforos en una ventana

            <controls:LEDTrafficLight x:Name="LEDTL1" Width="100" Height="300" GreenTime="4" RedTime="4" DelayTime="1" Margin="10"/>
            <controls:LEDTrafficLight x:Name="LEDTL2" Width="100" Height="300" GreenTime="5" RedTime="4" DelayTime="2" Margin="10"/>
            <controls:LEDTrafficLight x:Name="LEDTL3" Width="100" Height="300" GreenTime="5" RedTime="5" DelayTime="3" Margin="10"/>
            <controls:LEDTrafficLight x:Name="LEDTL4" Width="100" Height="300" GreenTime="5" RedTime="5" IsFlashingAmberLight="True" Margin="10"/>  

Video7
La próxima entrega, el sistema de control de semáforos

<UserControl x:Class="Controls.LEDTrafficLight"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:controls="clr-namespace:Controls"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="100">
    <Grid x:Name="grid">
        <Grid.Resources>
            <Style x:Key="LightStyle" TargetType="controls:LEDLight">
                <Setter Property="Margin" Value="5"/>
                <Style.Triggers>
                    <Trigger Property="LightIsEnabled" Value="True">
                        <Trigger.ExitActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation
                  Storyboard.TargetProperty="Opacity"
                  From="0" To="1" Duration="0:0:0.2" />
                                </Storyboard>
                            </BeginStoryboard>
                        </Trigger.ExitActions>
                        <Trigger.EnterActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation
                  Storyboard.TargetProperty="Opacity"
                  From="0" To="1" Duration="0:0:0.1" />
                                </Storyboard>
                            </BeginStoryboard>
                        </Trigger.EnterActions>
                    </Trigger>
                </Style.Triggers>
            </Style>
            <Style x:Key="RedLightStyle" TargetType="controls:LEDLight" BasedOn="{StaticResource LightStyle}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=controls:LEDTrafficLight},Path=State }" Value="{x:Static controls:LEDTrafficLight+LEDTrafficLightState.Red}">
                        <Setter Property="LightIsEnabled" Value="True"/>
                    </DataTrigger>

                </Style.Triggers>
            </Style>
            <Style x:Key="YellowLightStyle" TargetType="controls:LEDLight" BasedOn="{StaticResource LightStyle}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=controls:LEDTrafficLight},Path=State }" Value="{x:Static controls:LEDTrafficLight+LEDTrafficLightState.Yellow}">
                        <Setter Property="LightIsEnabled" Value="True"/>
                    </DataTrigger>

                </Style.Triggers>
            </Style>
            <Style x:Key="GreenLightStyle" TargetType="controls:LEDLight" BasedOn="{StaticResource LightStyle}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=controls:LEDTrafficLight},Path=State }" Value="{x:Static controls:LEDTrafficLight+LEDTrafficLightState.Green}">
                        <Setter Property="LightIsEnabled" Value="True"/>
                    </DataTrigger>

                </Style.Triggers>
            </Style>
            <Style x:Key="rectangleTraficLight" TargetType="Border">
                <Setter Property="Width" Value="{Binding ElementName=redEllipse, Path=ActualWidth}"/>
                <Setter Property="Height" Value="5"/>
                <Setter Property="Background" Value="Gray"/>
                <Setter Property="Margin" Value="1"/>
                <Setter Property="VerticalAlignment" Value="Top"/>
                <Setter Property="CornerRadius" Value="3"/>
            </Style>
            <Style x:Key="borderColor" TargetType="Border">
                <Setter Property="BorderBrush" Value="Gray"/>
                <Setter Property="BorderThickness" Value="2"/>
                <Setter Property="Margin" Value="2,0,2,2"/>
                <Setter Property="CornerRadius" Value="3"/>
            </Style>
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="100*"/>
            <RowDefinition Height="100*"/>
            <RowDefinition Height="100*"/>
        </Grid.RowDefinitions>
        <Border Grid.RowSpan="3" BorderBrush="DarkGreen" BorderThickness="5" Background="DarkGreen" Opacity=".5" CornerRadius="5"/>
        <Border Style="{StaticResource borderColor}" Grid.Row="0">
            <controls:LEDLight x:Name="RedLight" LEDLightBackgroundON="Red" Style="{StaticResource RedLightStyle}"/>
        </Border>
        <Border Style="{StaticResource borderColor}" Grid.Row="1">
            <controls:LEDLight x:Name="YellowLight"  LEDLightBackgroundON="Gold" Style="{StaticResource YellowLightStyle}"/>
        </Border>
        <Border Style="{StaticResource borderColor}" Grid.Row="2">
            <controls:LEDLight x:Name="GreenLight" Style="{StaticResource GreenLightStyle}"/>
        </Border>
    </Grid>
</UserControl>
namespace Controls
{
    /// <summary>
    /// Lógica de interacción para LEDTrafficLight.xaml
    /// </summary>
    public partial class LEDTrafficLight : UserControl
    {
        #region Constructor

        public LEDTrafficLight()
        {
            InitializeComponent();
            this.DataContext = this;
            timer.Tick += Timer_Tick;
            timer.IsEnabled = StartAuto;
        }

        #endregion

        #region Enum

        public enum LEDTrafficLightState
        {
            Red,
            Yellow,
            Green,
            None
        }

        #endregion

        #region DependencyProperties

        public int GreenTime
        {
            get { return (int)GetValue(GreenTimeProperty); }
            set
            {
                value = value > YellowTime ? value - YellowTime : YellowTime + 1;
                SetValue(GreenTimeProperty, value);
            }
        }

        public int RedTime
        {
            get { return (int)GetValue(RedTimeProperty); }
            set
            {
                if (value <= 0) value = 1;
                SetValue(RedTimeProperty, value);
            }
        }

        public int YellowTime
        {
            get { return (int)GetValue(YellowTimeProperty); }
            set { SetValue(YellowTimeProperty, value); }
        }

        public int DelayTime
        {
            get { return (int)GetValue(TimeProperty); }
            set { SetValue(TimeProperty, value); }
        }

        public bool IsFlashingAmberLight
        {
            get { return (bool)GetValue(IsFlickingProperty); }
            set
            {
                SetValue(IsFlickingProperty, value);
                State = LEDTrafficLightState.Yellow;
                // en 100 ms cambia a Parpadeo
                timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
            }
        }

        public bool IsManual
        {
            get { return (bool)GetValue(IsManualProperty); }
            set
            {
                SetValue(IsManualProperty, value);
                timer.IsEnabled = !value;
            }
        }

        public bool StartAuto
        {
            get { return (bool)GetValue(StarAutoProperty); }
            set
            {
                SetValue(StarAutoProperty, value);
                timer.IsEnabled = value;
            }
        }

        public LEDTrafficLightState State
        {
            get { return (LEDTrafficLightState)GetValue(StateProperty); }
            set { SetValue(StateProperty, value); }
        }

        #region DependecyProperties Register

        // Using a DependencyProperty as the backing store for StarAuto.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StarAutoProperty =
            DependencyProperty.Register("StartAuto", typeof(bool), typeof(LEDTrafficLight), new PropertyMetadata(true));

        // Using a DependencyProperty as the backing store for IsManual.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsManualProperty =
            DependencyProperty.Register("IsManual", typeof(bool), typeof(LEDTrafficLight), new PropertyMetadata(false));

        // Using a DependencyProperty as the backing store for IsFlicking.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsFlickingProperty =
            DependencyProperty.Register("IsFlashingAmberLight", typeof(bool), typeof(LEDTrafficLight), new PropertyMetadata(false));

        // Using a DependencyProperty as the backing store for State.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StateProperty =
            DependencyProperty.Register("State", typeof(LEDTrafficLightState), typeof(LEDTrafficLight), new PropertyMetadata(LEDTrafficLightState.Red));

        // Using a DependencyProperty as the backing store for Time.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TimeProperty =
            DependencyProperty.Register("DelayTime", typeof(int), typeof(LEDTrafficLight), new PropertyMetadata(0));

        // Using a DependencyProperty as the backing store for TimeYellow.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty YellowTimeProperty =
            DependencyProperty.Register("YellowTime", typeof(int), typeof(LEDTrafficLight), new PropertyMetadata(2));

        // Using a DependencyProperty as the backing store for TimeRed.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RedTimeProperty =
            DependencyProperty.Register("RedTime", typeof(int), typeof(LEDTrafficLight), new PropertyMetadata(7));

        // Using a DependencyProperty as the backing store for TimeGreen.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GreenTimeProperty =
            DependencyProperty.Register("GreenTime", typeof(int), typeof(LEDTrafficLight), new PropertyMetadata(7));

        #endregion

        #endregion

        #region Fields & Const & Event

        DispatcherTimer timer = new DispatcherTimer();

        #endregion


        #region Publics Method

        /// <summary>
        /// Inicia el semáforo cuando se encuentra en modo automático
        /// </summary>
        public void Start()
        {
            if (IsManual) return;
            timer.Start();
        }

        /// <summary>
        /// Detiene el semáforo
        /// </summary>
        public void Stop()
        {
            timer.Stop();
        }

        /// <summary>
        /// Cambia el semáforo a rojo
        /// </summary>
        public void OnRed()
        {
            State = LEDTrafficLightState.Red;
        }

        /// <summary>
        /// Cambia el semáforo a verde
        /// </summary>
        public void OnGreen()
        {
            State = LEDTrafficLightState.Green;
        }

        /// <summary>
        /// Cambia el semáforo a amarillo
        /// </summary>
        public void OnYellow()
        {
            State = LEDTrafficLightState.Yellow;
        }

        public void Test()
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Privates methods

        /// <summary>
        /// Cambia el estado del semáforo según el estado anterior
        /// </summary>
        void changingState()
        {
            // Retardo inicial
            if (DelayTime > 0)
            {
                timer.Interval = new TimeSpan(0, 0, (int)DelayTime);
                DelayTime = 0;
                return;
            }

            // Parpadeando
            if (IsFlashingAmberLight)
            {
                if (State.Equals(LEDTrafficLightState.Yellow))
                {
                    State = LEDTrafficLightState.None;
                    timer.Interval = new TimeSpan(0, 0, 0, 0, 500);
                }
                else
                {
                    State = LEDTrafficLightState.Yellow;
                    timer.Interval = new TimeSpan(0, 0, 0, 1);
                }

                return;
            }

            // Cambio de color
            switch (State)
            {
                case LEDTrafficLightState.Red:
                    // SEMÁFORO EN VERDE
                    State = LEDTrafficLightState.Green;
                    
                    timer.Interval = new TimeSpan(0, 0, GreenTime);
                    break;
                case LEDTrafficLightState.Yellow:
                    // SEMÁFORO EN VERDE
                    State = LEDTrafficLightState.Red;
                    timer.Interval = new TimeSpan(0, 0, RedTime);
                    break;
                case LEDTrafficLightState.Green:
                    // SEMÁFORO EN AMBAR
                    State = LEDTrafficLightState.Yellow;
                    timer.Interval = new TimeSpan(0, 0, YellowTime);
                    break;
                default:
                    break;
            }

        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            changingState();
        }

        #endregion
    }
}

Semáforo I

Semáforo I

Vamos a crear un sistema de control de semáforos. Un sistema de control de tráfico, debe coordinar semáforos conforme a la situación geográfica, prioridad de paso, alta circulación y sinfín de factores, en este caso vamos a crear uno sencillo que nos permita cruzar una avenida de una sola vez. Para ello debemos tener un sistema de control y semáforos lógicamente. La mayoría de los semáforos, como de todos es bien sabido, tienen 3 luces, una roja, otra ámbar y otra verde, algunos además tienen control de peatones e indicaciones auxiliares, pero nosotros vamos a crear uno sencillo, con sus tres luces y más adelante ya veremos si ampliamos sus funcionalidades.

Su funcionamiento básico es verde, pasa a amarillo esperando durante unos segundos pasando a rojo finalizando el bucle, vuelve al verde y comienza de nuevo. También puede estar parpadeando.

Pues empezando por lo básico, vamos a crear las luces y como estamos en 2017, vamos a crearlos de LED,s, ya que cambiar el color y añadir un degradado radial no es muy complicado, lo vamos a hacer un poco más difícil o más fácil según se vea.

He usado WPF para crear un control de usuario y dentro de este, he añadido un grid donde voy a distribuir los LED,s dándole un aspecto como el siguiente (el código lo adjunto al final del post)
semaforo01

Además, le he agregado algunas propiedades de dependencia en las que podamos cambiar el color de los LED cuando está encendida la luz, (en este caso yo las he puesto blancas), el color del LED cuando está apagada y el color de fondo cuando están de igual modo apagadas o encendidas. A continuación el código

        #region DependecyProperties

        public bool LightIsEnabled
        {
            get { return (bool)GetValue(LightIsEnabledProperty); }
            set { SetValue(LightIsEnabledProperty, value); }
        }

        public Brush LEDColorON
        {
            get { return (Brush)GetValue(LEDColorONProperty); }
            set { SetValue(LEDColorONProperty, value); }
        }

        public Brush LEDColorOFF
        {
            get { return (Brush)GetValue(LEDColorOFFProperty); }
            set { SetValue(LEDColorOFFProperty, value); }
        }

        public Brush LEDLightBackgroundOFF
        {
            get { return (Brush)GetValue(LEDLightBackgroundProperty); }
            set { SetValue(LEDLightBackgroundProperty, value); }
        }

        public Brush LEDLightBackgroundON
        {
            get { return (Brush)GetValue(LEDLightBackgroundONProperty); }
            set { SetValue(LEDLightBackgroundONProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LEDLightBackgroundONProperty =
            DependencyProperty.Register("LEDLightBackgroundON", typeof(Brush), typeof(LEDLight), new PropertyMetadata(Brushes.Lime));

        // Using a DependencyProperty as the backing store for LEDLightBackground.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LEDLightBackgroundProperty =
            DependencyProperty.Register("LEDLightBackgroundOFF", typeof(Brush), typeof(LEDLight), new PropertyMetadata(Brushes.Silver));

        // Using a DependencyProperty as the backing store for LEDColorOFF.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LEDColorOFFProperty =
            DependencyProperty.Register("LEDColorOFF", typeof(Brush), typeof(LEDLight), new PropertyMetadata(Brushes.LightGray));

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LEDColorONProperty =
            DependencyProperty.Register("LEDColorON", typeof(Brush), typeof(LEDLight), new PropertyMetadata(Brushes.White));

        // Using a DependencyProperty as the backing store for ON.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LightIsEnabledProperty =
            DependencyProperty.Register("LightIsEnabled", typeof(bool), typeof(LEDLight), new PropertyMetadata(false));

        #endregion

Y ¿como se enciende y se apaga? He incluido un par de DataTrigger que comprueban si el valor de la propiedad LightIsEnabled es verdadero y si es así asigna el valor de la propiedad LEDColorON a cada LED y el valor de la propiedad LEDLightBackgroundON al relleno de la ellipse interna. Si al crear una instancia de este, cambiamos esta propiedad, la luz se encenderá o se apagará.
Ahora solo nos queda crear una instancia desde un formulario y ver si funciona

Video5.gif
Fondo de color y LED,s blancos

Video6.gif

Fondo y LED,s de color

y las instancias desde XAML, quedarían así:


y el código del control de usuario. En la próxima entrega el semáforo.

<UserControl x:Class="Controls.LEDLight"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:controls="clr-namespace:Controls"
             mc:Ignorable="d" 
             d:DesignHeight="200" d:DesignWidth="200">
    <Grid x:Name="grid">
        <Grid.Resources>
            <!--ESTILO DE LED-->
            <Style x:Key="ellipseLED" TargetType="Ellipse">
                <Setter Property="Margin" Value="1"/>
                <Setter Property="HorizontalAlignment" Value="Stretch"/>
                <Setter Property="VerticalAlignment" Value="Stretch"/>
                <Setter Property="Fill" Value="{Binding LEDColorOFF}"/>
                <Setter Property="Grid.RowSpan" Value="2"/>
                <Setter Property="Grid.ColumnSpan" Value="2"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding LightIsEnabled}" Value="True">
                        <Setter Property="Fill" Value="{Binding LEDColorON}"/>
                    </DataTrigger>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=controls:LEDLight},Path=LEDLightInsideShape}" Value="{x:Static controls:LEDLight+LEDLightInsideShapeEnum.LeftArrow}"/>
                            <Condition Binding="{Binding LightIsEnabled}" Value="True"/>
                        </MultiDataTrigger.Conditions>
                        <Setter Property="Fill" Value="{Binding LEDColorON}"/>
                    </MultiDataTrigger>
                </Style.Triggers>
            </Style>
            <!--ESTILO DEL CIRCULO CENTRAL EXTERIOR-->
            <Style x:Key="centralEllipse" TargetType="Ellipse">
                <Setter Property="Fill" Value="Transparent"/>
                <Setter Property="Stroke" Value="DarkGray"/>
                <Setter Property="StrokeThickness" Value="5"/>
            </Style>
            <!--ESTILO DEL FONDO DEL CIRCULO INTERIOR-->
            <Style x:Key="centralEllipse2" TargetType="Ellipse">
                <Setter Property="Fill" Value="{Binding LEDLightBackgroundOFF}"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding LightIsEnabled}" Value="True">
                        <Setter Property="Fill" Value="{Binding LEDLightBackgroundON}"/>
                    </DataTrigger>
                </Style.Triggers>

            </Style>
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="10*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
            <ColumnDefinition Width="10*"/>
        </Grid.ColumnDefinitions>
        <!--Ellipse central. INTERIOR-->
        <Ellipse Grid.Row="0" Grid.ColumnSpan="27" Grid.RowSpan="28" Style="{StaticResource  centralEllipse2}"/>

        <!--Linea 0-->
        <Ellipse Grid.Row="1" Grid.Column="8" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="1" Grid.Column="10" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="1" Grid.Column="12" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="1" Grid.Column="14" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="1" Grid.Column="16" Style="{StaticResource ellipseLED}"/>
        
        <!--Linea 1-->
        <Ellipse Grid.Row="3" Grid.Column="5" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="3" Grid.Column="7" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="3" Grid.Column="9" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="3" Grid.Column="11" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="3" Grid.Column="13" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="3" Grid.Column="15" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="3" Grid.Column="17" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="3" Grid.Column="19" Style="{StaticResource ellipseLED}"/>
        
        <!--Linea 2-->
        <Ellipse Grid.Row="5" Grid.Column="4" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="5" Grid.Column="6" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="5" Grid.Column="8" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="5" Grid.Column="10" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="5" Grid.Column="12" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="5" Grid.Column="14" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="5" Grid.Column="16" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="5" Grid.Column="18" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="5" Grid.Column="20" Style="{StaticResource ellipseLED}"/>
        
        <!--Linea 3-->
        <Ellipse Grid.Row="7" Grid.Column="3" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="7" Grid.Column="5" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="7" Grid.Column="7" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="7" Grid.Column="9" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="7" Grid.Column="11" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="7" Grid.Column="13" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="7" Grid.Column="15" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="7" Grid.Column="17" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="7" Grid.Column="19" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="7" Grid.Column="21" Style="{StaticResource ellipseLED}"/>
        
        <!--Linea 4-->
        <Ellipse Grid.Row="9" Grid.Column="2" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="9" Grid.Column="4" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="9" Grid.Column="6" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="9" Grid.Column="8" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="9" Grid.Column="10" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="9" Grid.Column="12" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="9" Grid.Column="14" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="9" Grid.Column="16" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="9" Grid.Column="18" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="9" Grid.Column="20" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="9" Grid.Column="22" Style="{StaticResource ellipseLED}"/>
        
        <!--Linea 5-->
        <Ellipse Grid.Row="11" Grid.Column="1" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="11" Grid.Column="3" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="11" Grid.Column="5" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="11" Grid.Column="7" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="11" Grid.Column="9" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="11" Grid.Column="11" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="11" Grid.Column="13" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="11" Grid.Column="15" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="11" Grid.Column="17" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="11" Grid.Column="19" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="11" Grid.Column="21" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="11" Grid.Column="23" Style="{StaticResource ellipseLED}"/>
        
        <!--Linea 6-->
        <Ellipse Grid.Row="13" Grid.Column="2" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="13" Grid.Column="4" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="13" Grid.Column="6" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="13" Grid.Column="8" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="13" Grid.Column="10" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="13" Grid.Column="12" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="13" Grid.Column="14" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="13" Grid.Column="16" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="13" Grid.Column="18" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="13" Grid.Column="20" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="13" Grid.Column="22" Style="{StaticResource ellipseLED}"/>
        
        <!--Linea 7-->
        <Ellipse Grid.Row="15" Grid.Column="1" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="15" Grid.Column="3" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="15" Grid.Column="5" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="15" Grid.Column="7" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="15" Grid.Column="9" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="15" Grid.Column="11" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="15" Grid.Column="13" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="15" Grid.Column="15" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="15" Grid.Column="17" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="15" Grid.Column="19" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="15" Grid.Column="21" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="15" Grid.Column="23" Style="{StaticResource ellipseLED}"/>
        
        <!--Linea 8-->
        <Ellipse Grid.Row="17" Grid.Column="2" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="17" Grid.Column="4" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="17" Grid.Column="6" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="17" Grid.Column="8" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="17" Grid.Column="10" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="17" Grid.Column="12" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="17" Grid.Column="14" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="17" Grid.Column="16" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="17" Grid.Column="18" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="17" Grid.Column="20" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="17" Grid.Column="22" Style="{StaticResource ellipseLED}"/>
        
        <!--Linea 9-->
        <Ellipse Grid.Row="19" Grid.Column="3" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="19" Grid.Column="5" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="19" Grid.Column="7" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="19" Grid.Column="9" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="19" Grid.Column="11" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="19" Grid.Column="13" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="19" Grid.Column="15" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="19" Grid.Column="17" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="19" Grid.Column="19" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="19" Grid.Column="21" Style="{StaticResource ellipseLED}"/>
        
        <!--Linea 10-->
        <Ellipse Grid.Row="21" Grid.Column="4" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="21" Grid.Column="6" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="21" Grid.Column="8" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="21" Grid.Column="10" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="21" Grid.Column="12" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="21" Grid.Column="14" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="21" Grid.Column="16" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="21" Grid.Column="18" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="21" Grid.Column="20" Style="{StaticResource ellipseLED}"/>
        
        <!--Linea 11-->
        <Ellipse Grid.Row="23" Grid.Column="5" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="23" Grid.Column="7" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="23" Grid.Column="9" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="23" Grid.Column="11" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="23" Grid.Column="13" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="23" Grid.Column="15" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="23" Grid.Column="17" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="23" Grid.Column="19" Style="{StaticResource ellipseLED}"/>
        
        <!--Linea 12-->
        <Ellipse Grid.Row="25" Grid.Column="8" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="25" Grid.Column="10" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="25" Grid.Column="12" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="25" Grid.Column="14" Style="{StaticResource ellipseLED}"/>
        <Ellipse Grid.Row="25" Grid.Column="16" Style="{StaticResource ellipseLED}"/>
        
        <!--Ellipse central. EXTERIOR-->
        <Ellipse Grid.Row="0" Grid.ColumnSpan="27" Grid.RowSpan="28" Style="{StaticResource centralEllipse}"/>
    </Grid>
</UserControl>

ForEach

ForEach

Una colección que implementa la interfaz IEnumerable contiene un método GetEnumerator y este a su vez implementa la interfaz IEnumerator que contiene la propiedad Current y los métodos MoveNext y Reset. Esto va a permitir a la instrucción foreach iterar por la colección.

Así, si generamos una colección de 20.000.000 de números enteros y queremos efectuar una operación por cada uno de ellos, en este caso el inverso del producto de la raiz cuadrada por el logaritmo neperiano y luego realizar la suma sobre cada resultado, podemos iterar con una instrucción foreach,

static int n = 20000000;
static List list = Enumerable.Range(1, n).ToList();

static double forEachStandar()
        {
            var result = 0.0;

            foreach (var item in list)
            {
                result += 1 / Math.Sqrt(item) * Math.Log(item);
            }
            return result;
        }

Una colección además contiene un método ForEach, el cual es accesible mediante una expresión Lambda, asignando la acción que vamos a realizar en cada uno de los elementos.

        static double forEachLambda()
        {
            var result = 0.0;
            list.ForEach(n => result += 1 / Math.Sqrt(n) * Math.Log(n));
            return result;
        }

Podemos aprovechar al máximo los recursos disponibles y es iterando en paralelo por los elementos de la colección pudiendo realizar cálculos simultáneos. Este medio debe ser tratado con cuidado, puesto que si el orden interviene en el resultado final, esta herramienta no sería útil, y en todo caso, los resultados no serían los esperados; así mismo, siempre hay que replantearse el uso en paralelo. Para usarlo, podemos hacerlo mediante la clase Parallel
Parallel.ForEach(list, k => result += 1 / Math.Sqrt(n) * Math.Log(n));
o mediante el método AsParallel de la colección.

foreach (var item in list.AsParallel())
{
result += 1 / Math.Sqrt(item) * Math.Log(item);
}

Aprovechando el artículo sobre LINQ publicado en Wakicode, es posible efectuar cálculos sobre elementos de la colección mediante sintáxis de consulta en Linq. En el siquiente ejemplo, efectuamos el cálculo sobre cada uno de los elementos asignádolo a la variable sum y posteriormente efectuar la suma por cada uno de los elementos

static double queryLinq()
        {
            var query = (from u in list
                        select new { sum = 1 / Math.Sqrt(u) * Math.Log(u) }).Sum(k=>k.sum);

            return query;
        }

pero existe un método mejor y es que toda colección contiene el método Sum, y podremos entonces hacer lo siguiente

        static double collectionSum()
        {
            return list.Sum(k => 1 / Math.Sqrt(k) * Math.Log(k));
        }

Bien, todavía habría alguna otra forma de iterar sobre colecciones, pero con estas nos basta y ahora vamos a ver como se comportan en base a su rendimiento y para ello he creado cuatro hilos, de los cuales cada uno generará la suma de los elementos calculados de la colección, pero cada uno con su método y una vez que terminen todos, que nos muestre el tiempo invertido en los cuatro procesos y cual ha sido el más eficiente. Lanzaremos los procesos por este orden, foreach estándar, expresión foreach lambda, Linq sintáxis de consulta y por último la suma directa sobre la colección mediante expresión lambda.

        static void inBackGround()
        {
            string text = "";
            List cronos = new List();

            Stopwatch stw = new Stopwatch();
            Stopwatch stw1 = new Stopwatch();
            Stopwatch stw2 = new Stopwatch();
            Stopwatch stw3 = new Stopwatch();
            Stopwatch stw4 = new Stopwatch();

            cronos.Add(new KeyValuePair(stw1, "For Each Estandar"));
            cronos.Add(new KeyValuePair(stw2, "For Each Lambda"));
            cronos.Add(new KeyValuePair(stw2, "Query LINQ"));
            cronos.Add(new KeyValuePair(stw2, "Colección.Sum()"));

            stw.Start();
            Task[] tasks;

            var t1 = Task.Factory.StartNew(() =>
            {
                stw1.Start();
                text = string.Format("For Each Estandar. n = {0}. Resultado = {1}. Tiempo del proceso: {2}", n, forEachStandar(), stw1.Elapsed);
                stw1.Stop();
                Console.WriteLine(text);
            });

            var t2 = Task.Factory.StartNew(() =>
            {
                stw2.Start();
                text = string.Format("For Each Lambda. n = {0}. Resultado = {1}. Tiempo del proceso: {2}", n, forEachLambda(), stw2.Elapsed);
                stw2.Stop();
                Console.WriteLine(text);
            });

            var t3 = Task.Factory.StartNew(() =>
            {
                stw3.Start();
                text = string.Format("Query LINQ. n = {0}. Resultado = {1}. Tiempo del proceso: {2}", n, queryLinq(), stw3.Elapsed);
                stw3.Stop();
                Console.WriteLine(text);
            });

            var t4 = Task.Factory.StartNew(() =>
            {
                stw4.Start();
                text = string.Format("Colección.Sum(). n = {0}. Resultado = {1}. Tiempo del proceso: {2}", n, collectionSum(), stw4.Elapsed);
                stw4.Stop();
                Console.WriteLine(text);
            });

            tasks = new Task[] { t1, t2, t3, t4 };

            Task.WaitAll(tasks);
            Console.WriteLine(string.Format("Los cuatro procesos han terminado. Tiempo: {0}",stw.Elapsed));
            var theBest = cronos.Where(k => k.Key.Elapsed == cronos.Min(m => m.Key.Elapsed)).FirstOrDefault();
            Console.WriteLine(string.Format("El más efectivo ha sido {0} con un tiempo de {1}",theBest.Value, theBest.Key.Elapsed));

            Console.Read();

        }

y ahora el resultado en el que el ganador en cuanto a rendimiento es el método For Each Lambda por muy poco sobre la suma sobre la colección, el foreach estándar y el menos eficiente la consulta Linq.

foreach.png
El fin del artículo, para un problema podemos usar distintos métodos que producen el mismo efecto o resultado, pero debemos escoger el mejor de ellos en cuanto a eficiencia y rendimiento. Además, necesitamos conocer todos los métodos, no podemos escoger o seleccionar si no tenemos o no conocemos opciones.

Hilando

Hilando

Desde .NET 4, los hilos en C# se convirtieron en una tarea de lo más fácil. En este post vamos a realizar una par de muestras, una prueba sencilla donde vamos a ejecutar una tarea que usa mucho tiempo en terminarse como es la obtención de π(x) o la cantidad de números primos menores que un número.Vamos a usar una función lambda para obtener el codiciado π(x) y para ello vamos a usar la clase Enumerable del espacio System.Linq y un método llamado Range para obtener un conjunto de números enteros entre dos rangos numerable.Range(2, max), contamos los elementos con el predicado con el que obtengamos todos los números entre el rango de 2 y la raiz del número Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1) que cumplan la condición de tener un módulo mayor que 0 All(i => n % i > 0))o lo que es lo mismo todos los números que no son múltiplos.

Enumerable.Range(2, max).Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0))

Linq es bestial, ¿no?
Si le damos un valor alto a la variable max com por ejemplo 10.000.000, la tarea dejará la interfaz bloqueada y por tanto tendremos que crear un hilo para evitar este problema y aquí viene lo bueno de la clase Task.

Esta clase tiene un método Factory.StartNew al que se le pasa un método como delegado, el cual se ejecutará de forma asincrónica devolviendo la tarea iniciada, es decir no devuelve un valor sino la tarea Task en si con sus propiedades y métodos.

Task.Factory.StartNew(() => TareaMuyCostosa());
En nuestro código vamos a ver solo dos aspectos de los muchos de los que podemos extraerle a Task, uno es la propiedad IsCompleted y otra sería el método Wait.
Nuestra aplicación como he dicho, generará números primos en una tarea y mientras que no esté finalizada, mostrará un mensaje indicando el tiempo empleado y una vez que finalice, mostrará el resultado que lo mostraremos de dos modos, uno esperando a que finalice y otro con el método ContinueWith.

La primera

static void primeNumbersCount(int max)
        {
            Stopwatch stw = new Stopwatch();
            stw.Start();
            int sec = 0;
            var piXTask = Task.Factory.StartNew(() => Enumerable.Range(2, max).Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));

            var calculando = "Calculando ";
            while (!piXTask.IsCompleted || piXTask.IsFaulted || piXTask.IsCanceled)
            {
                Thread.Sleep(1000);
                Console.Clear();
                Console.WriteLine(calculando.PadRight(sec % 5 + 11,'.'));
                Console.WriteLine(string.Format("Tiempo empleado: {0} sengundos", sec++));
            }

            piXTask.Wait();

            string text = string.Format("Calculo de {0} números primos menores de {1}", piXTask.Result, max);
            Console.WriteLine(text);
            Console.WriteLine(string.Format("Tiempo empleado: {0}", stw.Elapsed));
            Console.Read();
        }

En este código, vemos que se lanza la tarea, luego pasamos a un bucle del que debemos asegurarnos que saldremos algún día (esta opción no la aconsejo, pero para mostrar que la app se queda en sus cosas mientras la tarea se está realizando, está bien), mediante la propiedad IsCompleted, IsFaulted o IsCanceled, es decir cuando pase alguna de estas cosas, sale del bucle.
Luego pasamos al método Wait, que lo que hace es lo mismo que el bucle, hasta que la tarea no haya finalizado, espera y no sobrepasa a la siguiente línea de código. Una vez acabado, muestra el resultado con la propiedad Result del tipo int, porque es el resultado que entrega el delegado que lanzó. Y el resultadotask03task01

Pero para mi, en este caso usaría lo mismo pero con el método ContinueWith.

static void primeNumbersCount(int max)
        {
            Stopwatch stw = new Stopwatch();
            stw.Start();
            int sec = 0;
            var piXTask = Task.Factory.StartNew(() => Enumerable.Range(2, max).Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
            var calculando = "calculando ";
            piXTask.ContinueWith((p) =>
            {
                string text = string.Format("Calculo de {0} números primos menores de {1}", piXTask.Result, max);
                Console.WriteLine(text); Console.WriteLine(string.Format("Tiempo empleado: {0}", stw.Elapsed));
            });
            Console.WriteLine("Esto no ha terminado...");
            Thread.Sleep(1000); sec++;
            while (!piXTask.IsCompleted)
            {
                Thread.Sleep(1000); Console.Clear();
                Console.WriteLine(calculando.PadRight(sec % 5 + 11, '.'));
                Console.WriteLine(string.Format("Tiempo empleado: {0} segundos", sec++));
            }
          }

Aquí podemos apreciar que se lanza la tarea, vemos el método ContinueWith que contiene código en su interior mediante otro delegado y posteriormente el código ese que hemos creado para mostrar cosas mientras la tarea se realiza. Al ejecutarlo, se lanza la tarea y se obvia el código del método ContinueWith en cuestión, continua y pasa al bucle, una vez que ha terminado la tarea, continua con lo que le queda dentro del bloque ContinueWith y se acabó, ¿fácil? facilísimo!!

Video4
Ya veremos en otra ocasión el uso de Arrays de Task, esperas, sincronías entre ellos y alguna otra cosa más.