WPF Advanced – ISealable

Recientemente en un cliente me ha ocurrido un caso muy extraño con las Dependecy Property de WPF y quiero escribir sobre el tema. Es un poco enrevesado pero creo que puede ser útil. Veamos.

WPF tiene un sistema de Binding muy potente que hace que se puedan hacer enlaces a datos a entidades de negocio. Pues bien el caso es que para que una clase pueda tener Dependency Property (DP a partir de ahora), es necesario que heredemos como mínimo de la clase DependencyObject que permite que una clase almacene DP, pues bien el caso es que mis entidades de negocio heredan de esta clase para que las propiedades que lo define sean DP y poder utilizar toda la potencia de WPF, hasta aquí todo bien. Para lo que no estéis tan al tanto de DP es un sistema que permite tener propiedades con metadatos y un sistema centralizado de almacenamiento de valores. Para definir un DP es así.

public static readonly DependencyProperty CurrentBusinessEntityProperty =
    DependencyProperty.Register("CurrentBusinessEntity", typeof(BusinesEntity), typeof(BusinessManager), new UIPropertyMetadata(new BusinesEntity()));

public BusinesEntity CurrentBusinessEntity
{
    get { return (BusinesEntity)GetValue(CurrentBusinessEntityProperty); }
    set { SetValue(CurrentBusinessEntityProperty, value); }
}

Si os fijáis en el primer parámetro del UIPropertyMetadata es el valor predeterminado del DP, hay que tener en cuenta que esto se inicializa en el constructor de tipo (static en C#) y que por tanto es seguro frente a subprocesos. Pues bien si el valor predeterminado que nosotros usamos es un objeto que hereda de DependencyObject WPF lanza una excepción. La excepción es:

clip_image002

Default value for the ‘CurrentBusinessEntity’ property cannot be bound to a specific thread.

Es algo bastante raro, porque si pongo null funciona si hago otro tipo de DP funciona correctamente y solamente si utilizo como valor predeterminado una entidad de negocio mía (que hereda de DependencyObject) la no cosa funciona. Pero la excepción que se nos lanza no nos da mucha información. Utilizando el código fuente del framework y depurando todo el proceso de inicialización del DP, llego hasta este bonito trozo del código.

if (checkThreadAffinity) 
{
// If the default value is a DispatcherObject with thread affinity 
// we cannot accept it as a default value. If it implements ISealable
// we attempt to seal it; if not we throw  an exception. Types not
// deriving from DispatcherObject are allowed - it is up to the user to
// make any custom types free-threaded. 

DispatcherObject dispatcherObject = defaultValue as DispatcherObject; 

if (dispatcherObject != null && dispatcherObject.Dispatcher != null)
{ 
    // Try to make the DispatcherObject free-threaded if it's an
    // ISealable.

    ISealable valueAsISealable = dispatcherObject as ISealable; 

    if (valueAsISealable != null && valueAsISealable.CanSeal) 
    { 
        Invariant.Assert (!valueAsISealable.IsSealed,
               "A Sealed ISealable must not have dispatcher affinity"); 

        valueAsISealable.Seal();

        Invariant.Assert(dispatcherObject.Dispatcher == null, 
            "ISealable.Seal() failed after ISealable.CanSeal returned true");
    } 
    else 
    {
        throw new ArgumentException(SR.Get(SRID.DefaultValueMustBeFreeThreaded, propertyName)); 
    }
}
}

En el que básicamente los señores que prograron WPF vienen a decir algo así como que el valor predeterminado es un DispathcerObject (en mi caso lo es) no pueden acceptar solo si implementa ISealable entonces lo sellan y si no pueden lanzan una excepción.

Una bonita manera de encontrar el error, continuando la investiagación, me dispongo pues a que mio clase implemente esta interfaz ISealable, pero cual es mi sorpresa que la interfaz no existe. La busco en el MSDN nada, abro el reflecto y para mi sorpresa me encuentro con esto

[FriendAccessAllowed]

internal interface ISealable

{

// Methods

void Seal();

// Properties

bool CanSeal { get; }

bool IsSealed { get; }

}

Que la internfaz es internal y encima se permite el acceso de los ensamblado amigos, pues con el mismo reflecto abierto me dispongo pues a ver que clases dentro del framework (por curiosidad mas que nada) implementan esta fatídica interfaz, y me encuentro con esto:

clip_image004

Que las tres únicas clases que implementan esa funcionalidad son FrameworkTemplate, Freezable y Style.

Los que halláis leído otras entradas de WPF sabréis que Freezable está presente en el árbol de algunos tipos de controles de WPF permitiendo que el contenido del control se pueda de alguna manera congelar para aumentar el rendimiento de la aplicación. Pues bien parece que para que pueda poner como valor predeterminado una entidad mia de negocio tengo que heredar no de DependencyObject sino de Freezable.

Resumen:

Esta una bonita historia sobre como WPF es a veces un sistema muy complejo que creo que ni la gente de Microsoft a veces entiende, no porque esté mal diseñada, sino por lo compleja que es. Así que me pareció un exceso tener que heredar de Freezable únicamente por poder poner una entidad de negocio mía como valor predeterminado de un DP, así que lo deje como estaba antes.

Ahora bien porque WPF hace esto?, al ser la DP usadas por una clase que hereda de DependencyObject esta clase tiene una propiedad muy importante el Dispatcher que es el gestor de threads dentro de WPF así que supongo que la comprobación vendrá por si el objeto se está iniciando en un thread que no contiene un Dispatcher o se tendrá que hacer invocación. Y por *intentar* hacer la vida más sencilla a los programadores Microsoft ofrece la clase Freezable, pero por lo menos lanza una excepción explicando el tema o algo porque sino la gente se vuelve loca.

 

Aquí teneis el codigo fuente de ejemplo [SealedExample.rar]

Saludos. Luis.

3 comentarios en “WPF Advanced – ISealable”

  1. Genial Luis!! Este tipo de artículos son los que me hacen interesarme por WPF y que, en el fondo, muy en el fondo, pueda llegar a admitir que WPF es algo mas que pintar colorines! Me encantan tus posts sobre WPF tio! 🙂

  2. Qué ventaja ofrece derivar de DependencyObject tus entidades de negocio respecto a implementar INotifyPropertyChanged ?

    Para ser el origen de DataBinding es suficiente y debería ser menos pesado y más natural ( y menos restrictivo en cuanto a la clase padre ).

  3. Es cierto eso que comentas Pablo Alarcon, solo que la diferencia es que las propiedades son más ricas en cuanto a metadatos, tiene implementado las notificaciones porque son DependencyPropertys.
    El uso que yo doy a las entidades de negocio en cliente para WPF es hacerles un Binding a la UI de WPF y usar DP me permite por ejemplo, establecer un Flag en los Metadatos de la DP que indique que esa propiedad afecta al Renderizado de mi control, cosa que sin DP no puedo hacer. Se podría decir que es cosa de gustos, porque estas notificaciones automáticas de que un control se tiene que repintar lo puedo conseguir yo mismo implementando esa funcionalidad, pero WPF ya tiene un mecanismo para hacerlo por mí y seguro mejor de lo que lo pueda hacer yo. Yo intento usar casi la mayoría de funcionalidad que viene en WPF antes de implementar cualquier cosa por mí, de hecho por ejemplo estoy en contra de cualquier modelo MVC MPM MPV o MVx que se pueda implementar en WPF, porque WPF tiene muchos mecanismos para implementar esta funcionalidad de la misma manera o de una manera más natural, solo hay que encontrarlo.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *