[Curso] WPF para programadores de Windows Forms 9 : Templates

[Curso] WPF para programadores de Windows Forms 9 : Templates

En el post anterior comentábamos como en WPF existen el árbol visual y el árbol lógico que nos permite definir como un control se comporta y como se dibuja en la pantalla, pues bien lo que vamos a ver ahora es como se pueden modificar el árbol visual y cómo podemos crear un árbol visual para una clase que no tiene árbol visual.

Todas los tipos de plantillas heredan de la misma clase, FrameworkTemplate que permite definir plantillas para diferentes tipos de controles.

image

Si nos fijamos en el diagrama, tenemos tres tipos de plantillas que definir:

· DataTemplate: permite definir una plantilla de datos, es decir asociar un árbol visual a una clase o tipo. Podemos definir cómo se va a visualizar la clase Employee.

· ControlTemplate: permite cambiar la plantilla visual de un control de WPF.

· ItemsPanelTemplate: permite cambiar la platilla de un control que es una lista de elementos, como por ejemplo un ListBox.

· HierarchicalDataTemplate: permite definir una plantilla de datos para una estructura jerárquica, como por ejemplo un árbol y asociar este a un TreeView.

DataTemplate

La clase DataTemplate permite definir una plantilla visual para un tipo cualquiera. Normalmente las plantillas se definen en XAML. Si tenemos un tipo como este:

public class Employee
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Company { get; set; }
}

Si lo establecemos como contenido de algún control WPF lo que hará será llamar al ToString() de este objeto para dibujarlo, podemos sobrescribir ToString() para darle otro sentido pero no podemos personalizar como el objeto se dibuja en pantalla.

Lo que podemos hacer es definir su plantilla visual mediante la clase DataTemplate en xaml, normalmente vamos a tener que definir nuestra DataTemplate en un diccionario de recursos o podemos hacerlo directamente en la propiedad ItemTemplate de algunos controles.

<DataTemplate DataType="{x:Type local:Employee}">
   <StackPanel>
       <TextBlock Text="{Binding Path=Name}" />
   </StackPanel>
</DataTemplate>

Como se puede observar en este ejemplo hemos definido un DataTemplate dentro del diccionario de recursos de Window, pero no hemos establecido la propiedad x:Key necesaria para poder acceder a ese valor en el diccionario, sino que lo único que hemos hecho es definir la propiedad DataType de la clase DataTemplate. Al hacer esto WPF automáticamente utilizará esta plantilla para dibujar el tipo Employee cuando se lo encuentre dentro del ámbito de la ventana. Esto es extremadamente útil, porque podemos definir esta plantilla en un ResourceDiccionary a nivel de Application y hacer que esté disponible para toda la aplicación.

Para asociar propiedades de la clase para mostrarla se realiza con un binding como se puede ver en el TextBlock, no quiero centrarme en eso ahora sino simplemente mostrar cuales son las capacidades de personalización de la DataTemplate.

Si hubiéramos utilizado un ContentControl para dibujar nuestro objeto Employe podríamos haber definido la plantilla directamente en la propiedad ContentTemplate, sin necesidad de asociar el DataType o establecer el x:Key.

<ContentControl>
    <ContentControl.ContentTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="{Binding Path=Name}" />
            </StackPanel>
        </DataTemplate>
    </ContentControl.ContentTemplate>
</ContentControl>

ControlTemplate

ControlTemplate nos permite definir o redefinir la plantilla de un control, es decir de cualquier control que herede de Control, que es donde está definida la propiedad Template. La manera de proceder es igual, para un Button tenemos:

<ControlTemplate TargetType="{x:Type Button}" x:Key="ButtonControlTemplate">
    <Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome" >
        <Border BorderThickness="3" BorderBrush="red">
            <ContentPresenter />
        </Border>
    </Microsoft_Windows_Themes:ButtonChrome>
</ControlTemplate>

Lo que hemos hecho en este ejemplo es redefinir la plantilla de Button para tener esto:

clip_image004

