Estrategias de código portable : #2 Código Vinculado | C#

Continuamos con esta serie de artículos, ahora abordaremos el mismo requerimiento que revisamos en el artículo anterior pero nuestra solución será haciendo uso de una característica llamada código vinculado.

Código fuente de esta serie de artículos

JuanKRuiz GitHub

El código fuente completo de esta seria de artículos se encuentra disponible en GitHub, incluye proyectos de Apps con todos los casos expuestos.

https://github.com/JuanKRuiz/Estrategias-de-Codigo-Portable

Código Vinculado

El código vinculado es una funcionalidad de Visual Studio que nos permite incorporar en un proyecto código fuente creado en un archivo por fuera de este. La particularidad es que este archivo no es una copia del archivo original, es el mismo archivo!

Esto es una característica propia de Visual Studio es decir del compilador.

Los proyectos de la solución están de la siguiente forma:

  • Windows 8.1
  • Windows Phone [Silverlight] 8.1

Implementaremos esta estrategia de código portable por lo que primero crearemos la App para WinRT.

Al cargar la Page principal programamos el evento Load y allí hacemos la lógica necesaria para guardar en los settings.

A continuación veremos la versión más simple de programar este requerimiento en cada una de las plataformas:

Código WinRT

//LocalSettingsManager.cs
using System.Diagnostics;  
using Windows.Storage;

namespace WinRTApp_Shared_IF.Manager  
{
    public class LocalSettingsManager
    {
        public void Set(string key, string value)
        {
            ValidateSecurity();
            ApplicationData.Current.LocalSettings.Values[key] = value;
            Audit();
        }

        public void ValidateSecurity()
        {
            //TODO a lot of things
        }

        public void Audit()
        {
            Debug.WriteLine("Parameter Set on WinRT");
            //TODO a lot of things
        }
    }
}
//MainPage.xaml.cs [fragment]
private void Page_Loaded(object sender, RoutedEventArgs e)  
{
    var lsm = new LocalSettingsManager();
    lsm.Set("Last start:", DateTime.Now.ToString());
}

Ahora vamos a la solución de Windows Phone y adicionamos el archivo LocalSettingsManager.cs creado en WinRT, pero lo hacemos con la opción que aparece en la siguiente figura:

Adicionar como código vinculado

Al hacerlo nos damos cuenta que el archivo es adicionado a la solución de Windows Phone pero tiene un glifo distintivo:

Glifo de código vinculado

Luego adicionamos los cambios correspondientes en el evento Loaded de la Page en Windows Phone.

//MainPage.xaml.cs [fragment]
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)  
{
    var lsm = new LocalSettingsManager();
    lsm.Set("Last start:", DateTime.Now.ToString());
}

Al ejecutar la App obtenemos lo siguiente:

Output

Parameter Set on WinRT  

Esto claramente está mal, acabamos de ejecutar la App en Windows Phone, nuestro código es portable pero hay una parte que no funciona adecuadamente en una de las plataformas, como solucionar este problema?

Hay dos formas de solucionarlo y las exploraremos a continuación:

  • Compilación Condicional
  • Clases y métodos parciales

Método 1: Compilación Condicional

Para esta solución haremos uso de las directivas de compilación que identifican si una App es de WinRT o de Windows Phone, existen muchas directivas diferentes pero puntualmente estas estan definidas en las propiedades de cada proyecto.

Vamos a las propiedades de cada proyecto y en la pestaña Build observamos los simbolos condicionales de compilación.

WinRT

Símbolos de compilación para WinRT

Windows Phone

Símbolos de compilación para Windows Phone

Esto no necesitas hacerlo todas las veces, solo lo ilustro aquí para que aprendas de donde salen las directivas.


Debemos identificar el código que cambia:

Debug.WriteLine("Parameter Set on WinRT");  

Pero tengamos en cuenta que esta línea está en el método Audit() y allí mismo dice que se hacen muchas cosas más, como sucederia en un App del mundo real.

public void Audit()  
{
    Debug.WriteLine("Parameter Set on WinRT");
    //TODO a lot of things
}

Así que la parte que cambia es el método Audit()

Vamos al proyecto de WinRT y 'envolvemos' el método audit con la siguiente directiva condicional:

#if WINDOWS_APP
        public void Audit()
        {
            Debug.WriteLine("Parameter Set on WinRT");
            //TODO a lot of things
        }
#endif

Esto le indica al compilador que esta fracción de código solo debe tenerse en cuenta si el símbolo WINDOWS_APP esta presente, como vimos este símbolo está presente en las Apps para WinRT.

Cerramos el archivo y ahora lo abrimos pero desde la solución de Windows Phone, allí nos damos cuenta que el método audit aparece sombreado:

Código sombreado por directiva de compilación

Teniendo el archivo abierto desde el proyecto de Windows Phone creamos el método Audit() con todo el código necesario, adicionalmente haremos que este fragmento de código solo sea compilado en Windows Phone para lo cual utilizaremos de nuevo la directiva de compilación con el símbolo correspondiente:

#if WINDOWS_APP
        public void Audit()
        {
            Debug.WriteLine("Parameter Set on WinRT");
            //TODO a lot of things
        }
