Restituzione del tipo anonimo in C #


100

Ho una query che restituisce un tipo anonimo e la query è in un metodo. Come scrivi questo:

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

      return "TheAnonymousType";
    }
}

5
Perché vorresti restituire un tipo anonimo? Come potresti usare quel risultato altrove?
Yuck


5
@Yuck e se restituissi json o qualcosa in cui il tipo c # non ha importanza
aw04

10
Non credo che questa domanda sia fuori ragione. In realtà ho dovuto farlo più volte. È più evidente quando si utilizza il framework di entità e si desidera eseguire la query in una funzione e utilizzare i risultati in diversi punti. Ne ho bisogno abbastanza spesso quando si visualizzano i risultati sullo schermo e quindi è necessario utilizzare gli stessi risultati in un report o durante l'esportazione in Excel. La query può contenere molti filtri e simili dall'interfaccia utente. non vuoi davvero creare la stessa query in più posti o puoi facilmente uscire dalla sincronizzazione quando vuoi aggiungere ai risultati
Kevbo

Risposte:


94

Non puoi.

È possibile restituire solo object, o di contenitore di oggetti, ad esempio IEnumerable<object>, IList<object>e così via


51
Oppure dynamic. Ciò lo rende leggermente più facile da lavorare.
vcsjones

ah ok, quindi puoi usare solo tipi anonimi all'interno di un metodo ma non come valori di ritorno?
Frenchie

2
@frenchie: Sì, solo all'interno del corpo del membro. Se vuoi restituirlo, rendilo un tipo noto.
abatishchev

11
L'utilizzo della dinamica non è una soluzione, i campi di tipo anonimo non sono pubblici, sono interni.
Hans Passant

7
@HansPassant Supponendo che il chiamante sia nello stesso assembly, allora è ancora (in qualche modo) utile. Per quel che vale, i campi sono pubblici - il tipo è interno. In genere sono convinto che non dovresti comunque restituire un tipo anonimo.
vcsjones

42

Puoi restituire dynamicche ti darà una versione controllata in esecuzione del tipo anonimo ma solo in .NET 4+


30

In C # 7 possiamo usare le tuple per ottenere ciò:

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

Potrebbe essere necessario installare il System.ValueTuplepacchetto nuget.


27

Non puoi restituire tipi anonimi. Puoi creare un modello che possa essere restituito? Altrimenti, devi usare un file object.

Ecco un articolo scritto da Jon Skeet sull'argomento

Codice dall'articolo:

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

Oppure, ecco un altro articolo simile

Oppure, come altri stanno commentando, potresti usare dynamic


8
Ovviamente posso creare un tipo; Stavo cercando di evitare di farlo.
Frenchie

il primo anello è morto, non sapresti per caso se è stato trasferito altrove?
Rémi

17

È possibile utilizzare la classe Tuple come sostituto di tipi anonimi quando è necessario restituire:

Nota: la tupla può avere fino a 8 parametri.

return Tuple.Create(variable1, variable2);

Oppure, per l'esempio dal post originale:

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

Il compilatore C # è un compilatore a due fasi. Nella prima fase controlla solo gli spazi dei nomi, le gerarchie di classi, le firme dei metodi, ecc. I corpi dei metodi vengono compilati solo durante la seconda fase.

I tipi anonimi non vengono determinati fino a quando non viene compilato il corpo del metodo.

Quindi il compilatore non ha modo di determinare il tipo di ritorno del metodo durante la prima fase.

Questo è il motivo per cui i tipi anonimi non possono essere utilizzati come tipo restituito.

Come altri hanno suggerito se stai usando .net 4.0 o una versione successiva, puoi usare Dynamic.

Se fossi in te, probabilmente creerei un tipo e restituirei quel tipo dal metodo. In questo modo è facile per i futuri programmatori che mantengono il codice e sono più leggibili.


8

Tre opzioni:

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

Opzione 2:

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

puoi iterarlo come oggetto

Opzione 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
   } 
}