Como se puede observar lo que tenemos en pantalla no se parece a un botón, no tiene el aspecto que esperamos de un botón, pero si preguntamos su tipo es un Button, así que lo que hemos conseguido es modificar su árbol visual sin necesidad de heredad de Button y generar un tipo nuevo, simplemente cambiado su plantilla.

Este cambio de plantilla normalmente viene integrado en un estilo (Style) pero eso lo veremos en otro post.

ItemsPanelTemplate

Esta clase permite definir la plantilla para un control de tipo ItemsControl, como por ejemplo TreeView, TabControl, etc. En este ejemplo vamos a cambiar la plantilla del ListBox para hacerlo con UniformGrid.

<ItemsPanelTemplate x:Key="ItemsPanel">
    <UniformGrid IsItemsHost="True">
    </UniformGrid>
</ItemsPanelTemplate>

Aquí podemos ver el resultado:

clip_image006clip_image008

Las posibilidades son infinitas, pues podemos personalizar cualquier control como nosotros queramos sin necesidad de tener que tocar una sola línea de código simplemente con un poco de xaml.

HierarchicalDataTemplate

Esta plantilla basada en un DataTemplate permite generar plantillas jerárquicas para nuestros tipos de datos, como está pensada para usarse con un tipo de datos, primero vamos a definir un tipo de dato en árbol y generar algunos elementos.

public class Node
{
    public string Name { get; set; }
    public ObservableCollection<Node> Items { get; set; }

    public Node()
    {
        Items = new ObservableCollection<Node>();
    }
}

Esta clase Node contiene un nombre una colección de elementos y una colección de elementos del mismo tipo en Items. Podemos definir una plantilla jerárquica como esta:

<HierarchicalDataTemplate ItemsSource="{Binding Path=Items}" x:Key="NodeTemplate">
    <TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>

Tenemos que definir cuál va a ser la propiedad que se va a usar como origen de datos para los Items que se van a generar, la propiedad ItemsSource nos permite definir mediante un Binding que la propiedad Items, de nuestra clase Node, será la encargada de generar los elementos hijos. Por cada uno de las clases Node que nos encontremos vamos a generar un TextBlock en el que vamos a asociar la propiedad Text del control, con un Binding, a la propiedad Name de nuestra clase Node.

Ahora tenemos que generar algunos elementos de ejemplo:

private Node GenerateRandomNodes()
{
    Node n = new Node();
    n.Name = "Raiz";
    for (int i = 0; i < 20; i++)
    {
        Node tmp = new Node();
        tmp.Name = i.ToString();
        n.Items.Add(tmp);
        for (int x = 0; x < 10; x++)
        {
            Node tmp2 = new Node();
            tmp2.Name = string.Format("{0} - {1}", i, x);
            tmp.Items.Add(tmp2);
        }
    }
    return n;
}

Una vez ejecutado este es el resultado:

clip_image010

Resumiendo

Como se puede observar una de las cosas buenas de tener un árbol visual y un lógico es que podemos redefinir como nuestro control o clase se visualiza en pantalla simplemente generando una plantilla. Las plantillas son el mecanismo natural por el cual se pueden personalizar los controles en WPF y son la principal herramienta de los diseñadores para personalizar.

Avanzado

Para terminar el artículo me gustaría hablar sobre un tema que a todos se nos plantea en algún momento, generar plantillas de manera dinámica, por código. En WPF, se pueden generar plantillas dinámicamente, pero el tema es algo complicado y abstracto. Veamos un ejemplo.

Vamos a intentar definir una plantilla dinámica para nuestro tipo de dato Employee por código, si empezamos a generar código vemos que podemos generar una instancia de la clase DataTemplate pasando por parametro el tipo del objeto al que queremos generar la plantilla, la propiedad VisualTree de tipo FrameworkElementFactory nos permite definir como queremos que sea nuestra plantilla.

