Come migrare il mio pensiero da C ++ a C #

12

Sono uno sviluppatore C ++ con esperienza, conosco la lingua con ottimi dettagli e ho usato intensamente alcune delle sue caratteristiche specifiche. Inoltre, conosco i principi dell'OOD e gli schemi di progettazione. Ora sto imparando C # ma non riesco a fermare la sensazione di non essere in grado di liberarmi della mentalità C ++. Mi sono legato così duramente ai punti di forza del C ++ che non posso vivere senza alcune delle caratteristiche. E non riesco a trovare soluzioni alternative o sostituzioni per loro in C #.

Quali buone pratiche , modelli di design , idiomi diversi in C # dalla prospettiva C ++ puoi suggerire? Come ottenere un design C ++ perfetto senza sembrare stupidi in C #?

In particolare, non riesco a trovare un buon modo C # per affrontare (esempi recenti):

  • Controllo della durata delle risorse che richiedono una pulizia deterministica (come i file). È facile avere using in mano ma come usarlo correttamente quando viene trasferita la proprietà della risorsa [... tra i thread]? In C ++ vorrei semplicemente usare i puntatori condivisi e lasciare che si occupasse della "garbage collection" al momento giusto.
  • Costantemente in difficoltà con funzioni di override per generici specifici (adoro le cose come la specializzazione di template parziale in C ++). Dovrei semplicemente abbandonare qualsiasi tentativo di fare una programmazione generica in C #? Forse i generici sono limitati di proposito e non è C # -ish usarli se non per uno specifico dominio di problemi?
  • Funzionalità a macroistruzione. Mentre in genere è una cattiva idea, per alcuni domini di problemi non vi è altra soluzione alternativa (ad esempio la valutazione condizionale di un'istruzione, come con i registri che dovrebbero solo andare alle versioni di Debug). Non averli significa che ho bisogno di mettere più if (condition) {...} boilerplate e non è ancora uguale in termini di innescare gli effetti collaterali.
posta Andrew 14.08.2013 - 22:50
fonte

5 risposte

16

Nella mia esperienza, non c'è un libro per farlo. I forum aiutano con gli idiomi e le migliori pratiche, ma alla fine dovrai metterti in tempo. Esercitati risolvendo un numero di problemi diversi in C #. Guarda cosa è stato difficile / imbarazzante e cerca di renderli meno duri e meno difficili la prossima volta. Per me, questo processo è durato circa un mese prima di "averlo capito".

La cosa più importante su cui concentrarsi è la mancanza di gestione della memoria. In C ++ questo è in grado di dominare ogni singola decisione progettuale, ma in C # è controproducente. In C #, spesso si desidera ignorare la morte o altre transizioni di stato dei propri oggetti. Quel tipo di associazione aggiunge un accoppiamento non necessario.

Per lo più, concentrati sull'uso più efficace dei nuovi strumenti, non picchiarti su un allegato a quelli vecchi.

How to get a perfect C++ design not looking dumb in C#?

Comprendendo che idiomi diversi spingono verso approcci progettuali diversi. Non puoi semplicemente portare qualcosa di 1: 1 tra (la maggior parte) delle lingue e aspettarti qualcosa di bello e di idiomatico dall'altra parte.

Ma in risposta a scenari specifici:

Risorse non gestite condivise - Questa è stata una cattiva idea in C ++, ed è una cattiva idea in C #. Se hai un file, aprilo, usalo e chiudilo. La condivisione tra thread richiede solo problemi e, se solo un thread lo sta realmente utilizzando, il thread lo apre / lo stesso / lo chiude. Se veramente hai un file di lunga durata per qualche motivo, allora crea una classe per rappresentarla e lasciala gestirla secondo necessità (chiudi prima dell'uscita, chiudi vicino a Chiusura applicazione tipi di eventi, ecc.)

Specializzazione di modelli parziali - Sei sfortunato qui, anche se più ho usato C # più trovo che l'uso dei template che ho fatto in C ++ ricada su YAGNI. Perché creare qualcosa di generico quando T è sempre un int? Altri scenari sono meglio risolti in C # dall'applicazione liberale di interfacce. Non hai bisogno di generici quando un'interfaccia o un tipo di delegato può scrivere con forza ciò che vuoi variare.

Condizioni condizionali preprocessore : C # ha #if , diventa matto. Meglio ancora, ha attributi condizionali che semplicemente lo abbandoneranno funzione dal codice se un simbolo del compilatore non è definito.

    
risposta data 14.08.2013 - 23:03
fonte
4

Per quanto posso vedere, l'unica cosa che consente la gestione deterministica del Lifetime è IDisposable , che scompone (come in: nessuna lingua o tipo supporto di sistema) quando, come hai notato, devi trasferire la proprietà di tale una risorsa.

Essere nella stessa barca come te - lo sviluppatore C ++ sta facendo C # atm. - la mancanza di supporto per modellare il trasferimento di proprietà (file, connessioni db, ...) nei tipi / firme C # è stato molto fastidioso per me, ma devo ammettere che funziona abbastanza bene a "solo" fare affidamento su convenzioni e documenti API per farlo bene.

  • Utilizza IDisposable per eseguire la pulizia deterministica iff necessaria.
  • Quando devi trasferire la proprietà di un IDisposable , assicurati che l'API in questione sia chiara e non utilizzare using in questo caso, perché using non può essere "annullato" in modo da parlare.

Sì, non è elegante come quello che puoi esprimere nel sistema dei tipi con il C ++ moderno (semidio del movimento, ecc.), ma il lavoro è fatto e (incredibilmente per me) la comunità C # nel suo complesso non lo fa Mi sembra che mi importi troppo, AFAICT.

    
risposta data 01.02.2015 - 20:14
fonte
2

Hai citato due caratteristiche specifiche di C ++. Discuterò RAII:

Di solito non è applicabile, dato che C # è garbage collection. La costruzione C # più vicina è probabilmente l'interfaccia IDisposable , utilizzata come segue:

using (FileStream fs = File.OpenRead(@"c:\path\filename.txt"))
{
   //Do stuff with the file.
} //File will be closed here.

Non dovresti aspettarti di implementare spesso IDisposable (e così facendo è leggermente avanzato), in quanto non è necessario per le risorse gestite. Tuttavia, dovresti utilizzare il costrutto using (o chiamare tu stesso Dispose , se using non è fattibile) per qualsiasi cosa che implementa IDisposable . Anche se lo incasini, le classi C # ben progettate proveranno a gestirlo per te (usando i finalizzatori). Tuttavia, in un linguaggio garbage-gather il pattern RAII non funziona completamente (poiché la raccolta non è deterministica), quindi la pulizia delle risorse verrà ritardata (a volte catastroficamente).

    
risposta data 14.08.2013 - 23:09
fonte
2

Questa è un'ottima domanda, in quanto ho visto un numero di sviluppatori C ++ scrivere codice C # terribile.

È meglio non pensare a C # come a "C ++ con sintassi migliore". Sono lingue diverse che richiedono approcci diversi nel tuo modo di pensare. C ++ ti obbliga a pensare costantemente a cosa faranno la CPU e la memoria. C # non è così C # è progettato in modo specifico per non pensare alla CPU e alla memoria e invece pensa al dominio aziendale che stai scrivendo .

Un esempio di ciò che ho visto è che molti sviluppatori C ++ vorrebbero usare i loop perché sono più veloci di foreach. Di solito questa è una pessima idea in C # perché limita i possibili tipi di raccolta su cui è stata eseguita l'iterazione (e quindi la riutilizzabilità e la flessibilità del codice).

Penso che il modo migliore per riadattarsi da C ++ a C # sia cercare di avvicinarsi alla codifica da una prospettiva diversa. All'inizio questo sarà difficile, perché nel corso degli anni sarai completamente abituato a usare il thread "che cosa fa la CPU e la memoria" nel tuo cervello per filtrare il codice che scrivi. Ma in C #, dovresti invece pensare alle relazioni tra gli oggetti nel dominio aziendale. "Cosa voglio fare" al contrario di "cosa sta facendo il computer".

Se vuoi fare qualcosa per tutto in un elenco di oggetti, invece di scrivere un ciclo for nell'elenco, crea un metodo che prende un IEnumerable<MyObject> e usa un ciclo foreach .

I tuoi esempi specifici:

Controlling the lifetime of resources that require deterministic cleanup (like files). This is easy having using in hand but how to use it properly when ownership of the resource is being transfered [... betwen threads]? In C++ I would simply use shared pointers and let it take care of 'garbage collection' just at the right time.

Non dovresti mai farlo in C # (in particolare tra i thread). Se devi fare qualcosa su un file, fallo in un posto alla volta. Va bene (e in effetti una buona pratica) scrivere una classe wrapper che gestisca la risorsa non gestita che viene passata tra le tue classi, ma non cercare di passare un file tra i thread e avere classi separate di scrittura / lettura / chiusura / apertura. Non condividere la proprietà delle risorse non gestite. Utilizza il modello Dispose per gestire la pulizia.

Constant struggling with overriding functions for specific generics (I love things like partial template specialization in C++). Should I just abandon any attempts to do any generic programming in C#? Maybe generics are limited on purpose and it is not C#-ish to use them except for a specific domain of problems?

I generici in C # sono progettati per essere generici. Le specializzazioni di generici dovrebbero essere gestite da classi derivate. Perché un List<T> dovrebbe comportarsi diversamente se è un List<int> o un List<string> ? Tutte le operazioni su List<T> sono generiche in modo da applicare a qualsiasi List<T> . Se si desidera modificare il comportamento del metodo .Add su List<string> , quindi creare una classe derivata MySpecializedStringCollection : List<string> o una classe composta MySpecializedStringCollection : IList<string> che utilizza internamente il generico ma fa le cose in modo diverso. Questo ti aiuta a evitare di violare il Principio di sostituzione di Liskov e di rovinare a fondo gli altri che usano la tua classe.

Macro-like functionality. While generally a bad idea, for some domain of problems there is no other workaround (e.g. conditional evaluation of a statement, like with logs that should only go to Debug releases). Not having them means that I need to put more if (condition) {...} boilerplate and it is still not equal in terms of triggering side effects.

Come altri hanno già detto, puoi usare i comandi del preprocessore per farlo. Gli attributi sono ancora meglio. In generale gli attributi sono il modo migliore per gestire cose che non sono funzionalità "core" per una classe.

In breve, quando scrivi C #, tieni presente che dovresti pensare interamente al dominio aziendale e non alla CPU e alla memoria. Puoi ottimizzare in seguito in seguito, ma il tuo codice dovrebbe riflettere le relazioni tra i principi aziendali che stai tentando di mappare.

    
risposta data 02.02.2015 - 00:59
fonte
1

La cosa più diversa per me era la tendenza a tutto nuovo. Se vuoi un oggetto, dimentica di provare a passarlo a una routine e condividere i dati sottostanti, sembra che il modello C # sia solo per crearti un nuovo oggetto. Questo, in generale, sembra essere molto più del modo C #. All'inizio questo schema sembrava molto inefficiente, ma immagino che la creazione di molti oggetti in C # sia un'operazione rapida.

Tuttavia, ci sono anche le classi statiche che mi infastidiscono davvero ogni volta che provi a crearne una solo per scoprire che devi semplicemente usarla. Trovo che non sia così facile sapere quale usare.

Sono abbastanza felice in un programma C # per ignorarlo quando non ne ho più bisogno, ma ricorda cosa contiene quell'oggetto - in genere devi solo pensare se ha dei dati "insoliti", oggetti che consistono solo la memoria andrà via da sola, gli altri dovranno essere eliminati o dovrai vedere come distruggerli - manualmente o usando un blocco se non lo sono.

lo raccoglierete molto rapidamente, tuttavia C # non è affatto diverso da VB, quindi potete considerarlo come lo stesso strumento RAD (se aiuta a pensare a C # come VB.NET, quindi fate la stessa cosa, la sintassi è solo leggermente diverso, ei miei vecchi tempi di utilizzo di VB mi hanno messo nella giusta mentalità per lo sviluppo di RAD). L'unico bit difficile è determinare quale bit delle enormi librerie .net dovresti usare. Google è sicuramente il tuo amico qui!

    
risposta data 15.08.2013 - 00:08
fonte

Leggi altre domande sui tag