e sarai in grado di iterarlo come oggetto dinamico e accedere direttamente alle loro proprietà


3

In questo caso puoi restituire l'elenco di oggetti.

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

      return TheQueryFromDB ;
    }
}

3

Utilizzando C # 7.0 non possiamo ancora restituire tipi anonimi, ma abbiamo un supporto per i tipi di tupla e quindi possiamo restituire una raccolta di tuple( System.ValueTuple<T1,T2>in questo caso). Attualmente Tuple types non sono supportati negli alberi delle espressioni ed è necessario caricare i dati in memoria.

La versione più breve del codice che desideri potrebbe essere simile a questa:

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

O usando la sintassi fluente di Linq:

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

Usando C # 7.1 possiamo omettere i nomi delle proprietà della tupla e verranno dedotti dall'inizializzazione della tupla come funziona con i tipi anonimi:

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

Creare la tua classe e interrogarla è la soluzione migliore che conosco Per quanto io sappia, non puoi usare valori di ritorno di tipo anonimo in un altro metodo, perché non sarà solo riconosciuto.Tuttavia, possono essere usati nello stesso metodo. Li restituivo come IQueryableo IEnumerable, anche se ancora non ti consente di vedere cosa c'è all'interno della variabile di tipo anonimo.

Mi sono imbattuto in qualcosa di simile prima mentre stavo cercando di refactoring del codice, puoi controllarlo qui: Refactoring e creazione di metodi separati


2

Con la riflessione.

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

Campione:

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

Produzione:

test2

1

Puoi utilizzare solo parole chiave dinamiche,

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

Ma con la parola chiave di tipo dinamico perderai la sicurezza del tempo di compilazione, IDE IntelliSense ecc ...


0

Un'altra opzione potrebbe essere l'utilizzo di automapper: convertirai in qualsiasi tipo dal tuo oggetto restituito anonimo finché le proprietà pubbliche corrispondono. I punti chiave sono, restituire l'oggetto, usare linq e autommaper. (o usa un'idea simile restituendo json serializzato, ecc. o usa la riflessione ..)

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

Ora soprattutto con le funzioni locali, ma puoi sempre farlo passando un delegato che rende il tipo anonimo.

Quindi, se il tuo obiettivo era eseguire una logica diversa sulle stesse fonti ed essere in grado di combinare i risultati in un unico elenco. Non sono sicuro di quale sfumatura manchi per raggiungere l'obiettivo dichiarato, ma finché restituisci a Te passi un delegato a make T, puoi restituire un tipo anonimo da una funzione.

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

È effettivamente possibile restituire un tipo anonimo da un metodo in un particolare caso d'uso. Diamo un'occhiata!

Con C # 7 è possibile restituire tipi anonimi da un metodo, sebbene venga fornito con un leggero vincolo. Useremo una nuova funzionalità del linguaggio chiamata funzione locale insieme a un trucco di riferimento indiretto (un altro livello di riferimento indiretto può risolvere qualsiasi problema di programmazione, giusto?).

Ecco un caso d'uso che ho identificato di recente. Voglio registrare tutti i valori di configurazione dopo averli caricati da AppSettings. Perché? Perché c'è una logica intorno ai valori mancanti che tornano ai valori predefiniti, alcuni analisi e così via. Un modo semplice per registrare i valori dopo aver applicato la logica è metterli tutti in una classe e serializzarli in un file di log (usando log4net). Voglio anche incapsulare la complessa logica della gestione delle impostazioni e separarla da qualsiasi cosa io debba fare con esse. Tutto senza creare una classe denominata che esiste solo per un singolo utilizzo!

Vediamo come risolvere questo problema utilizzando una funzione locale che crea un tipo anonimo.

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

Sono riuscito a costruire una classe anonima e ho anche incapsulato la logica di affrontare la gestione di impostazioni complesse, tutto all'interno CreateHttpCliente all'interno della propria "espressione". Questo potrebbe non essere esattamente ciò che voleva l'OP, ma è un approccio leggero con tipi anonimi che è attualmente possibile nel moderno C #.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.