Invest in people before investing in tools

Il blog di Matteo Baglini
posts - 115, comments - 91, trackbacks - 2323

Mixin, POCO e INotifyPropertyChanged mito o realtà?

Dopo aver chiesto ad i CDays2008 a Mauro cosa pensava dei mixin, dopo aver letto i vari post di Andrea, Raffaele e Gian Maria che parlano di POCO e dopo aver letto i due post (qui e qui) nel blog inglese di Gian Maria ho deciso di spiegare meglio cosa intendevo con il mio commento al post di Andrea.

Gian Maria mostra due modi diversi per rivolvere il "problema" dell'interfaccia INotifyPropertyChanged creando proxy a run-time, io avevo risolto il solito problema usando Spring.NET AOP. Questo dimostra che come al solito ci sono svariati modi per ottenere il solito risultato.

Finita questa piccola anticipazione arrivo al nocciolo del discorso e sparo la mia sentenza...nessuna di queste implementazioni è usabile!! Vi chiederete perchè ed io sono qui per dimostrarvelo. Piccola demo, provate a creare una semplice BindingList<Customer>, aggiungete ad essa un numero imprecisato di Customer-proxaty, agganciate l'evento ListChanged ed in fine provate a modificare la proprietà di un Customer della BindingList...et vualà l'evento non viene scatenato, cosa che dovrebbe accadere se Customer implementasse normalmente INotifyPropertyChanged.

init

Quando per la prima volta mi sono trovato davanti a questo problema armato di Reflector sono andato a sbirciare il codice della classe BindingList<T> ed ho trovato l'inghippo. Guardando il codice mostrato nell'immagine vediamo che la seconda linea di codice verifica se il tipo INotifyPropertyChanged è assegnabile dal tipo Customer, questa verifica nel caso di Customer in versione POCO è assolutamente falsa dato che il tipo Customer non sa niente di INotifyPropertyChanged. Per confermare questa teoria mi sono creato un po di test unitari che riproducono il problema, vediamoli. In entrambi i casi di test la creazione del Customer è stata "nascosta" dentro dei metodi Factory dato che non mi interessa mostrarne l'implementazione in questo post. Per prima cosa ho creato due classi Customer la prima implementa INotifyPropertyChanged e la seconda no, però ha le properties marcate come virtual per permettere la creazione del proxy a run-time. 

   1: public class Customer : INotifyPropertyChanged
   2: {
   3:     public event PropertyChangedEventHandler PropertyChanged;
   4:     private Guid id = Guid.NewGuid();
   5:     private string name;
   6:  
   7:     public Guid Id {
   8:         get { return id; }
   9:     }
  10:     public string Name {
  11:         get { return name; }
  12:         set {
  13:             if (name != value) {
  14:                 name = value;
  15:                 RaisePropertyChanged("Name");}
  16:             }
  17:     }
  18:     private void RaisePropertyChanged(string propertyName)
  19:     {
  20:         if (PropertyChanged != null)
  21:             PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  22:     }
  23: }
   1: public class CustomerPoco
   2: {
   3:     private Guid id = Guid.NewGuid();
   4:  
   5:     public virtual Guid Id {
   6:         get { return id; }
   7:     }
   8:     public virtual string Name { get; set; }
   9: }

Dopodiché ho creato due metodi di test, uno verifica se il Customer è castabile a INotifyPropertyChanged mentre l'altro verifica se è assegnabile.

   1: [Fact]
   2: public void ShouldBeCompatibleWithTypeINotifyPropertyChanged()
   3: {
   4:     Customer customer = CustomerFactory.CreateCustomer();
   5:     (customer is INotifyPropertyChanged).ShouldBeTrue("Is not INotifyPropertyChanged");
   6: }
   7: [Fact]
   8: public void ShouldBeAssignableToTypeINotifyPropertyChanged()
   9: {
  10:     typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(Customer))
  11:         .ShouldBeTrue("INotifyPropertyChanged is not assignable from Customer");
  12: }

