Retornando tipo anônimo em C #


100

Tenho uma consulta que retorna um tipo anônimo e a consulta está em um método. Como você escreve isso:

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

      return "TheAnonymousType";
    }
}

5
Por que você deseja retornar um tipo anônimo? Como você poderia usar esse resultado em qualquer outro lugar?
Eca,


5
@ Yuck, e se você estiver retornando json ou algo em que o tipo c # não importa
aw04

10
Não acho que essa pergunta seja absurda. Na verdade, precisei fazer isso várias vezes. É mais evidente ao usar a estrutura de entidade e você deseja fazer sua consulta em uma função e usar os resultados em vários lugares. Eu preciso disso com freqüência ao exibir os resultados na tela e, em seguida, precisar usar os mesmos resultados em um relatório ou ao exportar para o Excel. A consulta pode conter muitos filtros da IU. você realmente não deseja criar a mesma consulta em vários lugares ou pode facilmente ficar fora de sincronia quando quiser adicionar aos resultados
Kevbo

Respostas:


94

Você não pode.

Você só pode voltar object, ou recipiente de objetos, por exemplo IEnumerable<object>, IList<object>, etc.


51
Ou dynamic. Isso torna um pouco mais fácil de trabalhar.
vcsjones

ah ok, então você só pode usar tipos anônimos dentro de um método, mas não como valores de retorno?
francesa,

2
@frenchie: Sim, apenas dentro do corpo do membro. Se você quiser devolvê-lo - torne-o um tipo bem conhecido.
abatishchev,

11
Usar dynamic não é uma solução, os campos de um tipo anônimo não são públicos, são internos.
Hans Passant

7
@HansPassant Supondo que o chamador esteja no mesmo assembly, ainda é (um pouco) útil. Para valer a pena, os campos são públicos - o tipo é interno. Geralmente acredito que você não deveria retornar um tipo anônimo de qualquer maneira.
vcsjones,

42

Você pode retornar, o dynamicque lhe dará uma versão verificada em tempo de execução do tipo anônimo, mas apenas no .NET 4+


30

No C # 7, podemos usar tuplas para fazer isso:

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

Você pode precisar instalar o System.ValueTuplepacote nuget.


27

Você não pode retornar tipos anônimos. Você pode criar um modelo que pode ser devolvido? Caso contrário, você deve usar um object.

Aqui está um artigo escrito por Jon Skeet sobre o assunto

Código do artigo:

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, aqui está outro artigo semelhante

Ou, como outras pessoas estão comentando, você pode usar dynamic


8
Claro que posso criar um tipo; Eu estava tentando evitar fazer isso.
francesa,

o primeiro link está morto, você não saberia se ele foi transferido para outro lugar?
Rémi

17

Você pode usar a classe Tuple como um substituto para tipos anônimos quando o retorno for necessário:

Nota: Tupla pode ter até 8 parâmetros.

return Tuple.Create(variable1, variable2);

Ou, para o exemplo da postagem 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

O compilador C # é um compilador de duas fases. Na primeira fase, ele apenas verifica os namespaces, hierarquias de classes, assinaturas de métodos, etc. Os corpos dos métodos são compilados apenas durante a segunda fase.

Tipos anônimos não são determinados até que o corpo do método seja compilado.

Portanto, o compilador não tem como determinar o tipo de retorno do método durante a primeira fase.

Essa é a razão pela qual os tipos anônimos não podem ser usados ​​como tipo de retorno.

Como outros sugeriram, se você estiver usando .net 4.0 ou ralador, você pode usar Dynamic.

Se eu fosse você, provavelmente criaria um tipo e retornaria esse tipo do método. Dessa forma, é fácil para os futuros programadores que mantêm seu código e mais legível.


8

Três opções:

Opção 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;
   } 
}

Opção 2:

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

você pode iterá-lo como um objeto

Opção 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 você poderá iterá-lo como um objeto dinâmico e acessar suas propriedades diretamente


3

Você pode retornar uma lista de objetos neste caso.

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

      return TheQueryFromDB ;
    }
}

3

Usando o C # 7.0 , ainda não podemos retornar tipos anônimos, mas temos suporte para tipos de tupla e, portanto, podemos retornar uma coleção de tuple( System.ValueTuple<T1,T2>neste caso). Atualmente Tuple types não são suportados em árvores de expressão e você precisa carregar dados na memória.

A versão mais curta do código que você deseja pode ter a seguinte aparência:

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 usando a sintaxe fluente do 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 , podemos omitir nomes de propriedades de tupla e eles serão inferidos da inicialização de tupla como funciona com tipos anônimos:

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

Criar sua própria classe e consultá-la é a melhor solução que conheço. Por mais que eu saiba, você não pode usar valores de retorno de tipo anônimo em outro método, porque ele não será apenas reconhecido. No entanto, eles podem ser usados ​​no mesmo método. Eu costumava retorná-los como IQueryableou IEnumerable, embora ainda não permita que você veja o que está dentro da variável de tipo anônimo.

Já encontrei algo assim antes, enquanto tentava refatorar algum código, você pode verificar aqui: Refatorando e criando métodos separados


2

Com reflexão.

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

Amostra:

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

Resultado:

test2

1

Você só pode usar palavras-chave dinâmicas,

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

Mas com a palavra-chave do tipo dinâmico você perderá a segurança do tempo de compilação, IDE IntelliSense etc ...


0

Outra opção poderia ser usar o automapper: você converterá para qualquer tipo de seu objeto retornado anônimo, desde que as propriedades públicas correspondam. Os pontos principais são, retornar objeto, usar linq e autommaper. (ou use uma ideia semelhante retornando json serializado, etc. ou use reflexão ..)

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

Agora especialmente com funções locais, mas você sempre pode fazer isso passando um delegado que torna o tipo anônimo.

Portanto, se seu objetivo era executar lógicas diferentes nas mesmas fontes e ser capaz de combinar os resultados em uma única lista. Não tenho certeza de qual nuance está faltando para atingir a meta declarada, mas contanto que você retorne um Te passe um delegado para fazer T, você pode retornar um tipo anônimo de uma função.

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

Na verdade, é possível retornar um tipo anônimo de um método em um caso de uso específico. Vamos dar uma olhada!

Com C # 7, é possível retornar tipos anônimos de um método, embora venha com uma pequena restrição. Vamos usar um novo recurso de linguagem chamado função local junto com um truque de indireção (outra camada de indireção pode resolver qualquer desafio de programação, certo?).

Aqui está um caso de uso que identifiquei recentemente. Quero registrar todos os valores de configuração depois de carregá-los de AppSettings. Por quê? Porque há alguma lógica em torno dos valores ausentes que revertem para os valores padrão, alguma análise e assim por diante. Uma maneira fácil de registrar os valores após aplicar a lógica é colocá-los todos em uma classe e serializá-los em um arquivo de log (usando log4net). Também quero encapsular a lógica complexa de lidar com configurações e separar isso de tudo o que preciso fazer com elas. Tudo sem criar uma classe nomeada que existe apenas para um único uso!

Vamos ver como resolver isso usando uma função local que cria um tipo anônimo.

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

Consegui construir uma classe anônima e também encapsular a lógica de lidar com o gerenciamento de configurações complexas, tudo dentro CreateHttpCliente dentro de sua própria "expressão". Isso pode não ser exatamente o que o OP queria, mas é uma abordagem leve com tipos anônimos que atualmente é possível no C # moderno.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.