Todo el trabajo está en esta clase FrameworkElementFactory, para definir los elementos tenemos que especificar cómo se construirán, así que siguiendo nuestro ejemplo de DataTemplate, en el que tenemos un StackPanel y dentro un TextBlock así es como se definiría en código:

   1: private DataTemplate GenerateDataTemplate()
   2: {
   3:     DataTemplate dt = new DataTemplate(typeof(Employee));
   4:     FrameworkElementFactory stackPanel = new FrameworkElementFactory();
   5:     stackPanel.Type = typeof(StackPanel);
   6:     stackPanel.SetValue(StackPanel.BackgroundProperty, new SolidColorBrush(Colors.Yellow));
   7:     FrameworkElementFactory text = new FrameworkElementFactory(typeof(TextBlock));
   8:     text.SetBinding(TextBlock.TextProperty, new Binding("Name"));
   9:     stackPanel.AppendChild(text);
  10:     text = new FrameworkElementFactory(typeof(TextBlock));
  11:     text.SetBinding(TextBlock.TextProperty, new Binding("Company"));
  12:     stackPanel.AppendChild(text);
  13:     text = new FrameworkElementFactory(typeof(TextBlock));
  14:     text.SetBinding(TextBlock.TextProperty, new Binding("Age"));
  15:     stackPanel.AppendChild(text);
  16:     dt.VisualTree = stackPanel;
  17:     return dt;
  18: }

La idea es establecer en cada uno de los FrameworkElementFactory las DependencyProperties, Bindings y Childrens que definen a un control.

El código fuente lo podéis descargar de aquí http://www.luisguerrero.net/downloads/templates.zip

Saludos. Luis.

[Curso] WPF para programadores de Windows Forms 8

Ha pasado mucho tiempo desde mi último post de esta serie, el trabajo y haberme mudado de casa no me han permitido escribir este tipo de posts. Ahora un poco más relajado podemos continuar con esta serie de post sobre como programar WPF para los programadores de Windows Forms.

En este post vamos a hablar sobre los dos tipos de arboles de controles que hay que WPF: el árbol visual (Visual Tree) y el árbol lógico (Logical Tree). Estos dos conceptos son muy importantes para un programador de WPF porque permite conocer muy bien cómo trabaja WPF con los controles y cómo podemos personalizar los nuestros. En WF solamente tenemos el árbol lógico, siendo este la relación entre los controles que son contenedores con sus hijos.

En WF podemos tener un Panel y dentro de este podemos tener un botón o podemos tener un TabPage y dentro de este podemos tener otro panel y dentro un label. Las relaciones entre los padres e hijos forman un árbol de controles.

image

En este grafico podemos ver cuál es el árbol de controles de un formulario cualquiera de Windows Forms. Con este árbol lo único que estamos especificando es que control está dentro de cual y por tanto cual será su espacio de dibujado.

En WF para especificar el rectángulo de dibujado de un control tenemos la propiedad Location (Size) y tenemos Size (Size), dentro de ese rectángulo podemos dibujar con GDI lo que queramos que después será compuesto formando la imagen final del formulario. La manera en la que se definen visualmente los objetos en Windows Forms es a través del código que se ejecuta en OnRender pero no tenemos ninguna otra manera de definir el aspecto de un control, es decir si queremos cambiar el aspecto del control tenemos que sobreescribir el método OnRender teniendo así que cambiar código del control y generando un tipo nuevo, puesto que la única manera de sobrescribir un método virtual es heredando. Esto tiene una serie de problemas puesto que tenemos que modificar el código de nuestro control y desde la perspectiva de un diseñador gráfico es inviable sentarlo con el Visual Studio a dibujar con GDI.

En WPF la cosa cambia mucho. Ahora, como ya he comentado antes, tenemos dos arboles el visual y el lógico. El árbol visual, como su nombre indica, se encarga de definir cuáles son las clases que se encargan de definir como un control se visualiza en pantalla y el árbol lógico es como el control se comporta.

Para explicar este concepto vamos a escoger el botón de WPF para ver cuáles son su árbol visual y lógico.

clip_image004

Árbol Lógico:

image

Árbol Visual:

image

Como podeos observar la cosa cambia mucho porque, del árbol lógico, que es al que estamos todos acostumbrados, cambia mucho con respecto al árbol visual. Pero vayamos por partes.

