Comment passer des types anonymes comme paramètres?

143

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

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

La variable queryici n'a pas de type fort. Comment définir ma LogEmployeesfonction pour l'accepter?

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

    }
}

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

Saeed Neamati
la source
1
Meilleure question en double différente qui traite de la transmission des paramètres plutôt que du renvoi des données: stackoverflow.com/questions/16823658/...
Rob Church

Réponses:

183

Je pense que vous devriez créer une classe pour ce type anonyme. Ce serait la chose la plus sensée à faire à mon avis. Mais si vous ne voulez vraiment pas, 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 ce n'est pas fortement typé, donc si, par exemple, Name change en EmployeeName, vous ne saurez pas qu'il y a un problème avant l'exécution.

Tim S.
la source
J'ai vérifié cela comme une bonne réponse, en raison de l' dynamicutilisation. J'ai vraiment été utile pour moi. Merci :)
Saeed Neamati
1
Je suis d'accord qu'une fois que les données commencent à être transmises, une manière plus structurée peut / devrait normalement être préférée afin de ne pas introduire de 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 initialiseurs de dictionnaire C # sont assez pratiques à utiliser de nos jours.
Jonas
Il y a des cas où vous voulez une implémentation générique, et le passage de types hard signifie éventuellement une commutation ou une implémentation d'usine qui commence à gonfler le code. Si vous avez une situation vraiment dynamique et que cela ne vous dérange pas de réfléchir un peu pour gérer les données que vous recevez, alors c'est parfait. Merci pour la réponse @Tim S.
Larry Smith
42

Vous pouvez le faire comme ceci:

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 (par exemple) Nameet Iddirectement.

Jon Skeet
la source
2
Sauf que vous pouvez utiliser where T : some typeà la fin de la première ligne pour affiner le type. À ce stade, cependant, s'attendre à un certain type d'interface commune aurait plus de sens d'attendre une interface. :)
CassOnMars
9
@d_r_w: Vous ne pouvez pas utiliser where T : some typeavec des types anonymes, car ils n'implémentent aucun type d'interface ...
Jon Skeet
@dlev: Vous ne pouvez pas faire cela, foreach nécessite que la variable itérée sur implémente GetEnumerator, et les types anonymes ne le garantissent pas.
CassOnMars
1
@Jon Skeet: bon point, mon cerveau est sous-alimenté ce matin.
CassOnMars
1
@JonSkeet. Je suppose que vous pouvez utiliser la réflexion pour toujours accéder / définir les propriétés si T est un type anonyme, non? Je pense à un cas où quelqu'un écrit une instruction "Select * from" et utilise une classe anonyme (ou définie) pour définir quelles colonnes du résultat de la requête mappent aux mêmes propriétés nommées sur votre objet anonyme.
C. Tewalt
19

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

Votre meilleur pari est de créer un type et de l'utiliser comme retour de la requête, puis de le transmettre à 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 le champ directement. Cela entraînera la saisie de la requête en tant que type IEnumerablede champ. Dans ce cas, nom de la colonne.

var query = (from name in some.Table select name);  // IEnumerable<string>
JaredPar
la source
Mon exemple en était un, mais la plupart du temps, c'est plus. Votre réponse à travers des œuvres (et assez évidente maintenant). J'avais juste besoin d'une pause pour le déjeuner pour y réfléchir ;-)
Tony Trembath-Drake
FYI: fusionné de stackoverflow.com/questions/775387/…
Shog9
Une mise en garde est que lorsque vous créez une classe appropriée Equalschange le comportement. Ie vous devez le mettre en œuvre. (Je connaissais cet écart mais j'ai quand même réussi à l'oublier lors d'un refactoring.)
LosManos
11

Vous ne pouvez pas passer un type anonyme à une fonction non générique, sauf si le type de 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 à une utilisation à court terme dans une méthode.

Depuis MSDN - Types 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, en tant qu'argument à une méthode, vous pouvez déclarer le paramètre comme objet de type . Cependant, cela va à l'encontre de l'objectif d'un typage fort.

(c'est moi qui souligne)


Mettre à jour

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

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

    }
}
Oded
la source
4
Si vous ne parveniez pas à transmettre des types anonymes (ou des collections de type anonyme) aux méthodes, l'ensemble de LINQ échouerait. Vous pouvez, c'est juste que la méthode doit être entièrement générique, sans utiliser les propriétés du type anonyme.
Jon Skeet
2
re object- or dynamic; p
Marc Gravell
Si vous lancez avec "as", vous devriez vérifier si la liste est nulle
Alex
"can"! = "avoir à". Utiliser objectn'est pas la même chose que rendre une méthode générique de type anonyme, selon ma réponse.
Jon Skeet
8

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

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

Le compilateur doit alors déduire le Tmoment où vous appelez MapEntToObj(query). Je ne sais pas trop 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 MapEntToObjvous ne pouvez toujours pas nommer le T- vous pouvez non plus:

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

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

Une autre astuce (lors de l' extraction de données) 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);
Marc Gravell
la source
1
J'ai appris quelque chose de nouveau, je ne savais pas que les types anonymes sont immuables! ;)
Annie Lagang
1
@AnneLagang qui dépend vraiment du compilateur, car il les génère. Dans VB.NET, les types anon peuvent être mutables.
Marc Gravell
1
FYI: fusionné de stackoverflow.com/questions/775387/…
Shog9
7

Vous pouvez utiliser des génériques avec l'astuce suivante (conversion en type anonyme):

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {
        var typedItem = Cast(item, new { Name = "", Id = 0 });
        // now you can use typedItem.Name, etc.
    }
}

static T Cast<T>(object obj, T type)
{
    return (T)obj;
}
Stanislav Basovník
la source
6

«dynamique» peut également être utilisé à cette fin.

var anonymousType = new { Id = 1, Name = "A" };

var anonymousTypes = new[] { new { Id = 1, Name = "A" }, new { Id = 2, Name = "B" };

private void DisplayAnonymousType(dynamic anonymousType)
{
}

private void DisplayAnonymousTypes(IEnumerable<dynamic> anonymousTypes)
{
   foreach (var info in anonymousTypes)
   {

   }
}
Dinesh Kumar P
la source
1
C'est la bonne réponse! Il a juste besoin de plus d'amour :)
Korayem
2

Au lieu de passer un type anonyme, passez une List d'un type dynamique:

  1. var dynamicResult = anonymousQueryResult.ToList<dynamic>();
  2. Signature de la méthode: DoSomething(List<dynamic> _dynamicResult)
  3. Méthode d'appel: DoSomething(dynamicResult);
  4. terminé.

Merci à Petar Ivanov !

utileBee
la source
0

Si vous savez que vos résultats implémentent une certaine interface, vous pouvez utiliser l'interface comme type de données:

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

    }
}
Alex
la source
0

J'utiliserais IEnumerable<object>comme type pour l'argument. Cependant, ce n'est pas un grand gain pour l'incontournable casting explicite. À votre santé

Mario Vernari
la source