L'obiettivo finale è specificare i contratti per una classe che risiede in un assembly esterno su cui non ho il controllo (ad esempio non posso aggiungere direttamente contratti a quella classe direttamente).Creazione di contratti di codice per una libreria legacy
Quello che ho provato finora:
ContractClassFor
attributo.
Non funziona, perché la classe di destinazione deve fare riferimento alla classe del contratto.Creare manualmente un assembly di riferimento del contratto (ad esempio MyAsm.Contracts.dll) eseguendo il reverse engineering dei generatori automatici.
Non funziona, perché subito dopo averlo compilato, il rewriter esegue il kicking e taglia via l'attributoContractDeclarativeAssembly
, il che rende l'assembly non riconoscibile dagli strumenti come "assembly di riferimento del contratto". E non riesco a trovare un modo per disattivare il rewriter.Creare un assembly "falso" con lo stesso nome, versione, nome sicuro (se presente) e altri attributi dell'assembly di destinazione e inserire le classi con lo stesso nome con metodi con lo stesso nome. Quindi compilare l'assembly con l'opzione "build contract reference" attivata, quindi prendere l'assembly di riferimento del contratto generato e utilizzarlo.
Anche questo non ha funzionato per qualche motivo, anche se non ho idea di quale fosse esattamente questa ragione. Il controllo statico semplicemente ignora il mio assembly di riferimento generato da smartypants, come se non esistesse.
E un'altra cosa, nel caso in cui è importante: l'assieme di destinazione è compilato per .NET 2.0, e non posso ricompilare per 4,0.
Aggiornamento
Scrivi libreria "wrapper" con contratti definiti è fuori discussione.
Per uno, è un sacco di codice in più da scrivere. Ma anche se lo si lascia da parte, potrebbe essere una penalità significativa per le prestazioni: dovrei (potenzialmente) creare classi wrapper per ogni tipo "legacy" che viene usato come tipo di ritorno dai miei metodi "legacy". Ma anche quella non è la fine della storia. Immagina che alcuni metodi possano restituire riferimenti alle interfacce di base, e quindi il codice chiamante potrebbe lanciarli su interfacce derivate per vedere se l'oggetto supporta più funzionalità. Come li avvolgo? Probabilmente dovrei fornire metodi personalizzati Cast<T>()
sulle mie classi di wrapping, che proveranno a eseguire il cast della classe/interfaccia sottostante e restituiranno un wrapper per il risultato se avrà successo. Alla fine, la mia intera base di codice diventerà così complicata che probabilmente non ne valga la pena.
D'altra parte, proprio questo problema è già stato risolto con successo dal team CC stesso: spediscono gli assembly di riferimento del contratto appropriati per mscorlib, System.Core e alcuni altri gruppi di sistema, no? Come sono riusciti a costruirli? Se possono farlo, allora non vedo alcuna ragione per cui non dovrei essere in grado di realizzare lo stesso trucco.
Oh, beh, in realtà, vedo una ragione: non so come farlo. :-) - Fyodor Soikin 18 sec fa modifica
Puoi riflettere l'altro assieme e solo decompilarlo? –
Non ho bisogno di decompilarlo, in realtà ho il codice sorgente per questo. Ma il punto non è modificare quel codice. È anche usato in altri progetti, che hanno come target 2.0, quindi anche il più piccolo cambiamento possibile nell'aggiunta degli attributi '[ContractClass]' spezzerebbe quei progetti. Inoltre, non voglio separare il codice sorgente, perché in tal caso mi piacerebbe davvero unire le future modifiche in quel ramo. –
Beh, se si mantiene la sorgente da soli, sarebbe molto facile mantenere un ramo 2.0 e 4.0 dei contratti se si utilizza git/mercurial dove il ramo 2.0 è predefinito e il 4.0 è un ramo di funzionalità, quando vengono aggiunte le proprietà/cambiato al ramo predefinito dopo che tutto ciò che devi fare è usare il DVCS per unire le modifiche nel ramo 4.0 allora. Questo sarebbe molto poco lavoro a patto che ci si assicuri che l'unico codice che si modifica nel ramo 4.0 sia correlato al codice dei contratti C# e non alle stesse classi. –