Button es un caso especial porque este control hereda de ContentControl lo que significa que podemos establecer como contenido lo que queramos (object) en este caso un string. Esto está relacionado con el árbol lógico porque como vemos, el único hijo del botón es el contenido (Content) lo que desde luego tiene sentido porque en WPF podemos establecer como hijo de un botón lo que queramos como por ejemplo un checkbox.

Vayamos ahora a por el árbol visual: si nos fijamos el árbol visual tiene 3 elementos, un ButtonChrome, un ContentPresenter y un TextBlock. Vamos a identificar esos elementos en la imagen del botón.

clip_image010

· ButtonChrome: corresponde el borde más el degradado del botón

· ContentPresenter: es el hueco donde se dibujará el contenido del control

· TextBlock: es el control en WPF para dibujar texto

Podemos modificar el contenido del botón haciendo que en vez de que contenga un string que contenga un checkbox y este tenga un texto asignado.

clip_image012

Árbol Lógico:

image

Árbol Visual:

image

Ahora como hijo del ContentPresenter tenemos al CheckBox que a su vez tiene su árbol visual.

Como se puede observar para un ejemplo pequeño, el árbol visual contiene 7 elementos así que conforme se añaden nuevos elementos el árbol visual crece muy rápidamente.

Lo bueno que tiene los arboles visuales es que definen la manera en que el control se dibuja a sí mismo separando el aspecto del comportamiento. Ahora podemos modificar el aspecto del botón de WPF sin cambiar su comportamiento, como hacíamos en WF. Esto permite que se pueda definir el árbol visual de un control con XAML permitiendo esto que diseñadores puedan generar estilos o temas de los controles desde herramientas como Expression Blend.

La siguiente pregunta es cómo se modifican o generan estos tipos de plantillas visuales, pero eso lo veremos en el siguiente post que hablaré sobre las diferentes tipos de plantillas para WPF.

Ah, por cierto, si programais para Silverlight decir que todo lo dicho aquí es aplicable igualmente a esta tecnología.

Saludos. Luis.

Tool: Memory Pressure

Esta sencilla herramienta permite generar presión en la memoria del sistema a nuestro gusto.

clip_image002

La interfaz de usuario es muy sencilla, podemos seleccionar la cantidad de Megabytes que queremos reservar y cuál es el tamaño de los bloques que queremos usar. Hay que tener en cuenta que esta aplicación utiliza la reserva de memoria del heap de Windows, es decir llama a Marshal.AllocHGlobal que a su vez llama a LocalAlloc.

Una vez que tenemos la memoria reservada podemos liberarla para poder eliminar la presión.

Durante la reserva de memoria se llama a GC.AddMemoryPressure que notifica al recolector de basura la presión actual que se está haciendo en memoria nativa.

Os podeis instalar este software desde: http://www.luisguerrero.net/applications/MemoryPressure/

Tambien os podeis descargar el código fuente: http://www.luisguerrero.net/downloads/MemoryPressure.zip

Luis.

Constructores y Finalizadores

Dentro de la orientación a objetos nos encontramos dos tipos de métodos especiales, el constructor y el destructor. En .NET hay dos tipos de constructores de instancia (ctor) y de tipo (cctor) también llamado constructor estático.

Constructores

Cuando un tipo es accedido por primera vez, se ejecuta el constructor estático bajo un doble lock (Como implementar un Singleton concurrente) para asegurar que solamente se llama una vez, si además se está creando una instancia del objeto se llamará al constructor del objeto para inicializarlo. También se llama a todos los constructores base hasta que se llega a System.Object.

Hay que tener en cuenta que el constructor de un objeto no es más que un método que puede aceptar parámetros y que devuelve una referencia sí mismo a this. Como programadores no tenemos que manejar esta situación puesto que el propio compilador la realiza por nosotros.

Hay un caso excepcional en el que podemos crear un objeto pero no obtener una referencia a el nunca, si en el constructor se lanza una excepción. En este caso, el objeto se inicializa, se llama a los constructores base y se aborta el método en el punto en el que se lance la excepción, pero ya se ha reservado memoria para el objeto y nunca podremos acceder a esa referencia de ese objeto a medio construir.

