Comment puis-je déterminer de manière fiable le type d'une variable qui est déclarée à l'aide de var au moment de la conception?

Je suis en train de travailler sur un achèvement (intellisense) facilité pour C# dans emacs.

L'idée est que, si un utilisateur tape un fragment, puis demande à l'achèvement par l'intermédiaire d'un particulier, d'une combinaison de touches, l'achèvement de la facilité d'utilisation .NET de la réflexion pour déterminer les complétions possibles.

Cette opération nécessite le type de la chose d'être complétée, à être connue. Si c'est une chaîne, il y a un ensemble de méthodes et de propriétés; si c'est un Int32, il possède un ensemble distinct, et ainsi de suite.

À l'aide de la sémantique, un code lexer/analyseur de paquet disponible dans emacs, je peux localiser les déclarations de variables et leurs types. Compte tenu de cela, il est simple à utiliser la réflexion pour obtenir les méthodes et les propriétés du type, puis présenter la liste des options à l'utilisateur. (Ok, pas tout à fait simple faire dans emacs, mais à l'aide de la possibilité d'exécuter un processus powershell à l'intérieur d'emacs, il devient beaucoup plus facile. J'écris une coutume .NET de l'assemblée de la réflexion, de la charger dans le powershell, puis elisp cours d'exécution dans emacs peut envoyer des commandes powershell et lire les réponses, via comint. Comme un résultat emacs peut obtenir les résultats de la réflexion rapidement.)

Le problème arrive lorsque le code utilise var dans la déclaration de la chose en cours d'achèvement. Cela signifie que le type n'est pas spécifié, et l'achèvement de ne pas travailler.

Comment puis-je déterminer de manière fiable le type réel utilisé lorsque la variable est déclarée avec le var mot-clé? Juste pour être clair, je n'ai pas besoin de le déterminer au moment de l'exécution. Je veux déterminer au "moment de la Conception".

Jusqu'à présent j'ai de ces idées:

  1. de compiler et d'invoquer:
    • extrait de la déclaration de la fonction, par exemple, `var foo = "chaîne de valeur";`
    • concaténer une déclaration " foo.GetType();`
    • compiler dynamiquement le C# résultant fragment dans une nouvelle assemblée
    • charger l'assembly dans un nouveau domaine d'application, exécutez la framgment et d'obtenir le type de retour.
    • de décharger et de jeter l'assemblée

    Je sais comment faire tout cela. Mais ça sonne terriblement lourd, pour chaque réalisation requête dans l'éditeur.

    Je suppose que je n'ai pas besoin d'un nouveau domaine d'application à chaque fois. J'ai pu re-utiliser un seul domaine d'application pour plusieurs temporaire assemblées, et d'amortir le coût de sa mise en
    et de le déchirer vers le bas, à travers de multiples achèvement des demandes. C'est plus qu'un réglage de l'idée de base.

  2. de compiler et d'inspecter IL

    Simplement compiler la déclaration dans un module, puis vérifiez l'IL, afin de déterminer le type réel qui a été déduite par le compilateur. Comment serait-ce possible? Que serais-je utiliser pour examiner l'-t-IL?

Toutes les meilleures idées là-bas? Des commentaires? des suggestions?


MODIFIER - penser à cette autre, de compiler et d'invoquer n'est pas acceptable, parce que l'appel peut avoir des effets secondaires. Si la première option doit être exclu.

Aussi, je pense que je ne peut pas conclure à la présence d' .NET 4.0.


Mise à JOUR - La bonne réponse, non mentionnés ci-dessus, mais gentiment remarquer par Eric Lippert, est de mettre en œuvre une pleine fidélité type de système d'inférence. Il;s le seul moyen fiable de déterminer le type d'une variable au moment de la conception. Mais, c'est pas aussi facile à faire. Parce que je ne souffrent pas d'illusions que je veux essayer de construire une telle chose, j'ai pris le raccourci de l'option 2 - extrait de la déclaration code et le compiler, puis inspectez la résultante de l'IL.

Cela fonctionne réellement, pour juste un sous-ensemble de l'achèvement des scénarios.

Par exemple, supposons que dans le code suivant fragments, l' ? est la position à laquelle l'utilisateur en fait la demande pour l'achèvement. Cela fonctionne:

var x = "hello there"; 
x.?

L'achèvement se rend compte que x est une Chaîne, et offre les options appropriées. Pour cela, il génère et puis compiler le code source suivant:

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

...et ensuite d'inspecter le IL par simple réflexion.

Cela fonctionne aussi:

var x = new XmlDocument();
x.? 

Le moteur ajoute le à l'aide de clauses pour le code source généré, de sorte qu'il compile correctement, et puis la IL de l'inspection est la même.

Cela fonctionne, trop:

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

Cela signifie simplement IL d'inspection doit trouver le type de la troisième variable locale, au lieu de la première.

Et ce:

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

...ce qui est d'un niveau plus profond que l'état de la exemple.

Mais, ce n'est pas travail est effectué sur toute variable locale dont l'initialisation dépend à un point sur un membre de l'instance, ou d'un argument de méthode. Comme:

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

Ni la syntaxe LINQ.

Je vais devoir réfléchir à la façon de précieuses ces choses sont avant je envisager de lutter contre eux par le biais de ce qui est certainement un "processus de conception" (mot poli pour hack) pour l'achèvement.

Une approche pour aborder la question avec des dépendances sur des arguments de méthode ou les méthodes d'instance serait de remplacer, dans le fragment de code qui est généré, compilé et puis IL a analysé, les références à ces choses "synthétique" local vars du même type.


Une autre mise à Jour - l'achèvement des travaux sur vars qui dépendent des membres de l'instance, fonctionne maintenant.

Ce que j'ai fait a été d'interroger le type (via sémantique), puis de générer synthétique doublure membres de tous les membres existants. Pour un C# tampon comme ceci:

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...

...le code généré qui sera compilé, afin que je puisse apprendre à partir de la sortie IL le type de local var nnn, ressemble à ceci:

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);
      }
  }
}

L'ensemble de l'instance et de la statique des membres du type sont disponibles dans le squelette de code. Il compile correctement. À ce stade, de déterminer le type de local var est simple via la Réflexion.

Ce qui rend cela possible est:

  • la possibilité d'exécuter powershell dans emacs
  • le compilateur C# est vraiment rapide. Sur ma machine, il faut environ 0,5 s pour compiler une mémoire de l'assemblée. Pas assez rapide entre les frappes de l'analyse, mais suffisante pour prendre en charge la génération de la demande de l'achèvement des listes.

Je n'ai pas regardé en LINQ encore.

Qui sera le plus gros problème parce que la sémantique lexer/parser emacs a pour C#, ne pas "faire" de LINQ.

  • Le type de foo est compris et rempli par le compilateur par l'inférence de type. Je soupçonne que les mécanismes sont totalement différentes. Peut-être le type de moteur d'inférence a un crochet? À tout le moins je voudrais utiliser 'inférence de type' comme une balise.
  • Votre technique de faire un "faux" modèle d'objet qui dispose de tous les types, mais aucun de la sémantique des objets réels en est une bonne. Voilà comment j'ai fait IntelliSense pour JScript dans Visual InterDev retour dans la journée; nous faire un "faux" version de l'IE modèle d'objet qui contient toutes les méthodes et les types, mais aucun des effets secondaires, et puis encore un peu d'interprète sur l'analyse de code au moment de la compilation et de voir ce type de revient.