Zwracanie typu anonimowego w C #


100

Mam zapytanie, które zwraca typ anonimowy, a zapytanie jest w metodzie. Jak to piszesz:

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

      return "TheAnonymousType";
    }
}

5
Dlaczego chcesz zwrócić typ anonimowy? Jak możesz wykorzystać ten wynik gdziekolwiek indziej?
Fuj,


5
@Yuck co, jeśli zwracasz plik json lub coś, w którym typ C # nie ma znaczenia
aw04

10
Nie sądzę, żeby to pytanie było bez powodu. Właściwie musiałem to zrobić kilka razy. Jest to bardziej widoczne, gdy używasz struktury encji i chcesz wykonać zapytanie w jednej funkcji i użyć wyników w kilku miejscach. Potrzebuję tego dość często, gdy wyświetlam wyniki na ekranie, a następnie muszę użyć tych samych wyników w raporcie lub podczas eksportowania do programu Excel. Zapytanie może zawierać wiele filtrów i tym podobne z interfejsu użytkownika. nie chcesz tworzyć tego samego zapytania w kilku miejscach lub możesz łatwo
stracić

Odpowiedzi:


94

Nie możesz.

Można jedynie zwrócić objectlub pojemnik z przedmiotami, na przykład IEnumerable<object>, IList<object>itd


51
Lub dynamic. To sprawia, że ​​praca z nim jest nieco łatwiejsza.
vcsjones,

ok, więc możesz używać tylko anonimowych typów w metodzie, ale nie jako wartości zwracanych?
Frenchie

2
@frenchie: Tak, tylko wewnątrz ciała członka. Jeśli chcesz go zwrócić - nadaj mu dobrze znany typ.
abatishchev

11
Używanie dynamiki nie jest rozwiązaniem, pola typu anonimowego nie są publiczne, są wewnętrzne.
Hans Passant

7
@HansPassant Zakładając, że wywołujący jest w tym samym zestawie, to nadal (nieco) przydatne. Bo warto, pola są publiczne - typ jest wewnętrzny. Generalnie jestem w obozie, że i tak nie powinieneś zwracać anonimowego typu.
vcsjones,

42

Możesz zwrócić, dynamicco da ci sprawdzoną w czasie wykonywania wersję typu anonimowego, ale tylko w .NET 4+


30

W C # 7 możemy użyć krotek, aby to osiągnąć:

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

Może być jednak konieczne zainstalowanie System.ValueTuplepakietu NuGet.


27

Nie możesz zwrócić typów anonimowych. Czy możesz stworzyć model, który można zwrócić? W przeciwnym razie musisz użyć pliku object.

Oto artykuł napisany na ten temat przez Jona Skeeta

Kod z artykułu:

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

Albo tutaj jest inny podobny artykuł

Lub, jak komentują inni, możesz użyć dynamic


8
Oczywiście mogę stworzyć typ; Chciałem tego uniknąć.
Frenchie

pierwszy link jest martwy, czy nie wiesz, czy został przeniesiony gdzie indziej?
Rémi

17

Możesz użyć klasy Tuple jako substytutu dla typów anonimowych, gdy konieczne jest zwrócenie:

Uwaga: krotka może mieć maksymalnie 8 parametrów.

return Tuple.Create(variable1, variable2);

Lub na przykład z oryginalnego postu:

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

Kompilator C # jest kompilatorem dwufazowym. W pierwszej fazie sprawdza tylko przestrzenie nazw, hierarchie klas, sygnatury metod itp. Treści metod są kompilowane dopiero w drugiej fazie.

Typy anonimowe nie są określane do momentu skompilowania treści metody.

Dlatego kompilator nie ma możliwości określenia zwracanego typu metody w pierwszej fazie.

To jest powód, dla którego typy anonimowe nie mogą być używane jako typ zwracany.

Jak sugerowali inni, jeśli używasz .net 4.0 lub tarki, możesz użyć Dynamic.

Na twoim miejscu prawdopodobnie utworzyłbym typ i zwróciłbym ten typ z metody. W ten sposób jest to łatwe dla przyszłych programistów, którzy dbają o Twój kod i są bardziej czytelni.


8

Trzy opcje:

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

Opcja 2:

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

możesz iterować go jako obiekt

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

i będziesz mógł iterować go jako obiekt dynamiczny i uzyskać bezpośredni dostęp do jego właściwości


3

W takim przypadku możesz zwrócić listę obiektów.

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

      return TheQueryFromDB ;
    }
}

3

Używając C # 7.0 nadal nie możemy zwracać typów anonimowych, ale mamy obsługę typów krotek, a zatem możemy zwrócić kolekcję tuple( System.ValueTuple<T1,T2>w tym przypadku). Obecnie Tuple types nie są obsługiwane w drzewach wyrażeń i musisz załadować dane do pamięci.

Najkrótsza wersja żądanego kodu może wyglądać następująco:

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

Lub używając płynnej składni Linq:

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

Używając C # 7.1 możemy pominąć nazwy właściwości krotki i zostaną one wywnioskowane z inicjalizacji krotki, tak jak to działa z typami anonimowymi:

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

Stworzenie własnej klasy i odpytywanie o nią to najlepsze rozwiązanie jakie znam, o ile wiem, nie można użyć wartości zwracanych typu anonimowego w innej metodzie, ponieważ nie będą one tylko rozpoznawane, ale mogą być używane w tym samym metoda. Zwracałem je jako IQueryablelubIEnumerable , chociaż nadal nie pozwala ci zobaczyć, co znajduje się wewnątrz zmiennej typu anonimowego.

Wcześniej napotkałem coś takiego, gdy próbowałem refaktoryzować kod, możesz to sprawdzić tutaj: Refaktoryzacja i tworzenie oddzielnych metod


2

Z refleksją.

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

Próba:

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

Wynik:

test2

1

Możesz używać tylko dynamicznych słów kluczowych,

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

Ale dzięki słowu kluczowemu typu dynamicznego stracisz bezpieczeństwo czasu kompilacji, IDE IntelliSense itp.


0

Inną opcją może być użycie automappera: będziesz konwertować na dowolny typ z anonimowego zwróconego obiektu, o ile będą pasować długie właściwości publiczne. Kluczowe punkty to zwracanie obiektu, użycie linq i autommaper. (lub użyj podobnego pomysłu, zwracając zserializowany plik JSON itp. lub użyj odbicia)

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

Teraz szczególnie z funkcjami lokalnymi, ale zawsze możesz to zrobić, przekazując delegata, który tworzy anonimowy typ.

Więc jeśli twoim celem było uruchomienie różnych logiki na tych samych źródłach i możliwość łączenia wyników w jedną listę. Nie jestem pewien, jakiego niuansu brakuje, aby osiągnąć określony cel, ale tak długo, jak zwracasz a Ti przekazujesz delegata do wykonania T, możesz zwrócić anonimowy typ z funkcji.

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

W rzeczywistości możliwe jest zwrócenie anonimowego typu z metody w określonym przypadku użycia. Spójrzmy!

W języku C # 7 możliwe jest zwrócenie anonimowych typów z metody, chociaż wiąże się to z niewielkimi ograniczeniami. Zamierzamy użyć nowej funkcji języka zwanej funkcją lokalną wraz ze sztuczką pośredniczącą (kolejna warstwa pośrednictwa może rozwiązać każde wyzwanie programistyczne, prawda?).

Oto przypadek użycia, który niedawno zidentyfikowałem. Chcę rejestrować wszystkie wartości konfiguracyjne po ich załadowaniu zAppSettings . Czemu? Ponieważ istnieje pewna logika dotycząca brakujących wartości, które powracają do wartości domyślnych, trochę analizy i tak dalej. Łatwym sposobem rejestrowania wartości po zastosowaniu logiki jest umieszczenie ich wszystkich w klasie i serializowanie ich do pliku dziennika (przy użyciu log4net). Chcę też ująć złożoną logikę postępowania z ustawieniami i oddzielić to od tego, co muszę z nimi zrobić. Wszystko to bez tworzenia nazwanej klasy, która istnieje tylko do jednorazowego użytku!

Zobaczmy, jak rozwiązać ten problem, używając funkcji lokalnej, która tworzy typ anonimowy.

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

Udało mi się skonstruować anonimową klasę, a także zawrzeć logikę radzenia sobie ze złożonym zarządzaniem ustawieniami, wszystko wewnątrz CreateHttpClienti wewnątrz jego własnego „wyrażenia”. To może nie być dokładnie to, czego chciał OP, ale jest to lekkie podejście z typami anonimowymi, które jest obecnie możliwe w nowoczesnym C #.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.