public class Foo
{
    public DateTime DateTime { get; set; }
    public string file { get; set; }
    public Foo()
    {
        DateTime = DateTime.Now;
        string[] v = file.Split(',');
    }
}

Aquí tenemos un ejemplo de una clase que en el constructor lanzará una excepción de tipo NullReferenceException, realmente si se ha creado el objeto solo que si hacemos algo como:

Foo f = new Foo();

Nunca llegaremos a obtener la referencia del objeto hay creado y además tenemos que tener en cuenta que se tendrá que recolectar el objeto para liberar memoria en la siguiente recolección de basura.

Es por eso que hay que tener mucho cuidado con lo que se pone en el constructor porque podemos dejar a nuestro tipo inutilizado.

La situación se agrava si se lanza una excepción en el constructor de tipo, ya que no podremos acceder a ninguna variable estática ni crear instancias de este tipo.

Destructores o finalizadores

Los destructores (o finalizadores) son los métodos que se llamarán cuando el objeto ya no es usado y se reclama la memoria usada. En este caso es el CLR el que se encarga de llamar a este finalizador pues nosotros no tenemos ningún mecanismo para liberar un objeto.

Un finalizador se define de esta manera:

~Foo()
{
   Console.WriteLine("Adiossss !!");
}

Eso significa que cuando esa instancia del tipo Foo sea recolectada se ejecutará este método. Si miramos al IL generado por este programa realmente el destructor ha pasado a llamarse Finalize() y está marcado como virtual.

Además de definir un destructor de clase hay otro mecanismo para poder definir un destructor, implementando la interfaz IDisposable. Este es el único mecanismo que tiene un programador de .NET para poder definir un destructor de objeto y además poder llamarlo el mismo de codigo de usuario.

Para evitar que este método sea llamado otra vez por el CLR existe el método GC.SupressFinalize.

¿Cómo es llamado este método?

Cada vez que se realiza una recolección de basura el GC examina todos los objetos y si encuentra alguno que es finalizable, es decir, tiene destructor o implementa IDisposable, entonces ese objeto que está listo para ser recolectado pasa a la cola de finalización.

Cuando los objetos están en esa cola de finalización un Thread de alta prioridad se encarga de sacar un elemento de la cola, ejecutar su finalizador y marcarlo como listo para ser recolectado.

Consideraciones a tener en cuenta:

· Cuando el objeto está listo para ser finalizado se lleva a la cola de ReadyToFinalize y además todos los objeto de su grafo son marcados. Eso significa que si ese grafo de objeto normalmente moriría en esta recolección, ahora es promovida a la siguiente, simplemente porque su objeto raíz es finalizable.

· Las generaciones más viejas son recolectadas menos que las generaciones más jóvenes lo que indica que nuestro grafo de objetos puede permanecer vivo durante mucho tiempo hasta que es completamente finalizado y recolectado.

· Al utiliza un único thread, aunque está en prioridad alta, puede darse el caso de que el si tenemos una maquina con 32 procesadores y solamente tenemos una finalizado objetos la cola pueda crecer mucho.

· Hay que tener mucho cuidado con lo que se ejecuta en el finalizador porque si se produce una excepción el proceso entero se descarga. No estamos hablando de que se descargue el dominio de aplicación sino todo el proceso.

Garantías de finalización:

· Durante la ejecución normal de la aplicación y durante cada recolección de basura, se llamara a los finalizadores de los objetos que haya en la cola.

· Si el proceso termina sin la coordinación del CLR, llamando a TerminateProcess o ExitProcess, el CLR tendrá su primera noticia de que el proceso se está cerrando cuando se envíe el mensaje DLL_PROCESS_DETACH en el DllMain. Así que el CLR no tiene tiempo de hacer nada y lo único que puede pasar es que termine los finalizadores críticos (CriticalFinalizerObject).