In questo caso entrabi i test passano perfettamente, proseguiamo eseguendo i soliti test su CustomerPoco:

   1: [Fact]
   2: public void ShouldBeCompatibleWithTypeINotifyPropertyChanged()
   3: {
   4:     CustomerPoco customer = CustomerFactory.CreateCustomerPoco();
   5:     (customer is INotifyPropertyChanged).ShouldBeTrue("Is not INotifyPropertyChanged");
   6: }
   7: [Fact]
   8: public void ShouldBeAssignableToTypeINotifyPropertyChanged()
   9: {
  10:     typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(CustomerPoco))
  11:         .ShouldBeTrue("INotifyPropertyChanged is not assignable from CustomerPoco");
  12: }

In quest'ultimo caso il primo test passa mentre il secondo no distruggendo i nostri sogni di sviluppatore lazy che non doveva più implementare INotifyPropertyChanged smile_wink. Ecco perchè nel mio commento al post di Andrea ho detto: "...perchè il framework di per se non conosce i mixin quindi è stato scritto "bloccandone" l'uso". Problemi simili sono risolvibili con diversi hack, basta barare con un po di reflection dopo aver istanziato la collection:

   1: private static void EditRaiseItemChangedEvents(BindingList<Customer> list)
   2: {
   3:     FieldInfo _fi = list.GetType().GetField("raiseItemChangedEvents", BindingFlags.NonPublic | BindingFlags.Instance);
   4:     bool raiseItemChangedEvents = (bool)_fi.GetValue(list);
   5:     if (!raiseItemChangedEvents) _fi.SetValue(list, true);
   6: }

ed il gioco è fatto ma in generale hanno un costo troppo alto, sia di implementazione che di manutenzione dato che il problema non si presenta solo per collection come la BindingList ma anche per controlli come il BindingSource delle WindowsForm.

Print | posted on mercoledì 6 agosto 2008 23.54 | Filed Under [ .NET Architecture ]

Feedback

Gravatar

# re: Mixin, POCO e INotifyPropertyChanged mito o realtà?

Ciao,

interessante, io ho fatto qualche prova con DynamicProxy di Castle e c'è naturalmente lo stesso problema.
Inoltre a naso secondo me il gioco non vale la candela, perchè se è vero che risparmi un po' di codice di infrastruttura è anche vero che ti porti a casa un bel po' di codice che è tutto tranne che facilmente manutenibile.

IMHO la strada ideale è quella della generazione di codice con MSBuild in fase di compilazione... ergo creo la classe, creo le mie proprietà e magari marco classe (o meglio ancora il proxy) con un bell'attributo che viene valutato da un custom task di MSBuild che altro non fa che aggiungere un bel po' di codice che tanto è sempre lo stesso... quasi quasi mi ci metto ;-)

.m
07/08/2008 12.38 | Mauro Servienti
Gravatar

# re: Mixin, POCO e INotifyPropertyChanged mito o realtà?


Sono anche io dell'opinione che i proxy non sono utili in queste situazioni. Secondo me gli approcci possibili sono 2, o non ti preoccupi e metti la IXXX nel tuo oggetto di dominio oppure usi i DTO, aumentando in questo caso la base di codice da mantenere. Invece della generazione runtime di proxy molto più utile potrebbe essere la generazione di codice da Visual Studio, un po come i dataset strongly typed.
Riguardo alla generazione di codice con un custom task, mi piace di meno perchè è sempre codice che viene generato a compile time, un approccio con codeDom è migliore perchè ti fai creare un file con il sorgente del tuo proxy, classe partial, cosi puoi usarlo come vuoi ed estenderlo se ne hai bisogno.

alk.
07/08/2008 13.55 | Gian MAria
Gravatar

# re: Mixin, POCO e INotifyPropertyChanged mito o realtà?

Anch'io ho fatto prove con Castle.DynamicProxy e, a meno di workaround simili, l'unica alternativa era implementare INotifyPropertyChanged sulla entity, lasciando però all'interceptor (e quindi al proxy) l'incombenza di sollevare l'evento.
Poi, però, ho dovuto implementare una bella factory per le entity (ok, posso farla con un framework di IoC), ho dovuto far sì che quando NH recupera un oggetto ne venga automaticamente costruito il proxy, ecc.ecc.ecc.
Sapete cosa vi dico? Molto meglio uno snippet ;-)
07/08/2008 14.34 | Marco De Sanctis
Gravatar

