Rückgabe eines anonymen Typs in C #


99

Ich habe eine Abfrage, die einen anonymen Typ zurückgibt, und die Abfrage befindet sich in einer Methode. Wie schreibst du das:

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

      return "TheAnonymousType";
    }
}

5
Warum sollten Sie einen anonymen Typ zurückgeben? Wie könnten Sie dieses Ergebnis möglicherweise woanders verwenden?
Yuck


4
@ Yuck was ist, wenn Sie json oder etwas zurückgeben, wo der c # -Typ keine Rolle spielt
aw04

10
Ich denke nicht, dass diese Frage unbegründet ist. Ich musste das tatsächlich mehrmals tun. Dies ist offensichtlicher, wenn Sie das Entity Framework verwenden und Ihre Abfrage in einer Funktion ausführen und die Ergebnisse an mehreren Stellen verwenden möchten. Ich brauche dies ziemlich oft, wenn ich die Ergebnisse auf dem Bildschirm anzeige und dann dieselben Ergebnisse in einem Bericht verwenden muss oder wenn ich nach Excel exportiere. Die Abfrage kann viele Filter und dergleichen aus der Benutzeroberfläche enthalten. Sie möchten nicht wirklich dieselbe Abfrage an mehreren Stellen erstellen, oder Sie können leicht aus der Synchronisierung geraten, wenn Sie zu den Ergebnissen hinzufügen möchten
Kevbo

Antworten:


94

Das kannst du nicht.

Sie können nur zurückkehren object, oder Container von Objekten, zB IEnumerable<object>, IList<object>usw.


51
Oder dynamic. Das macht es etwas einfacher, damit zu arbeiten.
vcsjones

ah ok, also kannst du nur anonyme Typen innerhalb einer Methode verwenden, aber nicht als Rückgabewerte?
Frenchie

2
@frenchie: Ja, nur im Körper des Mitglieds. Wenn Sie es zurückgeben möchten, machen Sie es zu einem bekannten Typ.
Abatishchev

11
Die Verwendung von Dynamic ist keine Lösung. Die Felder eines anonymen Typs sind nicht öffentlich, sondern intern.
Hans Passant

7
@HansPassant Angenommen, der Anrufer befindet sich in derselben Assembly, ist er dennoch (etwas) nützlich. Für das, was es wert ist, sind die Felder öffentlich - der Typ ist intern. Ich bin im Allgemeinen im Lager, dass Sie sowieso keinen anonymen Typ zurückgeben sollten.
vcsjones

42

Sie können zurückkehren, dynamicwodurch Sie eine zur Laufzeit überprüfte Version des anonymen Typs erhalten, jedoch nur in .NET 4+


29

In C # 7 können wir Tupel verwenden, um dies zu erreichen:

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

Möglicherweise müssen Sie jedoch das System.ValueTupleNuget-Paket installieren .


27

Sie können keine anonymen Typen zurückgeben. Können Sie ein Modell erstellen, das zurückgegeben werden kann? Andernfalls müssen Sie eine verwenden object.

Hier ist ein Artikel von Jon Skeet zu diesem Thema

Code aus dem Artikel:

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

Oder hier ist ein anderer ähnlicher Artikel

Oder, wie andere kommentieren, könnten Sie verwenden dynamic


8
Natürlich kann ich einen Typ erstellen; Ich wollte das vermeiden.
Frenchie

Der erste Link ist tot. Würdest du nicht zufällig wissen, ob er an einen anderen Ort übertragen wurde?
Rémi

17

Sie können die Tuple-Klasse als Ersatz für anonyme Typen verwenden, wenn eine Rückgabe erforderlich ist:

Hinweis: Tupel kann bis zu 8 Parameter haben.

return Tuple.Create(variable1, variable2);

Oder für das Beispiel aus dem ursprünglichen Beitrag:

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

Der C # -Compiler ist ein Zwei-Phasen-Compiler. In der ersten Phase werden nur Namespaces, Klassenhierarchien, Methodensignaturen usw. überprüft. Methodenkörper werden nur in der zweiten Phase kompiliert.

Anonyme Typen werden erst ermittelt, wenn der Methodenkörper kompiliert wurde.

Der Compiler hat also in der ersten Phase keine Möglichkeit, den Rückgabetyp der Methode zu bestimmen.

Aus diesem Grund können anonyme Typen nicht als Rückgabetyp verwendet werden.

Wie andere vorgeschlagen haben, können Sie verwenden, wenn Sie .net 4.0 oder eine Reibe verwenden Dynamic.

Wenn ich Sie wäre, würde ich wahrscheinlich einen Typ erstellen und diesen Typ von der Methode zurückgeben. Auf diese Weise ist es für zukünftige Programmierer, die Ihren Code pflegen und leichter lesbar sind, einfach.


8

Drei Optionen:

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

Option 2:

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

Sie können es als Objekt iterieren

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

und Sie können es als dynamisches Objekt iterieren und direkt auf deren Eigenschaften zugreifen


3

In diesem Fall können Sie eine Liste von Objekten zurückgeben.

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

      return TheQueryFromDB ;
    }
}

3

Mit C # 7.0 können wir immer noch keine anonymen Typen zurückgeben, aber wir unterstützen Tupeltypen und können daher eine Sammlung von tuple( System.ValueTuple<T1,T2>in diesem Fall) zurückgeben. Derzeit Tuple types werden sie in Ausdrucksbäumen nicht unterstützt und Sie müssen Daten in den Speicher laden.

Die kürzeste Version des gewünschten Codes sieht möglicherweise folgendermaßen aus:

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

Oder verwenden Sie die fließende Linq-Syntax:

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

Mit C # 7.1 können wir die Eigenschaftsnamen von Tupeln weglassen und sie werden aus der Tupelinitialisierung abgeleitet, wie es bei anonymen Typen funktioniert:

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

Das Erstellen einer eigenen Klasse und das Abfragen danach ist die beste Lösung, die ich kenne. Soweit ich weiß, können Sie in einer anderen Methode keine Rückgabewerte für anonyme Typen verwenden, da diese nicht nur erkannt werden. Sie können jedoch in derselben Methode verwendet werden Methode. Früher habe ich sie als IQueryableoder zurückgegeben IEnumerable, obwohl Sie immer noch nicht sehen können, was sich in der anonymen Typvariablen befindet.

Ich bin schon einmal auf so etwas gestoßen, als ich versucht habe, Code umzugestalten. Sie können dies hier überprüfen: Umgestalten und Erstellen separater Methoden


2

Mit Reflexion.

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

Stichprobe:

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

Ausgabe:

test2

1

Sie können nur das dynamische Schlüsselwort verwenden.

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

Mit dem Schlüsselwort "dynamischer Typ" verlieren Sie jedoch die Sicherheit der Kompilierungszeit, IDE IntelliSense usw.


0

Eine andere Option könnte die Verwendung von Automapper sein: Sie konvertieren von Ihrem anonymen zurückgegebenen Objekt in einen beliebigen Typ, solange die öffentlichen Eigenschaften übereinstimmen. Die wichtigsten Punkte sind: Objekt zurückgeben, Linq und Autommaper verwenden. (oder verwenden Sie eine ähnliche Idee, um serialisierten JSON usw. zurückzugeben, oder verwenden Sie Reflection.)

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

Jetzt besonders mit lokalen Funktionen, aber Sie können dies immer tun, indem Sie einen Delegaten übergeben, der den anonymen Typ erstellt.

Wenn es also Ihr Ziel war, unterschiedliche Logik auf denselben Quellen auszuführen und die Ergebnisse in einer einzigen Liste zu kombinieren. Sie sind sich nicht sicher, welche Nuance dies fehlt, um das angegebene Ziel zu erreichen. Solange Sie jedoch a zurückgeben Tund einen Delegaten übergeben, Tkönnen Sie einen anonymen Typ von einer Funktion zurückgeben.

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

Es ist tatsächlich möglich, einen anonymen Typ von einer Methode in einem bestimmten Anwendungsfall zurückzugeben. Werfen wir einen Blick!

Mit C # 7 ist es möglich, anonyme Typen von einer Methode zurückzugeben, obwohl dies mit einer geringfügigen Einschränkung verbunden ist. Wir werden eine neue Sprachfunktion namens lokale Funktion zusammen mit einem Indirektionstrick verwenden (eine andere Indirektionsebene kann jede Programmieraufgabe lösen, oder?).

Hier ist ein Anwendungsfall, den ich kürzlich identifiziert habe. Ich möchte alle Konfigurationswerte protokollieren, nachdem ich sie von geladen habe AppSettings. Warum? Weil es eine Logik für fehlende Werte gibt, die auf Standardwerte zurückgesetzt werden, einige Parsing und so weiter. Eine einfache Möglichkeit, die Werte nach dem Anwenden der Logik zu protokollieren, besteht darin, sie alle in eine Klasse einzuteilen und in eine Protokolldatei zu serialisieren (mithilfe von log4net). Ich möchte auch die komplexe Logik des Umgangs mit Einstellungen zusammenfassen und diese von allem trennen, was ich damit tun muss. Alles ohne eine benannte Klasse zu erstellen, die nur für einen einzigen Gebrauch existiert!

Mal sehen, wie man dies mit einer lokalen Funktion löst, die einen anonymen Typ erstellt.

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

Es ist mir gelungen, eine anonyme Klasse zu erstellen, und ich habe auch die Logik des Umgangs mit der Verwaltung komplexer Einstellungen in CreateHttpClientund innerhalb ihres eigenen "Ausdrucks" zusammengefasst. Dies ist möglicherweise nicht genau das, was das OP wollte, aber es ist ein leichtgewichtiger Ansatz mit anonymen Typen, der derzeit in modernem C # möglich ist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.