· Si el proceso es cerrado con el pleno conocimiento del CLR, llamado a Exit o Enviroment.Exit, se finalizarán los objetos que están en la cola de finalización y los finalizadores críticos. Pero los posibles objetos que salgan de una recolección de basura listos para ser finalizados se quedarán sin ser finalizados.

· Si se descarga el dominio de aplicación entonces solamente se finalizarán los objetos que estuvieran en la cola de finalización y los finalizadores criticos.

El codigo del ejemplo: http://www.luisguerrero.net/downloads/ctor.zip

Saludos. Luis.

Rendimiento para el modelado de clases

Rendimiento. En muchos proyectos en los que trabajo una de las preocupaciones a la hora de hacer el proyecto es el rendimiento de la aplicación

Una de las tareas, por no decir la única, es trabajar con datos en una aplicación, modelamos constantemente clases que tiene estado y a su vez exponen una serie de métodos para que los podamos invocar. Hoy a lo que me voy a dedicar a explicar es justamente a ese modelado de datos, al estado de nuestras clases.

Dentro de .NET Framework tenemos varias maneras de definir el estado de una clase:

  • Field (Campo)
  • Property (Propiedad)
  • DependencyProperty (Propiedades de Dependencia)

Estas últimas están pensadas para la interacción de esas clases con Interfaz de Usuario (UI).

Las propiedades son una normalización de los métodos de acceso (set) o de obtención (get) que normalmente se hacía para obtener el acceso a los campos o modificarlos. Realmente las variables se guardan en los campos (field) solo que las propiedades permiten normalizar el acceso y encapsular su funcionalidad de acceso. Son implementadas como métodos dentro del ensamblado.

Lo que vamos a ver en este post es cual la velocidad de acceso a estas propiedades desde .net para saber cuál es la mejor opción si estamos trabajando con el estado de las propiedades y campos. Cualquiera me podría decir que según la lista anterior está claro que siempre la mejor opción (desde el punto de vista del rendimiento) son los campos (Field) puesto que accederos directamente y sin intermediarios, pero además de eso cuando los proyectos empiezan a complicarse puede pensar en usar la reflexión para acceder a los datos, porque se quiere automatizar el acceso a las propiedades de las clases.

Así que lo que vamos a hacer es tener dos tipos de clases una con campos y propiedades, (Field y Property), otra con DependencyProperty y vamos a medir el tiempo que tardamos en acceder a esas variables. Una de las desventajas de usar DP es que para poder usar su funcionalidad tenemos que heredar de la clase DependencyObject, que es la clase que implementa la funcionalidad de DP. Además como he comentado anteriormente las DP únicamente se usar para definir las propiedades de los controles de interfaz de usuario y de los objetos de negocio que interactúan con la UI.

Lo que vamos a hacer son una serie de pruebas con una clase intermedia para medir el tiempo y que se encargue de ejecutar el bloque de código n veces y de medir el tiempo que tarda en hacerlo.

Para la medida del tiempo no vamos a usar DateTime.Now antes de empezar y después de ejecutar porque la medida de tiempo de DateTime.Now no es de alta resolución y no vamos a tener la precisión que necesitamos para medir diferencias de tiempo. En vez de DateTime.Now vamos a usar Stopwatch, clase que está en System.Diagnosis que nos permite hacer medidas de tiempo en alta resolución, el uso de esta clase es bastante sencilla así que el lector la podrá descubrir por el mismo.

Vamos al lio, tenemos está clase normal de C#

public class NormalType
{
    internal int number;

    public int Number
    {
        get { return number; }
        set { number = value; }
    }

    internal string _string;

    public string String
    {
        get { return _string; }
        set { _string = value; }
    }
    internal Item item;

    public Item Item
    {
        get { return item; }
        set { item = value; }
    }
}

Como podéis ver es una clase con tres propiedades un entero, un string y un tipo referencia complejo (Item), todas las propiedades tienen un campo que respalda el valor, así que realmente las propiedades simplemente exponen los valores de los campos sin más.

public class DependencyPropertyType : DependencyObject
{
    public int Number
    {
        get { return (int)GetValue(NumberProperty); }
        set { SetValue(NumberProperty, value); }
    }