# re: Mixin, POCO e INotifyPropertyChanged mito o realtà?

Si Marco, lo snippet è bello ed alla fine produce meno probelmi.

Per quanto riguarda la azione MSBuild ho invece provato una cosa più carina, in un oretta e mezza ho fatto un programma che deassembla, modifica il codice MSIL e riassembla e fa si che le classi che tu gli indichi supportino l'INotifyPropertyChanged, potrebbe essere una soluzione.

alk.
07/08/2008 19.45 | Gian MAria
Gravatar

# re: Mixin, POCO e INotifyPropertyChanged mito o realtà?

Esatto snippet è la via più veloce/manutenibile, quindi meno costosa. Personalmente mi "muovo" come ha detto Gian Maria, o implemento l'interfaccia e me ne frego o passo ai DTO. Molto interessante l'idea del task per msbuild che modifica il codice MSIL!
07/08/2008 20.04 | Matteo Baglini
Gravatar

# re: Mixin, POCO e INotifyPropertyChanged mito o realtà?

Se a questo aggiungi il fatto che ci sono altre interfacce (vedi IEditableObject) e attributi (vedi WCF) che non puoi che implementare nella entity, meglio lo snippet in una partial class.
Purtroppo i partial method sono ancora acerbi, altrimenti si sarebbe potuto applicare gli attributi sul partial method vuoto e lasciare l'implementazione nell'altro file sorgente.
08/08/2008 9.05 | Raffaele Rialdi
Gravatar

# re: Mixin, POCO e INotifyPropertyChanged mito o realtà?

I make a semi-generic solution for this thats implements INotifyPropertyChanged, INotifyPropertyChanging, IEditableObject and other level of manual discard changes. This can be used in this way:

public class AnyBussinesObjectUIRepresentation : Entities.AnyBussinesObjectBase, IEditableUIObject
{
public override bool Active
{
get { return editableData.GetData<bool>("Active"); }
set { editableData.SetData("Active", value); }
}

public override string Description
{
get { return editableData.GetData<string>("Description"); }
set { editableData.SetData("Description", value); }
}

public override string Code
{
get { return editableData.GetData<string>("Code"); }
set { editableData.SetData("Code", value); }
}

public override Guid Id
{
get { return editableData.GetData<Guid>("Id"); }
set { editableData.SetData("Id", value); }
}

public override string Name
{
get { return editableData.GetData<string>("Name"); }
set { editableData.SetData("Name", value); }
}


#region IEditableUIObject Implementation

private IEditableUIObjectSupporter editableData = new EditableUIObjectSupporter();

public event PropertyChangingEventHandler PropertyChanging
{
add { editableData.PropertyChanging += value; }
remove { editableData.PropertyChanging -= value; }
}

public event PropertyChangedEventHandler PropertyChanged
{
add { editableData.PropertyChanged += value; }
remove { editableData.PropertyChanged -= value; }
}

[IgnoreDataMember]
public bool DiscartableChangesControl
{
get { return editableData.DiscartableChangesControl; }
set { editableData.DiscartableChangesControl = value; }
}

public bool IsDirty
{
get { return editableData.IsDirty; }
}

public void DiscardChanges()
{
editableData.DiscardChanges();
}

public void AcceptChanges()
{
editableData.AcceptChanges();
}

public ValueChangedDescriptorCollection GetChanges()
{
return editableData.GetChanges();
}

void IEditableObject.BeginEdit()
{
editableData.BeginEdit();
}

void IEditableObject.CancelEdit()
{
editableData.CancelEdit();
}

void IEditableObject.EndEdit()
{
editableData.EndEdit();
}

#endregion
}

I don't paste the base code because there are 300 lines but you can see it in code.google.com/.../UI

Maybe the Hashtables are not so efficients, but I think that it will be enough for my project.

Thanks for the inspiration.
04/03/2009 23.52 | andres

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 8 and 4 and type the answer here:

Powered by: