Um método pode retornar um tipo anônimo?

Os tipos anônimos (Anonymous Types) são uma característica da linguagem C# onde criamos diversas propriedades para um objeto sem ter que definir explicitamente seu tipo. Essa novidade surgiu na versão 3.0 junto com diversas outras tais como: propriedades automáticas, inferência de tipos, inicializadores de objetos, expressões lambda, métodos de extensão e outros.

Um tipo anônimo pode ser declarado da seguinte maneira:

var c = new { Nome = "Ari", Idade = 28 };

Neste caso a variável c é um tipo de possui duas propriedades: uma propriedade do tipo System.String chamada Nome e uma propriedade do tipo System.Int32 chamada Idade.

Os tipos anônimos são muito úteis quando realizamos consultas usando o LINQ pois normalmente temos que retornar um tipo com um conjunto de propriedades diferente dos tipos já existentes. Por exemplo, vejam as duas consultas abaixo:

var q1 = from c in dc.Customers
         where c.City == "London"
         select new { c.FirstName, c.LastName, c.City };

var q2 = from c in dc.Customers
         where c.City == "London"
         select new { c.FirstName, c.City };

A primeira consulta retorna um tipo que contém 3 propriedades (FirstName, LastName e City) e a segunda um tipo com 2 propriedades (FirstName e City). Sem os tipos anônimos teríamos que definir explicitamente duas classes para representar os tipos retornados.

Mas, se o tipo é anônimo, como podemos fazer para um método retorná-lo?

public ???? ListCustomers()
{
    var q1 = from c in dc.Customers
             where c.City == "London"
             select new { c.FirstName, c.LastName, c.City };

    return q1;
}

Bom, podemos resolver essa questão pensando da seguinte maneira: se um tipo anônimo é um tipo do .NET, então esse herda de System.Object, vamos então retornar um object.

public object ListCustomers() { ... }

Esse raciocínio está correto, o problema é que transformando para object perdemos a possibilidade de utilizarmos as propriedades do tipo anônimo diretamente (strong typing).

var c = ListCustomers();
c.FirstName = "Anders"; // erro

Bom, para resolver esse problema temos 3 possíveis soluções:

  1. Reflection.
  2. Dynamic Programming.
  3. Realizar um cast do object com o tipo anônimo.

A primeira opção é lenta e muito código deve ser criado, portanto será descartada. A segunda opção é interessante mas vou limitar meu escopo com as versões anteriores à 4.0. Vou mostrar então a terceira opção.

Para realizarmos um cast de um object para um tipo anônimo podemos, com auxílio de Generics, criar o método abaixo:

public T CastObjectToType<T>(object objeto, T tipo)
{
    return (T)objeto;
}

Podemos então converter o objeto em um tipo anônimo da seguinte maneira:

object o = ListCustomers();
var c = CastObjectToType(o, new { FirstName = String.Empty, 
                                  LastName = String.Empty, 
                                  City = String.Empty});
c.FirstName = "Anders";  // OK

Isso é possível graças à inferência de tipo, que nos permite fazer um cast sem conhecer o nome do tipo. É o que foi realizado no método CastObjectToType.

Finalizado, a resposta é SIM. Podemos retornar um tipo anônimo de um método.

Links de ajuda:

Anonymous Types (C# Programming Guide)
http://bit.ly/69HYJd

Generic Methods (C# Programming Guide)
http://bit.ly/6wFbas

Getting Started with LINQ in C#
http://bit.ly/4BOIUU

6 comentários:

  1. Gostei, põe isso no codeproject (se é que não está ainda)

    ResponderExcluir
  2. Henrry,
    No CodeProject já existem alguns artigos parecidos.

    Converting anonymous types to any type
    http://bit.ly/b856Z7

    Casting and Passing Anonymous Types
    http://bit.ly/aFlt1f

    Obrigado pelo feedback...
    Um abraço.

    ResponderExcluir
  3. Oi, muito bom o artigo, gostaria de saber como fazer o mesmo com a segunda opção (Dynamic Programming).

    ResponderExcluir
  4. Olá Ari,

    Apenas um comentário.

    No caso do exemplo, o que ocorre é um upcast (para object) e um downcast (para o anonymous type). Isto é necessário, pois o anonymous type não é exportado publicamente. Mas fazendo assim você paga um preço de tempo de execução.

    Então este caso seria mais eficiente vc ter um tipo não anonimo para ser exportado e resolvido em tempo de compilação.

    class TipoNaoAnonimo //Escolher Um Nome Melhor
    {
    public string FirstName{ get; internal set; }
    public string LastName{ get; internal set; }
    public string City { get; internal set; }
    }

    e vc faz a projeção a partir deste tipo.

    public IEnumerable ListCustomers()
    {
    var q1 = from c in dc.Customers
    where c.City == "London"
    select new TipoNaoAnonimo{ c.FirstName, c.LastName, c.City };

    return q1;
    }

    e seu respectivo consumo

    var c = ListCustomers().First();
    c.FirstName = "Anders"; // OK

    ResponderExcluir