Come posso determinare in modo affidabile il tipo di una variabile dichiarata utilizzando var in fase di progettazione?


109

Sto lavorando a una struttura di completamento (intellisense) per C # in emacs.

L'idea è che, se un utente digita un frammento, quindi richiede il completamento tramite una particolare combinazione di tasti, la funzione di completamento utilizzerà la riflessione .NET per determinare i possibili completamenti.

Per fare questo è necessario conoscere il tipo di cosa da completare. Se è una stringa, esiste un insieme noto di possibili metodi e proprietà; se è un Int32, ha un set separato e così via.

Usando semantic, un pacchetto lexer / parser di codice disponibile in emacs, posso individuare le dichiarazioni di variabili e i loro tipi. Detto questo, è semplice utilizzare la reflection per ottenere i metodi e le proprietà sul tipo, quindi presentare l'elenco di opzioni all'utente. (Ok, non è abbastanza semplice da fare all'interno di emacs, ma usando la possibilità di eseguire un processo powershell all'interno di emacs , diventa molto più semplice. Scrivo un assembly .NET personalizzato per fare la riflessione, lo carico in PowerShell e poi elisp viene eseguito all'interno emacs può inviare comandi a powershell e leggere le risposte, tramite comint. Di conseguenza emacs può ottenere rapidamente i risultati della riflessione.)

Il problema arriva quando il codice utilizza var nella dichiarazione della cosa da completare. Ciò significa che il tipo non è specificato esplicitamente e il completamento non funzionerà.

Come posso determinare in modo affidabile il tipo effettivo utilizzato, quando la variabile viene dichiarata con l'estensione var parola chiave? Giusto per essere chiari, non ho bisogno di determinarlo in fase di esecuzione. Voglio determinarlo in "Design time".

Finora ho queste idee:

  1. compilare e invocare:
    • estrae la dichiarazione di dichiarazione, ad esempio `var foo =" a string value ";`
    • concatenare un'istruzione `foo.GetType ();`
    • compilare dinamicamente il frammento C # risultante in un nuovo assembly
    • caricare l'assembly in un nuovo AppDomain, eseguire il framgment e ottenere il tipo restituito.
    • scaricare e scartare l'assieme

    So come fare tutto questo. Ma suona terribilmente pesante, per ogni richiesta di completamento nell'editor.

    Suppongo di non aver bisogno ogni volta di un nuovo AppDomain. Potrei riutilizzare un singolo AppDomain per più assemblaggi temporanei e ammortizzare il costo di installazione e demolizione, tra più richieste di completamento. È più un ritocco dell'idea di base.

  2. compilare e ispezionare IL

    Compilare semplicemente la dichiarazione in un modulo, quindi ispezionare l'IL per determinare il tipo effettivo dedotto dal compilatore. Come sarebbe possibile? Cosa userei per esaminare l'IL?

Qualche idea migliore là fuori? Commenti? suggerimenti?


MODIFICA - pensando a questo ulteriormente, la compilazione e l'invocazione non è accettabile, perché l'invocazione potrebbe avere effetti collaterali. Quindi la prima opzione deve essere esclusa.

Inoltre, penso di non poter presumere la presenza di .NET 4.0.


AGGIORNAMENTO - La risposta corretta, non menzionata sopra, ma delicatamente sottolineata da Eric Lippert, è quella di implementare un sistema di inferenza di tipo full fidelity. È l'unico modo per determinare in modo affidabile il tipo di una variabile in fase di progettazione. Ma non è nemmeno facile da fare. Poiché non mi illudo di voler tentare di costruire una cosa del genere, ho scelto la scorciatoia dell'opzione 2: estrarre il codice di dichiarazione pertinente e compilarlo, quindi ispezionare l'IL risultante.

Questo funziona effettivamente, per un discreto sottoinsieme degli scenari di completamento.

Ad esempio, si supponga nei seguenti frammenti di codice, il? è la posizione in cui l'utente chiede il completamento. Funziona:

var x = "hello there"; 
x.?

Il completamento si rende conto che x è una stringa e fornisce le opzioni appropriate. Lo fa generando e quindi compilando il seguente codice sorgente:

namespace N1 {
  static class dmriiann5he { // randomly-generated class name
    static void M1 () {
       var x = "hello there"; 
    }
  }
}

... e poi ispezionare l'IL con una semplice riflessione.

Funziona anche:

var x = new XmlDocument();
x.? 

Il motore aggiunge le clausole using appropriate al codice sorgente generato, in modo che venga compilato correttamente, quindi l'ispezione IL è la stessa.

Funziona anche questo:

var x = "hello"; 
var y = x.ToCharArray();    
var z = y.?

Significa solo che l'ispezione IL deve trovare il tipo della terza variabile locale, invece della prima.

E questo:

var foo = "Tra la la";
var fred = new System.Collections.Generic.List<String>
    {
        foo,
        foo.Length.ToString()
    };
var z = fred.Count;
var x = z.?

... che è solo un livello più profondo dell'esempio precedente.

Ma ciò che non funziona è il completamento di qualsiasi variabile locale la cui inizializzazione dipende in qualsiasi momento da un membro dell'istanza o dall'argomento del metodo locale. Piace:

var foo = this.InstanceMethod();
foo.?

Né la sintassi LINQ.

Dovrò pensare a quanto siano preziose queste cose prima di considerare di affrontarle tramite quello che è sicuramente un "progetto limitato" (parola gentile per hack) per il completamento.

Un approccio per affrontare il problema con le dipendenze dagli argomenti del metodo o dai metodi di istanza potrebbe essere quello di sostituire, nel frammento di codice che viene generato, compilato e quindi analizzato IL, i riferimenti a queste cose con variabili locali "sintetiche" dello stesso tipo.


Un altro aggiornamento : il completamento delle variabili che dipendono dai membri dell'istanza ora funziona.

Quello che ho fatto è stato interrogare il tipo (tramite semantica) e quindi generare membri sostitutivi sintetici per tutti i membri esistenti. Per un buffer C # come questo:

public class CsharpCompletion
{
    private static int PrivateStaticField1 = 17;

    string InstanceMethod1(int index)
    {
        ...lots of code here...
        return result;
    }

    public void Run(int count)
    {
        var foo = "this is a string";
        var fred = new System.Collections.Generic.List<String>
        {
            foo,
            foo.Length.ToString()
        };
        var z = fred.Count;
        var mmm = count + z + CsharpCompletion.PrivateStaticField1;
        var nnn = this.InstanceMethod1(mmm);
        var fff = nnn.?

        ...more code here...

... il codice generato che viene compilato, in modo che io possa apprendere dall'output IL il tipo di var locale nnn, assomiglia a questo:

namespace Nsbwhi0rdami {
  class CsharpCompletion {
    private static int PrivateStaticField1 = default(int);
    string InstanceMethod1(int index) { return default(string); }

    void M0zpstti30f4 (int count) {
       var foo = "this is a string";
       var fred = new System.Collections.Generic.List<String> { foo, foo.Length.ToString() };
       var z = fred.Count;
       var mmm = count + z + CsharpCompletion.PrivateStaticField1;
       var nnn = this.InstanceMethod1(mmm);
      }
  }
}

Tutti i membri dell'istanza e del tipo statico sono disponibili nel codice scheletro. Si compila con successo. A quel punto, determinare il tipo di var locale è semplice tramite Reflection.

Ciò che lo rende possibile è:

  • la possibilità di eseguire PowerShell in emacs
  • il compilatore C # è davvero veloce. Sulla mia macchina, ci vogliono circa 0,5 secondi per compilare un assembly in memoria. Non abbastanza veloce per l'analisi tra le sequenze di tasti, ma abbastanza veloce da supportare la generazione su richiesta di elenchi di completamento.

Non ho ancora esaminato LINQ.
Questo sarà un problema molto più grande perché il lexer / parser semantico che emacs ha per C #, non "fa" LINQ.


4
Il tipo di foo viene individuato e compilato dal compilatore tramite l'inferenza del tipo. Sospetto che i meccanismi siano completamente diversi. Forse il motore di inferenza del tipo ha un gancio? Per lo meno userei "inferenza di tipo" come tag.
George Mauer

3
La tua tecnica per creare un modello di oggetti "falso" che abbia tutti i tipi ma nessuna delle semantiche degli oggetti reali è buona. È così che ho fatto IntelliSense per JScript in Visual InterDev nel corso della giornata; creiamo una versione "falsa" del modello a oggetti di IE che ha tutti i metodi e i tipi ma nessuno degli effetti collaterali, quindi eseguiamo un piccolo interprete sul codice analizzato in fase di compilazione e vediamo quale tipo ritorna.
Eric Lippert

Risposte:


202

Posso descrivere per voi come lo facciamo in modo efficiente nel "vero" C # IDE.

La prima cosa che facciamo è eseguire un passaggio che analizza solo le cose di "livello superiore" nel codice sorgente. Saltiamo tutti i corpi dei metodi. Ciò ci consente di creare rapidamente un database di informazioni su quali spazi dei nomi, tipi e metodi (e costruttori, ecc.) Sono presenti nel codice sorgente del programma. L'analisi di ogni singola riga di codice in ogni corpo del metodo richiederebbe troppo tempo se provi a farlo tra le sequenze di tasti.

Quando l'IDE ha bisogno di elaborare il tipo di una particolare espressione all'interno del corpo di un metodo, si supponga di aver digitato "foo". e dobbiamo capire quali sono i membri di foo - facciamo la stessa cosa; saltiamo tutto il lavoro che ragionevolmente possiamo.

Iniziamo con un passaggio che analizza solo le dichiarazioni di variabili locali all'interno di quel metodo. Quando eseguiamo questo passaggio, creiamo una mappatura da una coppia di "scope" e "name" a un "type determiner". Il "determinatore di tipo" è un oggetto che rappresenta la nozione di "posso elaborare il tipo di questo locale se necessario". Elaborare il tipo di locale può essere costoso, quindi vogliamo rimandare quel lavoro se necessario.

Ora abbiamo un database costruito pigramente che può dirci il tipo di ogni locale. Quindi, tornando a quel "foo". - scopriamo in quale affermazione si trova l'espressione pertinente e quindi eseguiamo l'analizzatore semantico solo su quell'affermazione. Ad esempio, supponi di avere il corpo del metodo:

String x = "hello";
var y = x.ToCharArray();
var z = from foo in y where foo.

e ora dobbiamo capire che foo è di tipo char. Creiamo un database che contiene tutti i metadati, i metodi di estensione, i tipi di codice sorgente e così via. Creiamo un database che ha determinanti di tipo per x, y e z. Analizziamo l'affermazione contenente l'espressione interessante. Iniziamo trasformandolo sintatticamente in

var z = y.Where(foo=>foo.

Per capire il tipo di foo dobbiamo prima conoscere il tipo di y. Quindi a questo punto chiediamo al determinatore di tipo "qual è il tipo di y"? Quindi avvia un analizzatore di espressioni che analizza x.ToCharArray () e chiede "qual è il tipo di x"? Abbiamo un determinatore di tipo per quello che dice "Ho bisogno di cercare" String "nel contesto corrente". Non esiste alcun tipo String nel tipo corrente, quindi cerchiamo nello spazio dei nomi. Non è nemmeno lì, quindi guardiamo nelle direttive using e scopriamo che esiste un "sistema using" e che System ha un tipo String. OK, quindi questo è il tipo di x.

Quindi interroghiamo i metadati di System.String per il tipo di ToCharArray e viene indicato che si tratta di un System.Char []. Super. Quindi abbiamo un tipo per y.

Ora chiediamo "System.Char [] ha un metodo Where?" No. Quindi guardiamo nelle direttive using; abbiamo già precalcolato un database contenente tutti i metadati per i metodi di estensione che potrebbero essere utilizzati.

Ora diciamo "OK, ci sono diciotto dozzine di metodi di estensione denominati Where nell'ambito, qualcuno di essi ha un primo parametro formale il cui tipo è compatibile con System.Char []?" Quindi iniziamo un ciclo di test di convertibilità. Tuttavia, i metodi di estensione Where sono generici , il che significa che dobbiamo eseguire l'inferenza del tipo.

Ho scritto un motore di inferenza di tipo speciale in grado di gestire inferenze incomplete dal primo argomento a un metodo di estensione. Eseguiamo il type inferrer e scopriamo che esiste un metodo Where che accetta un IEnumerable<T>, e che possiamo fare un'inferenza da System.Char [] a IEnumerable<System.Char>, quindi T è System.Char.

La firma di questo metodo è Where<T>(this IEnumerable<T> items, Func<T, bool> predicate), e sappiamo che T è System.Char. Inoltre sappiamo che il primo argomento tra parentesi al metodo di estensione è un lambda. Quindi avviamo un inferrer di tipo di espressione lambda che dice "si presume che il parametro formale foo sia System.Char", usa questo fatto quando analizzi il resto del lambda.

Ora abbiamo tutte le informazioni necessarie per analizzare il corpo del lambda, che è "foo.". Cerchiamo il tipo di foo, scopriamo che secondo il legante lambda è System.Char, e abbiamo finito; vengono visualizzate le informazioni sul tipo per System.Char.

E facciamo tutto tranne l'analisi "di primo livello" tra le sequenze di tasti . Questa è la vera parte complicata. In realtà scrivere tutta l'analisi non è difficile; è renderlo abbastanza veloce da poterlo fare alla velocità di digitazione che è la vera parte complicata.

In bocca al lupo!


8
Eric, grazie per la risposta completa. Mi hai aperto un bel po 'gli occhi. Per emacs, non aspiravo a produrre un motore dinamico tra le sequenze di tasti che potesse competere con Visual Studio in termini di qualità dell'esperienza utente. Per prima cosa, a causa della latenza di ~ 0,5 secondi insita nel mio progetto, la struttura basata su emacs è e rimarrà solo su richiesta; nessun suggerimento di tipo anticipato. Per un altro, implementerò il supporto di base dei var locali, ma sarò felice di puntare quando le cose si complicano o quando il grafico delle dipendenze supera un certo limite. Non sono ancora sicuro di quale sia quel limite. Grazie ancora.
Cheeso

13
Onestamente mi stupisce che tutto questo possa funzionare così rapidamente e in modo affidabile, in particolare con le espressioni lambda e l'inferenza di tipo generico. In realtà sono rimasto piuttosto sorpreso la prima volta che ho scritto un'espressione lambda e Intellisense conosceva il tipo del mio parametro quando ho premuto., Anche se l'istruzione non era ancora completa e non ho mai specificato esplicitamente i parametri generici dei metodi di estensione. Grazie per questa piccola sbirciatina nella magia.
Dan Bryant

21
@ Dan: ho visto (o scritto) il codice sorgente e mi stupisce che funzioni anche lui. :-) Ci sono delle cose pelose lì dentro.
Eric Lippert

11
I ragazzi di Eclipse probabilmente lo fanno meglio perché sono più fantastici del compilatore C # e del team IDE.
Eric Lippert

23
Non ricordo affatto di aver fatto questo stupido commento. Non ha nemmeno senso. Devo essere ubriaco. Scusate.
Tomas Andrle

15

Posso dirti più o meno come funziona l'IDE Delphi con il compilatore Delphi per fare intellisense (l'insight del codice è quello che Delphi lo chiama). Non è applicabile al 100% a C #, ma è un approccio interessante che merita considerazione.

La maggior parte dell'analisi semantica in Delphi viene eseguita nel parser stesso. Le espressioni vengono digitate man mano che vengono analizzate, ad eccezione delle situazioni in cui ciò non è facile: in questo caso viene utilizzata l'analisi anticipata per determinare ciò che è inteso, quindi tale decisione viene utilizzata nell'analisi.

L'analisi è in gran parte discendente ricorsiva LL (2), ad eccezione delle espressioni, che vengono analizzate utilizzando la precedenza degli operatori. Una delle cose distinte di Delphi è che è un linguaggio a passaggio singolo, quindi i costrutti devono essere dichiarati prima di essere utilizzati, quindi non è necessario alcun passaggio di primo livello per far emergere tali informazioni.

Questa combinazione di funzionalità significa che il parser ha all'incirca tutte le informazioni necessarie per la comprensione del codice per ogni punto in cui è necessario. Il modo in cui funziona è questo: l'IDE informa il lexer del compilatore della posizione del cursore (il punto in cui si desidera approfondire il codice) e il lexer lo trasforma in un token speciale (si chiama token kibitz). Ogni volta che il parser incontra questo token (che potrebbe essere ovunque) sa che questo è il segnale per rimandare tutte le informazioni che ha all'editor. Lo fa usando un longjmp perché è scritto in C; quello che fa è che notifica al chiamante finale il tipo di costrutto sintattico (cioè contesto grammaticale) in cui è stato trovato il punto kibitz, così come tutte le tabelle simboliche necessarie per quel punto. Quindi per esempio se il contesto è in un'espressione che è un argomento di un metodo, possiamo controllare gli overload del metodo, guardare i tipi di argomento e filtrare i simboli validi solo per quelli che possono risolversi in quel tipo di argomento (questo riduce in un molti cruft irrilevanti nel menu a discesa). Se si trova in un contesto di ambito nidificato (ad esempio, dopo un "."), Il parser restituirà un riferimento all'ambito e l'IDE può enumerare tutti i simboli trovati in tale ambito.

Si fanno anche altre cose; ad esempio, i corpi dei metodi vengono saltati se il token kibitz non si trova nel loro intervallo: ciò viene fatto in modo ottimistico e viene eseguito il rollback se ha saltato il token. L'equivalente dei metodi di estensione - gli helper di classe in Delphi - hanno una sorta di cache con versione, quindi la loro ricerca è ragionevolmente veloce. Ma l'inferenza di tipo generico di Delphi è molto più debole di quella di C #.

Ora, alla domanda specifica: inferire i tipi di variabili dichiarati con varè equivalente al modo in cui Pascal deduce il tipo di costanti. Deriva dal tipo di espressione di inizializzazione. Questi tipi sono costruiti dal basso verso l'alto. Se xè di tipo Integer, ed yè di tipo Double, allora x + ysarà di tipo Double, perché quelle sono le regole della lingua; ecc. Segui queste regole finché non hai un tipo per l'espressione completa sul lato destro, e questo è il tipo che usi per il simbolo sulla sinistra.



4

I sistemi Intellisense in genere rappresentano il codice utilizzando un albero sintassi astratto, che consente loro di risolvere il tipo di ritorno della funzione assegnata alla variabile "var" più o meno nello stesso modo in cui lo farà il compilatore. Se usi VS Intellisense, potresti notare che non ti darà il tipo di var finché non avrai terminato di inserire un'espressione di assegnazione valida (risolvibile). Se l'espressione è ancora ambigua (ad esempio, non può dedurre completamente gli argomenti generici per l'espressione), il tipo var non verrà risolto. Questo può essere un processo abbastanza complesso, poiché potrebbe essere necessario camminare abbastanza in profondità in un albero per risolvere il tipo. Per esempio:

var items = myList.OfType<Foo>().Select(foo => foo.Bar);

Il tipo restituito è IEnumerable<Bar>, ma per risolvere questo problema è necessario conoscere:

  1. myList è del tipo che implementa IEnumerable.
  2. Esiste un metodo di estensione OfType<T>che si applica a IEnumerable.
  3. Il valore risultante è IEnumerable<Foo>e c'è un metodo di estensione Selectche si applica a questo.
  4. L'espressione lambda foo => foo.Barha il parametro foo di tipo Foo. Ciò è dedotto dall'uso di Select, che richiede un Func<TIn,TOut>e poiché TIn è noto (Foo), il tipo di foo può essere dedotto.
  5. Il tipo Foo ha una proprietà Bar, che è di tipo Bar. Sappiamo che Select return IEnumerable<TOut>e TOut possono essere dedotti dal risultato dell'espressione lambda, quindi il tipo di elementi risultante deve essere IEnumerable<Bar>.

Bene, può diventare piuttosto profondo. Sono a mio agio con la risoluzione di tutte le dipendenze. Solo pensando a questo, la prima opzione che ho descritto - compila e invoca - non è assolutamente accettabile, perché invocare il codice può avere effetti collaterali, come aggiornare un database, e questo non è qualcosa che un editor dovrebbe fare. Compilare è ok, invocare no. Per quanto riguarda la costruzione dell'AST, non credo di volerlo fare. Davvero voglio rimandare quel lavoro al compilatore, che sa già come farlo. Voglio poter chiedere al compilatore di dirmi cosa voglio sapere. Voglio solo una risposta semplice.
Cheeso

La sfida con l'ispezione dalla compilazione è che le dipendenze possono essere arbitrariamente profonde, il che significa che potrebbe essere necessario compilare tutto affinché il compilatore generi codice. Se lo fai, penso che tu possa usare i simboli del debugger con l'IL generato e abbinare il tipo di ogni locale con esso.
Dan Bryant

1
@Cheeso: il compilatore non offre quel tipo di analisi del tipo come servizio. Spero che in futuro lo farà, ma nessuna promessa.
Eric Lippert

sì, penso che questa potrebbe essere la strada da percorrere: risolvere tutte le dipendenze, quindi compilare e ispezionare IL. @ Eric, buono a sapersi. Per ora se non aspiro a fare l'analisi AST completa, devo ricorrere a un trucco sporco per produrre questo servizio utilizzando gli strumenti esistenti. Ad esempio, compilare un frammento di codice costruito in modo intelligente e quindi utilizzare ILDASM (o simile) a livello di programmazione per ottenere la risposta che cerco.
Cheeso

4

Dato che stai prendendo di mira Emacs, potrebbe essere meglio iniziare con la suite CEDET. Tutti i dettagli che Eric Lippert sono già trattati nell'analizzatore di codice nello strumento CEDET / Semantic per C ++. C'è anche un parser C # (che probabilmente necessita di un po 'di TLC), quindi le uniche parti mancanti sono relative all'ottimizzazione delle parti necessarie per C #.

I comportamenti di base sono definiti in algoritmi di base che dipendono da funzioni sovraccaricabili definite in base al linguaggio. Il successo del motore di completamento dipende dalla quantità di ottimizzazione eseguita. Con c ++ come guida, ottenere un supporto simile a C ++ non dovrebbe essere troppo male.

La risposta di Daniel suggerisce di utilizzare MonoDevelop per eseguire analisi e analisi. Questo potrebbe essere un meccanismo alternativo al posto del parser C # esistente oppure potrebbe essere usato per aumentare il parser esistente.


Bene, so di CEDET e sto usando il supporto C # nella directory contrib per la semantica. Semantic fornisce l'elenco delle variabili locali e dei loro tipi. Un motore di completamento può scansionare quell'elenco e offrire le scelte giuste all'utente. Il problema è quando la variabile è var. Semantic lo identifica correttamente come var, ma non fornisce inferenza di tipo. La mia domanda era specificamente intorno come affrontare quella . Ho anche esaminato il collegamento al completamento CEDET esistente, ma non sono riuscito a capire come. La documentazione per CEDET è ... ah ... non completa.
Cheeso

Commento a margine - CEDET è ammirevolmente ambizioso, ma l'ho trovato difficile da usare ed estendere. Attualmente il parser tratta lo "spazio dei nomi" come un indicatore di classe in C #. Non riuscivo nemmeno a capire come aggiungere "spazio dei nomi" come elemento sintattico distinto. Ciò ha impedito tutte le altre analisi sintattiche e non sono riuscito a capire perché. In precedenza ho spiegato la difficoltà che ho avuto con il framework di completamento. Oltre a questi problemi, ci sono cuciture e sovrapposizioni tra i pezzi. Ad esempio, la navigazione fa parte sia della semantica che del senatore. CEDET sembra allettante, ma alla fine ... è troppo ingombrante per impegnarsi.
Cheeso

Cheeso, se vuoi ottenere il massimo dalle parti meno documentate di CEDET, la soluzione migliore è provare la mailing list. È facile per le domande approfondire aree che non sono state ancora ben sviluppate, quindi sono necessarie alcune iterazioni per elaborare buone soluzioni o per spiegare quelle esistenti. Per C # in particolare, dato che non ne so nulla, non ci saranno risposte semplici una tantum.
Eric

2

È un problema difficile fare bene. Fondamentalmente è necessario modellare le specifiche del linguaggio / compilatore attraverso la maggior parte del lexing / parsing / typechecking e costruire un modello interno del codice sorgente che puoi quindi interrogare. Eric lo descrive in dettaglio per C #. È sempre possibile scaricare il codice sorgente del compilatore F # (parte di F # CTP) e dare un'occhiata aservice.fsi all'interfaccia esposta dal compilatore F # che il servizio del linguaggio F # utilizza per fornire intellisense, suggerimenti per i tipi dedotti, ecc. un senso di una possibile "interfaccia" se si aveva già il compilatore disponibile come API da chiamare.

L'altra strada consiste nel riutilizzare i compilatori così come sono descritti, quindi utilizzare la riflessione o guardare il codice generato. Questo è problematico dal punto di vista che hai bisogno di 'programmi completi' per ottenere un output di compilazione da un compilatore, mentre quando modifichi il codice sorgente nell'editor, spesso hai solo 'programmi parziali' che non analizzano ancora, non hanno ancora implementato tutti i metodi, ecc.

In breve, penso che la versione "low budget" sia molto difficile da fare bene, e la versione "reale" è molto, molto difficile da fare bene. (Dove "difficile" qui misura sia lo "sforzo" che la "difficoltà tecnica".)


Sì, la versione "low budget" ha alcuni chiari limiti. Sto cercando di decidere cosa sia "abbastanza buono" e se posso soddisfare quel bar. Nella mia esperienza sperimentale di ciò che ho ottenuto finora, rende la scrittura in C # all'interno di emacs molto più piacevole.
Cheeso


0

Per la soluzione "1" hai una nuova funzione in .NET 4 per farlo rapidamente e facilmente. Quindi, se puoi convertire il tuo programma in .NET 4, sarebbe la scelta migliore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.