Renvoyer un type anonyme en C #


100

J'ai une requête qui renvoie un type anonyme et la requête est dans une méthode. Comment écrivez-vous ceci:

public "TheAnonymousType" TheMethod(SomeParameter)
{
  using (MyDC TheDC = new MyDC())
  {
     var TheQueryFromDB = (....
                           select new { SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();

      return "TheAnonymousType";
    }
}

5
Pourquoi voudriez-vous renvoyer un type anonyme? Comment pourriez-vous utiliser ce résultat ailleurs?
Beurk le


5
@Yuck et si vous retournez json ou quelque chose où le type c # n'a pas d'importance
aw04

10
Je ne pense pas que cette question soit hors de propos. J'ai en fait eu besoin de le faire plusieurs fois. C'est plus évident lorsque vous utilisez le framework d'entité et que vous souhaitez effectuer votre requête dans une fonction et utiliser les résultats à plusieurs endroits. J'en ai besoin assez souvent pour afficher les résultats à l'écran et ensuite utiliser les mêmes résultats dans un rapport ou lors de l'exportation vers Excel. La requête peut contenir de nombreux filtres et autres provenant de l'interface utilisateur. vous ne voulez pas vraiment créer la même requête à plusieurs endroits ou vous pouvez facilement vous désynchroniser lorsque vous souhaitez ajouter aux résultats
Kevbo

Réponses:


94

Vous ne pouvez pas.

Vous ne pouvez retourner objectou conteneur d'objets, par exemple IEnumerable<object>, IList<object>etc.


51
Ou dynamic. Cela rend le travail un peu plus facile.
vcsjones

ah ok, donc vous ne pouvez utiliser que des types anonymes dans une méthode mais pas comme valeurs de retour?
frenchie

2
@frenchie: Oui, seulement à l'intérieur du corps du membre. Si vous voulez le retourner, faites-en un type bien connu.
abatishchev

11
Utiliser dynamic n'est pas une solution, les champs de type anonyme ne sont pas publics, ils sont internes.
Hans Passant

7
@HansPassant En supposant que l'appelant est dans le même assembly, cela reste (un peu) utile. Pour ce que ça vaut, les champs sont publics - le type est interne. Je suis généralement dans le camp que vous ne devriez pas renvoyer un type anonyme de toute façon.
vcsjones

42

Vous pouvez retourner dynamicce qui vous donnera une version vérifiée à l'exécution du type anonyme, mais uniquement dans .NET 4+


30

En C # 7, nous pouvons utiliser des tuples pour accomplir ceci:

public List<(int SomeVariable, string AnotherVariable)> TheMethod(SomeParameter)
{
  using (MyDC TheDC = new MyDC())
  {
     var TheQueryFromDB = (....
                       select new { SomeVariable = ....,
                                    AnotherVariable = ....}
                       ).ToList();

      return TheQueryFromDB
                .Select(s => (
                     SomeVariable = s.SomeVariable, 
                     AnotherVariable = s.AnotherVariable))
                 .ToList();
  }
}

Vous devrez peut-être installer le System.ValueTuplepackage nuget.


27

Vous ne pouvez pas renvoyer de types anonymes. Pouvez-vous créer un modèle qui peut être retourné? Sinon, vous devez utiliser un fichier object.

Voici un article écrit par Jon Skeet sur le sujet

Code de l'article:

using System;

static class GrottyHacks
{
    internal static T Cast<T>(object target, T example)
    {
        return (T) target;
    }
}

class CheesecakeFactory
{
    static object CreateCheesecake()
    {
        return new { Fruit="Strawberry", Topping="Chocolate" };
    }

    static void Main()
    {
        object weaklyTyped = CreateCheesecake();
        var stronglyTyped = GrottyHacks.Cast(weaklyTyped,
            new { Fruit="", Topping="" });

        Console.WriteLine("Cheesecake: {0} ({1})",
            stronglyTyped.Fruit, stronglyTyped.Topping);            
    }
}

Ou, voici un autre article similaire

Ou, comme d'autres le commentent, vous pouvez utiliser dynamic


8
Bien sûr, je peux créer un type; Je cherchais à éviter de faire ça.
frenchie

le premier lien est mort, ne sauriez-vous pas s'il a été transféré ailleurs?
Rémi

17

Vous pouvez utiliser la classe Tuple comme substitut à un type anonyme lorsque le retour est nécessaire:

Remarque: Tuple peut avoir jusqu'à 8 paramètres.

return Tuple.Create(variable1, variable2);

Ou, pour l'exemple de l'article original:

public List<Tuple<SomeType, AnotherType>> TheMethod(SomeParameter)
{
  using (MyDC TheDC = new MyDC())
  {
     var TheQueryFromDB = (....
                           select Tuple.Create(..., ...)
                           ).ToList();

      return TheQueryFromDB.ToList();
    }
}

http://msdn.microsoft.com/en-us/library/system.tuple(v=vs.110).aspx


10

Le compilateur C # est un compilateur en deux phases. Dans la première phase, il ne vérifie que les espaces de noms, les hiérarchies de classes, les signatures de méthode, etc. Les corps de méthode ne sont compilés que pendant la deuxième phase.

Les types anonymes ne sont pas déterminés tant que le corps de la méthode n'est pas compilé.

Le compilateur n'a donc aucun moyen de déterminer le type de retour de la méthode lors de la première phase.

C'est la raison pour laquelle les types anonymes ne peuvent pas être utilisés comme type de retour.

Comme d'autres l'ont suggéré si vous utilisez .net 4.0 ou râpe, vous pouvez utiliser Dynamic.

Si j'étais vous, je créerais probablement un type et je renverrais ce type à partir de la méthode. De cette façon, c'est facile pour les futurs programmeurs qui maintiennent votre code et plus lisible.


8

Trois options:

Option 1:

public class TheRepresentativeType {
    public ... SomeVariable {get;set;}
    public ... AnotherVariable {get;set;}
}

public IEnumerable<TheRepresentativeType> TheMethod(SomeParameter)
{
   using (MyDC TheDC = new MyDC())
   {
     var TheQueryFromDB = (....
                           select new TheRepresentativeType{ SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();

     return TheQueryFromDB;
   } 
}

Option 2:

public IEnumerable TheMethod(SomeParameter)
{
   using (MyDC TheDC = new MyDC())
   {
     var TheQueryFromDB = (....
                           select new TheRepresentativeType{ SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();
     return TheQueryFromDB;
   } 
}

vous pouvez l'itérer en tant qu'objet

Option 3:

public IEnumerable<dynamic> TheMethod(SomeParameter)
{
   using (MyDC TheDC = new MyDC())
   {
     var TheQueryFromDB = (....
                           select new TheRepresentativeType{ SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();

     return TheQueryFromDB; //You may need to call .Cast<dynamic>(), but I'm not sure
   } 
}

et vous pourrez l'itérer en tant qu'objet dynamique et accéder directement à leurs propriétés


3

Vous pouvez renvoyer la liste des objets dans ce cas.

public List<object> TheMethod(SomeParameter)
{
  using (MyDC TheDC = new MyDC())
  {
     var TheQueryFromDB = (....
                           select new { SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();

      return TheQueryFromDB ;
    }
}

3

En utilisant C # 7.0, nous ne pouvons toujours pas retourner de types anonymes, mais nous avons un support des types de tuple et nous pouvons donc retourner une collection de tuple( System.ValueTuple<T1,T2>dans ce cas). Actuellement, Tuple types ne sont pas pris en charge dans les arborescences d'expressions et vous devez charger des données en mémoire.

La version la plus courte du code que vous souhaitez peut ressembler à ceci:

public IEnumerable<(int SomeVariable, object AnotherVariable)> TheMethod()
{
    ...

    return (from data in TheDC.Data
        select new { data.SomeInt, data.SomeObject }).ToList()
        .Select(data => (SomeVariable: data.SomeInt, AnotherVariable: data.SomeObject))
}

Ou en utilisant la syntaxe Linq fluide:

return TheDC.Data
    .Select(data => new {SomeVariable: data.SomeInt, AnotherVariable: data.SomeObject})
    .ToList();
    .Select(data => (SomeVariable: data.SomeInt, AnotherVariable: data.SomeObject))

En utilisant C # 7.1, nous pouvons omettre les noms de propriétés de tuple et ils seront déduits de l'initialisation de tuple comme cela fonctionne avec des types anonymes:

select (data.SomeInt, data.SomeObject)
// or
Select(data => (data.SomeInt, data.SomeObject))

2
public List<SomeClass> TheMethod(SomeParameter)
{
  using (MyDC TheDC = new MyDC())
  {
     var TheQueryFromDB = (....
                           select new SomeClass{ SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();

      return TheQueryFromDB.ToList();
    }
}

public class SomeClass{
   public string SomeVariable{get;set}
   public string AnotherVariable{get;set;}
}

Créer votre propre classe et l'interroger est la meilleure solution que je connaisse.Pour autant que je sache, vous ne pouvez pas utiliser de valeurs de retour de type anonyme dans une autre méthode, car elles ne seront pas simplement reconnues.Cependant, elles peuvent être utilisées dans le même méthode. J'avais l'habitude de les renvoyer comme IQueryableou IEnumerable, bien que cela ne vous laisse toujours pas voir ce qu'il y a à l'intérieur de la variable de type anonyme.

J'ai rencontré quelque chose comme ça avant alors que j'essayais de refactoriser du code, vous pouvez le vérifier ici: Refactoring et création de méthodes séparées


2

Avec réflexion.

public object tst() {
    var a = new {
        prop1 = "test1",
        prop2 = "test2"
    };

    return a;
}


public string tst2(object anonymousObject, string propName) {
    return anonymousObject.GetType().GetProperties()
        .Where(w => w.Name == propName)
        .Select(s => s.GetValue(anonymousObject))
        .FirstOrDefault().ToString();
}

Échantillon:

object a = tst();
var val = tst2(a, "prop2");

Production:

test2

1

Vous ne pouvez utiliser que des mots clés dynamiques,

   dynamic obj = GetAnonymousType();

   Console.WriteLine(obj.Name);
   Console.WriteLine(obj.LastName);
   Console.WriteLine(obj.Age); 


   public static dynamic GetAnonymousType()
   {
       return new { Name = "John", LastName = "Smith", Age=42};
   }

Mais avec un mot-clé de type dynamique, vous perdrez la sécurité du temps de compilation, IDE IntelliSense, etc.


0

Une autre option pourrait être d'utiliser automapper: vous serez converti en n'importe quel type à partir de votre objet retourné anonyme tant que les propriétés publiques correspondent. Les points clés sont, renvoyer l'objet, utiliser linq et autommaper. (ou utilisez une idée similaire renvoyant json sérialisé, etc. ou utilisez la réflexion ..)

using System.Linq;
using System.Reflection;
using AutoMapper;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;

namespace UnitTestProject1
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            var data = GetData();

            var firts = data.First();

            var info = firts.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).First(p => p.Name == "Name");
            var value = info.GetValue(firts);

            Assert.AreEqual(value, "One");
        }


        [TestMethod]
        public void TestMethod2()
        {
            var data = GetData();

            var config = new MapperConfiguration(cfg => cfg.CreateMissingTypeMaps = true);
            var mapper = config.CreateMapper();

            var users = data.Select(mapper.Map<User>).ToArray();

            var firts = users.First();

            Assert.AreEqual(firts.Name, "One");

        }

        [TestMethod]
        public void TestMethod3()
        {
            var data = GetJData();


            var users = JsonConvert.DeserializeObject<User[]>(data);

            var firts = users.First();

            Assert.AreEqual(firts.Name, "One");

        }

        private object[] GetData()
        {

            return new[] { new { Id = 1, Name = "One" }, new { Id = 2, Name = "Two" } };
        }

        private string GetJData()
        {

            return JsonConvert.SerializeObject(new []{ new { Id = 1, Name = "One" }, new { Id = 2, Name = "Two" } }, Formatting.None);
        }

        public class User
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }
    }

}

0

Maintenant avec les fonctions locales en particulier, mais vous pouvez toujours le faire en passant un délégué qui rend le type anonyme.

Donc, si votre objectif était d'exécuter une logique différente sur les mêmes sources et de pouvoir combiner les résultats dans une seule liste. Je ne sais pas quelle nuance il manque pour atteindre l'objectif déclaré, mais tant que vous retournez a Tet que vous passez un délégué à faire T, vous pouvez renvoyer un type anonyme à partir d'une fonction.

// returning an anonymous type
// look mom no casting
void LookMyChildReturnsAnAnonICanConsume()
{
    // if C# had first class functions you could do
    // var anonyFunc = (name:string,id:int) => new {Name=name,Id=id};
    var items = new[] { new { Item1 = "hello", Item2 = 3 } };
    var itemsProjection =items.Select(x => SomeLogic(x.Item1, x.Item2, (y, i) => new { Word = y, Count = i} ));
    // same projection = same type
    var otherSourceProjection = SomeOtherSource((y,i) => new {Word=y,Count=i});
    var q =
        from anony1 in itemsProjection
        join anony2 in otherSourceProjection
            on anony1.Word equals anony2.Word
        select new {anony1.Word,Source1Count=anony1.Count,Source2Count=anony2.Count};
    var togetherForever = itemsProjection.Concat(otherSourceProjection).ToList();
}

T SomeLogic<T>(string item1, int item2, Func<string,int,T> f){
    return f(item1,item2);
}
IEnumerable<T> SomeOtherSource<T>(Func<string,int,T> f){
    var dbValues = new []{Tuple.Create("hello",1), Tuple.Create("bye",2)};
    foreach(var x in dbValues)
        yield return f(x.Item1,x.Item2);
}

0

Il est en fait possible de renvoyer un type anonyme à partir d'une méthode dans un cas d'utilisation particulier. Regardons!

Avec C # 7, il est possible de renvoyer des types anonymes à partir d'une méthode, bien que cela s'accompagne d'une légère contrainte. Nous allons utiliser une nouvelle fonctionnalité de langage appelée fonction locale avec une astuce d'indirection (une autre couche d'indirection peut résoudre n'importe quel défi de programmation, non?).

Voici un cas d'utilisation que j'ai récemment identifié. Je veux enregistrer toutes les valeurs de configuration après les avoir chargées depuisAppSettings . Pourquoi? Parce qu'il y a une certaine logique autour des valeurs manquantes qui reviennent aux valeurs par défaut, une certaine analyse et ainsi de suite. Un moyen simple de consigner les valeurs après l'application de la logique consiste à les placer toutes dans une classe et à la sérialiser dans un fichier journal (à l'aide de log4net). Je veux également résumer la logique complexe du traitement des paramètres et la séparer de tout ce que j'ai besoin d'en faire. Le tout sans créer une classe nommée qui n'existe que pour une seule utilisation!

Voyons comment résoudre ce problème en utilisant une fonction locale qui crée un type anonyme.

public static HttpClient CreateHttpClient()
{
    // I deal with configuration values in this slightly convoluted way.
    // The benefit is encapsulation of logic and we do not need to
    // create a class, as we can use an anonymous class.
    // The result resembles an expression statement that
    // returns a value (similar to expressions in F#)
    var config = Invoke(() =>
    {
        // slightly complex logic with default value
        // in case of missing configuration value
        // (this is what I want to encapsulate)
        int? acquireTokenTimeoutSeconds = null;
        if (int.TryParse(ConfigurationManager.AppSettings["AcquireTokenTimeoutSeconds"], out int i))
        {
            acquireTokenTimeoutSeconds = i;
        }

        // more complex logic surrounding configuration values ...

        // construct the aggregate configuration class as an anonymous type!
        var c = new
        {
            AcquireTokenTimeoutSeconds =
                acquireTokenTimeoutSeconds ?? DefaultAcquireTokenTimeoutSeconds,
            // ... more properties
        };

        // log the whole object for monitoring purposes
        // (this is also a reason I want encapsulation)
        Log.InfoFormat("Config={0}", c);
        return c;
    });

    // use this configuration in any way necessary...
    // the rest of the method concerns only the factory,
    // i.e. creating the HttpClient with whatever configuration
    // in my case this:
    return new HttpClient(...);

    // local function that enables the above expression
    T Invoke<T>(Func<T> func) => func.Invoke();
}

J'ai réussi à construire une classe anonyme et également à encapsuler la logique de gestion de paramètres complexes, le tout à l'intérieur CreateHttpClientet à l'intérieur de sa propre «expression». Ce n'est peut-être pas exactement ce que l'OP souhaitait, mais c'est une approche légère avec des types anonymes qui est actuellement possible dans le C # moderne.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.