#elif WINDOWS_PHONE
        public void Audit()
        {
            Debug.WriteLine("Parameter Set on Windows Phone");
            //TODO a lot of things
        }
#endif

Si abrimos el archivo desde WinRT veremos como las porciones de código no tenidas en cuenta son sombreadas, mientras que las porciones que aplican para la plataforma son tenidas en cuenta.

Código sombreado por directiva de compilación

Si ahora ejecutamos las Apps de WinRT y Windows Phone obtenemos:

Output

Parameter Set on WinRT  
Parameter Set on Windows Phone  

Las directivas condicionales pueden usarse en diversas situaciones entre ellas:

  • Métodos completos
  • Líneas de código independientes
  • Definición de parámetros dentro de un método
  • Sentencias using

Prácticamente cualquier porción de código puede ser sometida a compilación condicional.

Nota

Utilizar directivas condicionales es sencillo, pero en ocasiones debes usarla de manera intensa en cada archivo de código y es allí donde las cosas se complican en especial porque el código se torna muy dificil de entender y mantener.

Este método tiene un riesgo asociado, imagina que te piden hacer una modificación en el proyecto de Windows Phone, tu realizas la modificación y sin fijarte has modificado y mal logrado el código para WinRT.

No hay forma de darte cuenta del error pues al compilar y realizar todas las pruebas en el proyecto de Windows Phone todo funciona sin problema.

Meses después otra persona tiene un requerimiento para WinRT y apenas abre la solución se da cuenta que esta nisiqueira compila e incluso le falta una porción significativa de código.

Método 2: Clases y métodos parciales

Otra estrategia util para utilizar código vinculado es el uso de clases y métodos parciales, esta característica del lenguaje C# y para hacer uso de ella se debe utilizar la palabra clave partial.

partial utilizado en las directivas de acceso de la clase indica que existe otra parte de la clase definida en otro archivo.

partial utilizando en la directiva de acceso de un método implica que el método esta definido en un archivo pero su cuerpo y su lógica esta en un archivo diferente con la misma clase parcial.


Volvemos al estado inicial de la solución.

Código WinRT

//MainPage.xaml.cs [fragment]
private void Page_Loaded(object sender, RoutedEventArgs e)  
{
    var lsm = new LocalSettingsManager();
    lsm.Set("Last start:", DateTime.Now.ToString());
}

Código Windows Phone

//MainPage.xaml.cs [fragment]
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)  
{
    var lsm = new LocalSettingsManager();
    lsm.Set("Last start:", DateTime.Now.ToString());
}

Creamos primero la clase LocalSettingsManager en el proyecto de WinRT, la marcamos como partial, ya tenemos claro que la parte que cambia es el método Audit() así que creamos únicamente la definición del método (llamada habitualmente firma o signature):

//LocalSettingsManager.cs
using Windows.Storage;

namespace WinRTApp_Shared_Partial.Manager  
{
    public partial class LocalSettingsManager
    {
        public void Set(string key, string value)
        {
            ValidateSecurity();
            ApplicationData.Current.LocalSettings.Values[key] = value;
            Audit();
        }

        public void ValidateSecurity()
        {
            //TODO a lot of things
        }

        partial void Audit();
    }
}

Ahora creamos un nuevo archivo que para fines prácticos llamaremos LocalSettingsManager.WinRT.cs, el nombre del archivo no es relevasnte desde l punto de vista de programación, pero es conveniente seguir alguna norma de nomenclatura en estos casos.

En este archivo creamos la otra parte de la clase parcial, y en esta definimos el contenido del método Audit().

//LocalSettingsManager.WinRT.cs
using System.Diagnostics;

namespace WinRTApp_Shared_Partial.Manager  
{
    partial class LocalSettingsManager
    {
        partial void Audit()
        {
            Debug.WriteLine("Parameter Set on WinRT");
            //TODO a lot of things
        }
    }
}

Ahora en el proyecto de Windows Phone agregamos LocalSettingsManager.cs como archivo vínculado y adicionalmente creamos un nuevo archivo LocalSettingsManager.WinPhone.cs dónde definimos la clase parcial con el código para Windows Phone.

ATENCIÓN: Un error muy frecuente en este punto es crear la nueva clase parcial en un namespace diferente, en el ejemplo esta debe ser creada en el mismo namespace de la clase en el proyecto WinRT.
//LocalSettingsManager.WinPhone.cs
using System.Diagnostics;

namespace WinRTApp_Shared_Partial.Manager  
{
    partial class LocalSettingsManager
    {
        partial void Audit()
        {
            Debug.WriteLine("Parameter Set on Windows Phone");
            //TODO a lot of things
        }
    }
}
Eso es todo ahora ejecutamos las Apps y este es el resultado ####Output
Parameter Set on WinRT  
Parameter Set on Windows Phone  

Conclusiones

La estrategia de portabilidad con base en el código vinculado permite crear rápidamente y con baja complejidad técnica código portable.

De acuerdo a las circunstacias puede ser más o menos conveniente usar alguna de las estrategias complementarias mostradas, sin embargo considero que se debe favorecer en la medida de lo posible el uso de clases y métodos parciales por ser una alternativa que genera código más fácil de mantener.

Espero sus comentarios y no duden en compartirlo ;)

Comparte este artículo

comments powered by Disqus