Une des grandes nouveautés de C# 3 est la possibilité des créer des types anonymes. Un type anonyme offre la possibilité de composer des types « temporaires » avec une ou plusieurs propriétés en lecture seule. Cette nouveauté s’avère extrêmement efficace avec Linq car on va pouvoir interroger des sources de données et récupérer uniquement les informations dont on a besoin au travers d’objets anonymes.

Durant ce petit article, nous verrons très brièvement ce qu’est un type anonyme pour ensuite tenter de renvoyer un type anonyme à partir d’une méthode.


Prenons un exemple simple qui va être de récupérer la liste des processus triés par nom.

var query = from proc in System.Diagnostics.Process.GetProcesses()
            orderby proc.ProcessName ascending
            select proc;

foreach (var item in query)
{
    Console.WriteLine(« {0}\t {1} », item.Id, item.ProcessName);
}

Évidemment, on est forcé de récupérer un objet de type « System.Diagnostics.Process » avec toutes les informations qui l’accompagne alors que dans notre cas, le nom de processus et son id nous auraient suffit. C’est là qu’intervient la notion de type anonyme :

var query = from proc in System.Diagnostics.Process.GetProcesses()
            orderby proc.ProcessName ascending
            select new { proc.Id, proc.ProcessName };

foreach (var item in query)
{
    Console.WriteLine(« {0}\t {1} », item.Id, item.ProcessName);
}

Comme vous pouvez le voir, un type anonyme est créé en utilisant l’opérateur new suivi d’accolades pour initialiser les différentes propriétés que l’on désire voir apparaître dans son objet anonyme. Dans la boucle foreach, on s’aperçoit que « item » propose bien deux propriétés « Id » et « ProcessName« . Chose importante à noter, « item » représente un type anonyme, il n’est donc pas possible de le nommer… Il faut donc passer par l’utilisation du mot clé var qui déduira le type automatiquement.

Pour vous en convaincre que « item » n’est pas de type « System.Diagnostics.Process« , voici la preuve en image :

image

La compilateur a donc effectivement créé un type avec les propriétés adéquates. Le type de ces mêmes propriétés a été au passage inféré grâce à l’initialisation. Bien sûr, rien ne vous empêche de nommer vos noms de propriétés comme vous le désirez :

var query = from proc in System.Diagnostics.Process.GetProcesses()
            orderby proc.ProcessName ascending
            select new { ProcessID = proc.Id, Name = proc.ProcessName };

foreach (var item in query)
{
    Console.WriteLine(« {0}\t {1} », item.ProcessID, item.Name);
}

Si on jette un petit coup d’oeil au niveau du code compilé avec Reflector (http://www.red-gate.com/products/reflector/), on remarque qu’une classe générique a été créée automatiquement pour représenter notre type anonyme.

image

Cette nouveauté de types anonymes n’est donc possible que grâce à un changement du compilateur C#, le CLR (Common Language Runtime) n’a en aucun cas été modifié.

Maintenant que vous avons mis les choses à plat sur ce qu’est un type anonyme, penchons nous sur le sujet de ce billet et qui est de savoir comment il est possible de renvoyer un type anonyme à partir d’une méthode.

Comme je l’ai dit tout à l’heure, le type anonyme n’ayant pas de nom, on est obligé de passer par le mot clé var pour manipuler de tels objets. Si on désire encapsuler dans une méthode notre code qui permet de récupérer cette fois-ci le premier processus, cette méthode sera obligée de renvoyer un objet de type « System.Object« . Effectivement, peu importe qu’on ait un type anonyme ou pas, tous dérive de la classe « System.Object« .

static System.Object GetProcesses()
{
    return (from proc in System.Diagnostics.Process.GetProcesses()
            orderby proc.ProcessName ascending
            select new { ProcessID = proc.Id, Name = proc.ProcessName }).First();
}

Seulement petit problème, on perd l’intellisense en ayant converti notre type anonyme en « System.Object« , donc impossibilité de récupérer les valeurs dans les propriétés « ProcessID » et « Name« . Solution que vous allez de suite me proposer : La réflexion ! Et vous avez raison sur le point que ça fonctionnera mais niveau performance, on sera loin… En fait, il y a une solution beaucoup plus simple et c’est en allant voir le code compilé que nous en aurons la preuve !

Tout d’abord, nous avons notre objet anonyme de la méthode « GetProcesses » qui est composé des éléments suivants :

  • Une propriété « ProcessID » de type System.Int32
  • Une propriété « Name » de type System.String

Maintenant, nous allons faire un test en créant exactement le même type anonyme mais en dehors de cette méthode, par exemple dans la méthode « Main » :

var anonymousType = new { ProcessID = 0, Name = «  » };

Et si on vérifie le code compilé, on s’aperçoit que le compilateur a détecté que dans notre programme, il y avait deux types anonymes exactement les mêmes (même noms de propriétés, dans le même ordre et avec le même type) : il a donc tout simplement généré une et une seule classe pour ces deux types anonymes identiques.

A partir de cette idée, on peut très facilement implémenter une méthode générique qui prendra en paramètres :

  • L’instance de notre objet anonyme de type « System.Object« 
  • Et une autre instance « bidon » de ce même type mais passée de manière générique

Une simple conversion et le tour est joué !

class Program
{
    static void Main(string[] args)
    {
        var result = CastAnonymousObject(GetProcesses(), new { ProcessID = 0, Name = «  » });
        Console.WriteLine(« {0}\t {1} », result.ProcessID, result.Name);
        Console.Read();
    }

    static System.Object GetProcesses()
    {
        return (from proc in System.Diagnostics.Process.GetProcesses()
                orderby proc.ProcessName ascending
                select new { ProcessID = proc.Id, Name = proc.ProcessName }).First();
    }

    static A CastAnonymousObject<A>(object anomymousObject, A anonymousType)
    {
        return (A)anomymousObject;
    }
}

Simple non ?