    public static readonly DependencyProperty NumberProperty =
        DependencyProperty.Register("Number", typeof(int), typeof(DependencyPropertyType), new PropertyMetadata(0));

    public string String
    {
        get { return (string)GetValue(StringProperty); }
        set { SetValue(StringProperty, value); }
    }

    public static readonly DependencyProperty StringProperty =
        DependencyProperty.Register("String", typeof(string), typeof(DependencyPropertyType), new PropertyMetadata(null));

    public Item Item
    {
        get { return (Item)GetValue(ItemProperty); }
        set { SetValue(ItemProperty, value); }
    }

    public static readonly DependencyProperty ItemProperty =
        DependencyProperty.Register("Item", typeof(Item), typeof(DependencyPropertyType), new PropertyMetadata(null));

}

Esta otra clase es la clase que implementa las DependencyProperty, como veis ahora por cada propiedad hay una definición de una clase de tipo DependencyProperty que es estático y de solo lectura (static readonly) y además no es el tipo que definimos las propiedades. Eso que significa que los valores de esta clase son compartidos, desde luego no porque las propiedades son de instancia no estáticas, solo que ahora utilizamos dos métodos helpers que nos ayudan a acceder y establecer los valores de las propiedades (GetValue,SetValue). Esos dos métodos están definidos en la clase base DependencyObject. Realmente nosotros no tenemos información de donde se están guardando esas variables dentro de nuestra clase no hay ningún campo (Field) ni lista ni nada que nos indique donde se están almacenando esos valores, y si miramos en la clase base nos pasa lo mismo. En otro post desvelare toda la potencia y misterios de las DP ahora simplemente lo usaremos sin más para esta prueba de rendimiento.

Ahora lo que vamos a hacer son las pruebas en sí de acceso a estos valores.

image 

Sin reflexion

Con Reflexion

Getter Field

92,176

4483,588

Getter Property

142,813

3169,277

Getter Dependency Property

755,433

1619,854

Setter Field

0,607

3716,121

Setter Property

0,584

2600,364

Setter Dependency Property

129,623

973,799

La prueba está dividida en dos grandes grupos Setters y Getters (establecer y obtener).

Para los Setters (establecer) los campos tiene el mejor rendimiento cuando se trabaja con ellos, pero son los que tiene el peor rendimiento cuando se trata de acceder a través de reflexión, las propiedades es el mismo caso solo que se mejora un poco el tiempo de acceso desde reflexión y para las Dependency Property están muy equilibradas en cuanto a tiempo de acceso con y sin reflexión.

En el caso de Getters (obtener) la historia se repite, los campos y propiedades son increíblemente rápidos, pero cuando se trabaja con ellos a través de reflexión se penaliza, las Dependency Property en relación con el Setter son más rápidas con y sin reflexión.

El caso especial de las Dependency Proeprty.

¿Por qué las Dependency Properties tiene mejores tiempos?, podríamos decir que la prueba esta adulterada, porque realmente siempre se utiliza los métodos GetValue y SetValue que DependencyObject nos proporciona para acceder a esos valores y ahí es donde está la sobrecarga pero hay que tener en cuenta que la reflexión usada en esta prueba es solo para acceder a las instancias de las DependencyProperty que son necesarias en las llamadas de GetValue y SetValue.

Por si sentís curiosidad los valores de las instancias de clases que heredan de DependencyObject se guardan en un campo interno de la clase DependencyObject llamado _effectiveValues de tipo EffectiveValueEntry,

private EffectiveValueEntry[] _effectiveValues;

EffectiveValueEntry es la clase que manera el valor de una Dependency Property, cada una de estos EffectiveValueEntry tiene asociado un índice dentro de todos los registros de Dependency Proeprty al cual nosotros podemos acceder desde DependencyProperty.GlobalIndex (int) pero no podemos usar de ninguna manera, no sé porque Microsoft ha permitido acceder a esta propiedad si no tiene utilizad.

La solución de Visual Studio para que hagáis vuestras pruebas.

http://www.luisguerrero.net/downloads/DPPerformance.zip

Luis Guerrero.