Devolver tipo anónimo en C #


99

Tengo una consulta que devuelve un tipo anónimo y la consulta está en un método. ¿Cómo escribes esto?

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

      return "TheAnonymousType";
    }
}

5
¿Por qué querría devolver un tipo anónimo? ¿Cómo podrías usar ese resultado en otro lugar?
Yuck


5
@Yuck, ¿qué pasa si devuelve json o algo donde el tipo c # no importa?
aw04

10
No creo que esta pregunta esté fuera de razón. De hecho, he necesitado hacer esto varias veces. Es más evidente cuando se usa el marco de la entidad y desea hacer su consulta en una función y usar los resultados en varios lugares. Necesito esto con bastante frecuencia cuando visualizo los resultados en la pantalla y luego necesito usar los mismos resultados en un informe o al exportar a Excel. La consulta puede contener muchos filtros y demás de la interfaz de usuario. realmente no desea crear la misma consulta en varios lugares o puede desincronizarse fácilmente cuando desea agregar a los resultados
Kevbo

Respuestas:


94

No puedes.

Sólo puede volver object, o contenedor de objetos, por ejemplo IEnumerable<object>, IList<object>, etc.


51
O dynamic. Eso hace que sea un poco más fácil trabajar con él.
vcsjones

ah ok, entonces solo puedes usar tipos anónimos dentro de un método pero no como valores de retorno?
frenchie

2
@frenchie: Sí, solo dentro del cuerpo del miembro. Si desea devolverlo, conviértalo en un tipo conocido.
abatishchev

11
Usar dinámico no es una solución, los campos de tipo anónimo no son públicos, son internos.
Hans Passant

7
@HansPassant Suponiendo que la persona que llama está en el mismo ensamblado, todavía es (algo) útil. Por lo que vale, los campos son públicos, el tipo es interno. Por lo general, estoy en el campamento de que no deberías devolver un tipo anónimo de todos modos.
vcsjones

42

Puede regresar, lo dynamicque le dará una versión verificada en tiempo de ejecución del tipo anónimo, pero solo en .NET 4+


30

En C # 7 podemos usar tuplas para lograr esto:

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

System.ValueTupleSin embargo, es posible que deba instalar el paquete nuget.


27

No puede devolver tipos anónimos. ¿Puedes crear un modelo que se pueda devolver? De lo contrario, debe utilizar un object.

Aquí hay un artículo escrito por Jon Skeet sobre el tema.

Código del artículo:

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

O aquí hay otro artículo similar

O, como otros comentan, puede usar dynamic


8
Por supuesto que puedo crear un tipo; Estaba buscando evitar hacer esto.
frenchie

el primer enlace está muerto, ¿no sabría usted si se ha transferido a otro lugar?
Rémi

17

Puede usar la clase Tuple como sustituto de tipos anónimos cuando sea necesario regresar:

Nota: Tuple puede tener hasta 8 parámetros.

return Tuple.Create(variable1, variable2);

O, para el ejemplo de la publicación 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

El compilador de C # es un compilador de dos fases. En la primera fase, solo verifica los espacios de nombres, jerarquías de clases, firmas de métodos, etc. Los cuerpos de los métodos se compilan solo durante la segunda fase.

Los tipos anónimos no se determinan hasta que se compila el cuerpo del método.

Por lo tanto, el compilador no tiene forma de determinar el tipo de retorno del método durante la primera fase.

Esa es la razón por la que los tipos anónimos no se pueden utilizar como tipo de retorno.

Como han sugerido otros, si está usando .net 4.0 o rallador, puede usar Dynamic.

Si yo fuera usted, probablemente crearía un tipo y devolvería ese tipo del método. De esa manera es fácil para los futuros programadores que mantienen su código y más legible.


8

Tres opciones:

Opción 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;
   } 
}

Opcion 2:

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

puedes iterarlo como objeto

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

y podrás iterarlo como un objeto dinámico y acceder a sus propiedades directamente


3

Puede devolver la lista de objetos en este caso.

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

      return TheQueryFromDB ;
    }
}

3

Usando C # 7.0 todavía no podemos devolver tipos anónimos pero tenemos soporte para tipos de tupla y, por lo tanto, podemos devolver una colección de tuple( System.ValueTuple<T1,T2>en este caso). Actualmente Tuple types no se admiten en árboles de expresión y necesita cargar datos en la memoria.

La versión más corta del código que desea puede verse así:

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 sintaxis fluida de 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 los nombres de las propiedades de la tupla y se deducirán de la inicialización de la tupla como funciona con 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;}
}

Crear su propia clase y consultarla es la mejor solución que conozco. Por lo que sé, no puede usar valores de retorno de tipo anónimo en otro método, porque no solo será reconocido. Sin embargo, se pueden usar en el mismo. método. Solía ​​devolverlos como IQueryableo IEnumerable, aunque todavía no te permite ver qué hay dentro de la variable de tipo anónimo.

Me encontré con algo como esto antes mientras intentaba refactorizar un código, puede verificarlo aquí: Refactorización y creación de métodos separados


2

Con reflexión.

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

Muestra:

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

Salida:

test2

1

Solo puede utilizar palabras clave 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};
   }

Pero con la palabra clave de tipo dinámico perderá seguridad en el tiempo de compilación, IDE IntelliSense, etc.


0

Otra opción podría ser el uso de automapper: convertirá a cualquier tipo de su objeto devuelto anónimo a medida que las propiedades públicas largas coincidan. Los puntos clave son, devolver el objeto, usar linq y autommaper. (o use una idea similar que devuelva json serializado, etc. o use la reflexión ..)

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

Ahora con funciones locales especialmente, pero siempre puede hacerlo pasando un delegado que haga el tipo anónimo.

Entonces, si su objetivo era ejecutar una lógica diferente en las mismas fuentes y poder combinar los resultados en una sola lista. No estoy seguro de qué matiz falta para cumplir con el objetivo establecido, pero siempre que devuelva un Ty pase un delegado para hacer T, puede devolver un tipo anónimo de una función.

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

De hecho, es posible devolver un tipo anónimo de un método en un caso de uso particular. ¡Echemos un vistazo!

Con C # 7 es posible devolver tipos anónimos de un método, aunque viene con una ligera restricción. Vamos a utilizar una nueva característica del lenguaje llamada función local junto con un truco de indirección (otra capa de indirección puede resolver cualquier desafío de programación, ¿verdad?).

Aquí hay un caso de uso que identifiqué recientemente. Quiero registrar todos los valores de configuración después de haberlos cargado AppSettings. ¿Por qué? Porque hay cierta lógica en torno a los valores perdidos que vuelven a los valores predeterminados, algunos análisis, etc. Una forma fácil de registrar los valores después de aplicar la lógica es ponerlos todos en una clase y serializarlos en un archivo de registro (usando log4net). También quiero encapsular la lógica compleja de lidiar con las configuraciones y separar eso de lo que tenga que hacer con ellas. ¡Todo sin crear una clase con nombre que exista para un solo uso!

Veamos cómo resolver esto usando una función local que crea un 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();
}

He logrado construir una clase anónima y también encapsular la lógica de lidiar con la gestión de entornos complejos, todo dentro CreateHttpClienty dentro de su propia "expresión". Puede que esto no sea exactamente lo que quería el OP, pero es un enfoque ligero con tipos anónimos que actualmente es posible en C # moderno.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.