2009-05-14 7 views
12

Abbiamo un sacco di oggetti valore immutabili nel nostro modello di dominio, un esempio di questo è una posizione, definita da una latitudine, longitudine & altezza.Come posso modificare oggetti immutabili in WPF senza duplicare il codice?

/// <remarks>When I grow up I want to be an F# record.</remarks> 
public class Position 
{ 
    public double Latitude 
    { 
     get; 
     private set; 
    } 

    // snip 

    public Position(double latitude, double longitude, double height) 
    { 
     Latitude = latitude; 
     // snip 
    } 
} 

Il modo ovvio per consentire la modifica di una posizione è quello di costruire una ViewModel che ha getter e incastonatori, nonché un metodo ToPosition() per estrarre l'istanza posizione immutabile convalidato. Mentre questa soluzione sarebbe ok, risulterebbe in un sacco di codice duplicato, specialmente XAML.

Gli oggetti valore in questione sono composti da tre a cinque proprietà, che di solito sono alcune varianti di X, Y, Z & alcune cose ausiliarie. Detto questo, avevo preso in considerazione la creazione di tre ViewModels per gestire le varie possibilità, in cui ciascun ViewModel avrebbe bisogno di esporre le proprietà per il valore di ogni proprietà e una descrizione da visualizzare per ogni etichetta (ad esempio "Latitude").

Andando oltre, sembra che potrei semplificarlo con un ViewModel generale che può gestire le proprietà N e collegare tutto usando la riflessione. Qualcosa come una griglia di proprietà, ma per oggetti immutabili. Un problema con una griglia proprietà è che voglio essere in grado di cambiare l'aspetto in modo da poter avere etichette e caselle di testo come ad esempio:

Latitude: [  32 ] <- TextBox 
Longitude: [  115 ] 
Height:  [  12 ] 

o metterlo in un DataGrid come ad esempio:

Latitude | Longitude | Height 
     32   115   12 

Quindi la mia domanda è:

si può pensare a un modo elegante per risolvere questo problema? Ci sono librerie che fanno questo o articoli su qualcosa di simile?

Sono soprattutto alla ricerca di:

  • duplicazione del codice da minimizzare
  • Facile aggiungere nuovi tipi di oggetto valore
  • possibile estendere con qualche tipo di convalida

risposta

2

Ho trovato questa vecchia domanda mentre ricercavo le mie possibili opzioni nella stessa situazione. Ho pensato che dovrei aggiornarlo nel caso in cui qualcun altro si imbattesse in esso:

Un'altra opzione (non disponibile quando Paul ha offerto la sua soluzione poiché .Net 4 non era ancora disponibile) è di usare la stessa strategia, ma invece di implementare usando CustomTypeDescriptors, usa una combinazione di generici, oggetti dinamici e riflessioni per ottenere lo stesso effetto.

In questo caso, si definisce una classe

class Mutable<ImmutableType> : DynamicObject 
{ 
    //... 
} 

costruttore di Ci vuole un esempio del tipo di immutabile e un delegato che costruisce una nuova istanza di fuori di un dizionario, proprio come nella risposta di Paolo. La differenza, tuttavia, è che si sostituiscono TryGetMember e TrySetMember per popolare un dizionario interno che verrà utilizzato come argomento per il delegato del costruttore. Si utilizza la riflessione per verificare che le sole proprietà che si accettano siano quelle effettivamente implementate in ImmutableType.

Per quanto riguarda le prestazioni, scommetto che la risposta di Paul è più veloce e non coinvolge oggetti dinamici, che sono noti per mettere gli sviluppatori C# in crisi. Ma l'implementazione di questa soluzione è anche un po 'più semplice, perché i descrittori di tipi sono un po' arcani.


Ecco l'implementazione proof-of-concept/example richiesto:

https://bitbucket.org/jwrush/mutable-generic-example

+0

Questa è una bella soluzione, la preferisco a quella di Paul perché c'è meno spazio. Naturalmente, si potrebbe effettivamente utilizzare la riflessione nella sua soluzione per ridurre il sovraccarico del programmatore di mappare le proprietà al dizionario. In realtà non c'è una risposta accettata perché ho risolto il problema in un altro modo e non avevo notato la risposta di Paolo 6 mesi circa dopo che avevo posto la domanda. Se dovessi offrire uno snippet di codice utilizzabile che le persone potrebbero usare, contrassegnerò questa come risposta accettata. –

0

Riesci a pensare a un modo elegante per risolvere questo problema?

Onestamente, si balla intorno al problema, ma non parlare del problema stesso;).

Se indovino correttamente il problema, la combinazione di MultiBinding e IMultiValueConverter dovrebbe fare la differenza.

HTH.

P.S. A proposito, hai istanze di classe immutabili, non oggetti di valore. Con oggetti valore (che sono descritti dalla parola chiave struct) si balla molto di più, non importa se ci fossero setter o meno :).

+2

Penso che tu abbia un malinteso di oggetti di valore. I tipi di valore sono strutture come dici tu, ma un oggetto valore non è la stessa cosa. Vedi questo articolo per maggiori informazioni: http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/05/20/entities-value-objects-aggregates-and-roots.aspx –

+1

Potresti forse approfondire come posso rendere il problema più chiaro? In poche parole, ho un oggetto che è immutabile e mi piacerebbe essere in grado di associare facilmente le caselle di testo alle sue proprietà nello stesso modo in cui posso con un oggetto mutevole. –

+0

Mi dispiace per gli oggetti valore - colpa mia, non sapevo che ce ne fosse uno. Informazioni sull'elaborazione del problema: ad esempio, è possibile fornire uno degli snip che si desidera generare. Forse, sarebbe anche utile se fornisci alcuni meta-snips della tua visione della soluzione generica ideale in XAML. – archimed7592

5

I descrittori di tipo personalizzato potrebbero essere utilizzati per risolvere questo problema. Prima di eseguire il binding a una posizione, il descrittore di tipo potrebbe eseguire il kick-in e fornire i metodi get e set per creare temporaneamente i valori. Quando vengono eseguite le modifiche, è possibile creare l'oggetto immutabile.

Potrebbe sembrare qualcosa di simile:

DataContext = new Mutable(position, 
    dictionary => new Position(dictionary["lattitude"], ...) 
); 

le associazioni possono ancora apparire come segue:

<TextBox Text="{Binding Path=Lattitude}" /> 

Poiché l'oggetto Mutevole sarà 'finta' di avere immobili come Lattitude grazie alla sua TypeDescriptor .

In alternativa è possibile utilizzare un convertitore nei binding e ottenere una sorta di convenzione.

La classe Mutable prende l'attuale oggetto immutabile e un Func<IDictionary, object> che consente di creare il nuovo oggetto immutabile una volta completata la modifica. La tua classe Mutable farebbe uso del descrittore di tipi, che creerebbe PropertyDescriptors che crea il nuovo oggetto immutabile dopo essere stato impostato.

Per un esempio di come utilizzare tipo descrittori, vedere qui:

http://www.paulstovell.com/editable-object-adapter

Edit: se si desidera limitare la frequenza vengono creati gli oggetti immutabili, si potrebbe anche guardare BindingGroups e IEditableObject, che il tuo Mutable può anche implementare.

Problemi correlati