166 votes

Comment passer des types anonymes en tant que paramètres ?

Comment puis-je passer des types anonymes comme paramètres à d'autres fonctions ? Prenons l'exemple suivant :

var query = from employee in employees select new { Name = employee.Name, Id = employee.Id };
LogEmployees(query);

La variable query ici n'a pas de type fort. Comment dois-je définir mon LogEmployees fonction pour l'accepter ?

public void LogEmployees (? list)
{
    foreach (? item in list)
    {

    }
}

En d'autres termes, que dois-je utiliser à la place de ? des marques.

1 votes

Meilleure question dupliquée différente qui traite du passage de paramètres plutôt que du retour de données : stackoverflow.com/questions/16823658/

203voto

Tim S. Points 30377

Je pense que vous devriez créer une classe pour ce type d'anonyme. Ce serait la chose la plus sensée à faire à mon avis. Mais si vous ne voulez vraiment pas le faire, vous pouvez utiliser la dynamique :

public void LogEmployees (IEnumerable<dynamic> list)
{
    foreach (dynamic item in list)
    {
        string name = item.Name;
        int id = item.Id;
    }
}

Notez que c'est pas fortement typée, de sorte que si, par exemple, Name devient EmployeeName, vous ne saurez pas qu'il y a un problème avant l'exécution.

0 votes

J'ai coché cette réponse comme étant correcte, en raison de dynamic l'usage. C'est vraiment très pratique pour moi. Merci :)

2 votes

Je suis d'accord sur le fait qu'une fois que les données commencent à circuler, une manière plus structurée peut/doit normalement être préférée afin de ne pas introduire des bogues difficiles à trouver (vous évitez le système de types). Cependant, si vous voulez trouver un compromis, une autre façon est de simplement passer un dictionnaire générique. Les initialisateurs de dictionnaire C# sont assez pratiques à utiliser de nos jours.

0 votes

Il y a des cas où vous voulez une implémentation générique, et passer des types durs signifie éventuellement changer d'implémentation ou de fabrique, ce qui commence à gonfler le code. Si vous avez une situation vraiment dynamique et que cela ne vous dérange pas de faire un peu de réflexion pour traiter les données que vous recevez, alors c'est parfait. Merci pour la réponse @Tim S.

47voto

Jon Skeet Points 692016

Vous pouvez le faire comme ça :

public void LogEmployees<T>(List<T> list) // Or IEnumerable<T> list
{
    foreach (T item in list)
    {

    }
}

... mais vous ne pourrez pas faire grand-chose avec chaque élément. Vous pouvez appeler ToString, mais vous ne pourrez pas utiliser (disons) Name et Id directement.

2 votes

Sauf que vous pouvez utiliser where T : some type à la fin de la première ligne pour réduire le type. À ce stade, cependant, s'attendre à un certain type d'interface commune serait plus logique que de s'attendre à une interface :)

12 votes

@d_r_w : Tu ne peux pas utiliser where T : some type avec les types anonymes cependant, car ils n'implémentent aucune sorte d'interface...

0 votes

@dlev : Vous ne pouvez pas faire cela, foreach exige que la variable itérée implémente GetEnumerator, et les types anonymes ne le garantissent pas.

19voto

JaredPar Points 333733

Malheureusement, ce que vous essayez de faire est impossible. Sous le capot, la variable de requête est typée pour être un fichier IEnumerable d'un type anonyme. Les noms de types anonymes ne peuvent pas être représentés dans le code utilisateur et il n'y a donc aucun moyen d'en faire un paramètre d'entrée d'une fonction.

Le mieux est de créer un type et de l'utiliser comme retour de la requête, puis de le passer dans la fonction. Par exemple,

struct Data {
  public string ColumnName; 
}

var query = (from name in some.Table
            select new Data { ColumnName = name });
MethodOp(query);
...
MethodOp(IEnumerable<Data> enumerable);

Dans ce cas, cependant, vous ne sélectionnez qu'un seul champ. Il peut donc être plus facile de sélectionner directement le champ. La requête sera alors saisie sous la forme d'un champ de type IEnumerable du type de champ. Dans ce cas, il s'agit du nom de la colonne.

var query = (from name in some.Table select name);  // IEnumerable<string>

0 votes

Mon exemple en était un, mais la plupart du temps, c'est plus. Votre réponse fonctionne (et est assez évidente maintenant). J'avais juste besoin d'une pause déjeuner pour y penser ;-)

0 votes

FYI : fusionné à partir de stackoverflow.com/questions/775387/

0 votes

Un avertissement est que lorsque vous créez une classe propre Equals modifie le comportement. C'est-à-dire que vous devez le mettre en œuvre. (Je connaissais cette divergence mais j'ai quand même réussi à l'oublier lors d'un refactoring).

14voto

Oded Points 271275

Vous ne pouvez pas passer un type anonyme à une fonction non générique, sauf si le type du paramètre est object .

public void LogEmployees (object obj)
{
    var list = obj as IEnumerable(); 
    if (list == null)
       return;

    foreach (var item in list)
    {

    }
}

Les types anonymes sont destinés à être utilisés à court terme dans une méthode.

De MSDN - Types d'anonymes :

Vous ne pouvez pas déclarer un champ, une propriété, un événement ou le type de retour d'une méthode comme ayant un type anonyme. De même, vous ne pouvez pas déclarer un paramètre formel d'une méthode, d'une propriété, d'un constructeur ou d'un indexeur comme ayant un type anonyme. Pour passer un type anonyme, ou une collection qui contient des types anonymes, comme argument d'une méthode, vous pouvez déclarer le paramètre comme type objet . Cependant, cela va à l'encontre de l'objectif du typage fort.

(c'est moi qui souligne)


Mise à jour

Vous pouvez utiliser des génériques pour obtenir ce que vous voulez :

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {

    }
}

4 votes

Si vous ne pouviez pas passer des types anonymes (ou des collections d'un type anonyme) aux méthodes, l'ensemble de LINQ échouerait. Vous pouvez le faire, mais la méthode doit être entièrement générique, sans utiliser les propriétés du type anonyme.

2 votes

Concernant object - ou dynamic ;p

0 votes

Si le casting se fait avec "as", il faut vérifier si la liste est nulle.

8voto

Marc Gravell Points 482669

Normalement, vous faites cela avec des génériques, par exemple :

MapEntToObj<T>(IQueryable<T> query) {...}

Le compilateur doit alors déduire le T quand vous appelez MapEntToObj(query) . Je ne sais pas exactement ce que vous voulez faire à l'intérieur de la méthode, donc je ne peux pas dire si cela est utile... le problème est qu'à l'intérieur de la méthode MapEntToObj vous ne pouvez toujours pas nommer le T - vous pouvez soit :

  • appeler d'autres méthodes génériques avec T
  • utiliser la réflexion sur T pour faire des choses

mais à part cela, il est assez difficile de manipuler les types anonymes - notamment parce qu'ils sont immuables ;-p

Une autre astuce (lorsque en extrayant data) est de passer également un sélecteur - c'est-à-dire quelque chose comme :

Foo<TSource, TValue>(IEnumerable<TSource> source,
        Func<TSource,string> name) {
    foreach(TSource item in source) Console.WriteLine(name(item));
}
...
Foo(query, x=>x.Title);

1 votes

J'ai appris quelque chose de nouveau, je ne savais pas que les types anonymes étaient immuables ;)

1 votes

@AnneLagang cela dépend vraiment du compilateur, puisqu'il les génère. En VB.NET, les anon-types peuvent être mutables.

1 votes

FYI : fusionné à partir de stackoverflow.com/questions/775387/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X