Wie übergebe ich anonyme Typen als Parameter?

143

Wie kann ich anonyme Typen als Parameter an andere Funktionen übergeben? Betrachten Sie dieses Beispiel:

var query = from employee in employees select new { Name = employee.Name, Id = employee.Id };
LogEmployees(query);

Die Variable query hier hat keinen starken Typ. Wie soll ich meine LogEmployeesFunktion definieren , um sie zu akzeptieren?

public void LogEmployees (? list)
{
    foreach (? item in list)
    {

    }
}

Mit anderen Worten, was soll ich anstelle von verwenden ? Markierungen verwenden.

Saeed Neamati
quelle
1
Bessere andere doppelte Frage, die sich mit der Übergabe von Parametern befasst, anstatt Daten zurückzugeben: stackoverflow.com/questions/16823658/…
Rob Church

Antworten:

183

Ich denke, Sie sollten eine Klasse für diesen anonymen Typ erstellen. Das wäre meiner Meinung nach das Vernünftigste. Aber wenn Sie wirklich nicht wollen, können Sie Dynamik verwenden:

public void LogEmployees (IEnumerable<dynamic> list)
{
    foreach (dynamic item in list)
    {
        string name = item.Name;
        int id = item.Id;
    }
}

Beachten Sie, dass dies nicht stark typisiert ist. Wenn sich beispielsweise Name in EmployeeName ändert, wissen Sie erst zur Laufzeit, dass ein Problem vorliegt.

Tim S.
quelle
Ich habe dies aufgrund der dynamicVerwendung als richtige Antwort überprüft . Ich habe mich wirklich als nützlich erwiesen. Danke :)
Saeed Neamati
1
Ich bin damit einverstanden, dass, sobald Daten weitergegeben werden, normalerweise eine strukturiertere Methode bevorzugt werden kann / sollte, um keine schwer zu findenden Fehler einzuführen (Sie umgehen das Typsystem). Wenn Sie jedoch einen Kompromiss finden möchten, können Sie auch einfach ein generisches Wörterbuch übergeben. C # -Wörterbuchinitialisierer sind heutzutage recht praktisch.
Jonas
In einigen Fällen möchten Sie eine generische Implementierung, und das Übergeben von Hardtypen bedeutet möglicherweise ein Wechseln oder eine werkseitige Implementierung, die den Code aufblähen lässt. Wenn Sie eine wirklich dynamische Situation haben und sich ein wenig Gedanken über den Umgang mit den empfangenen Daten machen, ist dies perfekt. Vielen Dank für die Antwort @Tim S.
Larry Smith
42

Sie können es so machen:

public void LogEmployees<T>(List<T> list) // Or IEnumerable<T> list
{
    foreach (T item in list)
    {

    }
}

... aber Sie werden nicht viel mit jedem Gegenstand anfangen können. Sie könnten ToString nennen, aber Sie werden nutzen können (sagen wir) nicht Nameund Iddirekt.

Jon Skeet
quelle
2
Außer Sie können where T : some typeam Ende der ersten Zeile verwenden, um den Typ einzugrenzen. Zu diesem Zeitpunkt wäre es jedoch sinnvoller, eine bestimmte Art von gemeinsamer Schnittstelle zu erwarten, um eine Schnittstelle zu erwarten. :)
CassOnMars
9
@d_r_w: Sie können jedoch nicht where T : some typemit anonymen Typen verwenden, da sie keine Schnittstelle implementieren ...
Jon Skeet
@dlev: Das können Sie nicht, foreach erfordert, dass die Variable bei der Implementierung von GetEnumerator iteriert wird, und anonyme Typen garantieren dies nicht.
CassOnMars
1
@ Jon Skeet: Guter Punkt, mein Gehirn ist heute Morgen unterfordert.
CassOnMars
1
@ JonSkeet. Ich nehme an, Sie könnten Reflection verwenden, um weiterhin auf die Eigenschaften zuzugreifen / diese festzulegen, wenn T ein anonymer Typ ist, oder? Ich denke an einen Fall, in dem jemand eine "Select * from" -Anweisung schreibt und eine anonyme (oder definierte) Klasse verwendet, um zu definieren, welche Spalten aus dem Abfrageergebnis denselben gleichnamigen Eigenschaften in Ihrem anonymen Objekt zugeordnet sind.
C. Tewalt
19

Leider ist das, was Sie versuchen, unmöglich. Unter der Haube wird die Abfragevariable IEnumerableals anonymer Typ eingegeben . Anonyme Typnamen können nicht im Benutzercode dargestellt werden, daher gibt es keine Möglichkeit, sie zu einem Eingabeparameter für eine Funktion zu machen.

Am besten erstellen Sie einen Typ und verwenden diesen als Rückgabe aus der Abfrage und übergeben ihn dann an die Funktion. Beispielsweise,

struct Data {
  public string ColumnName; 
}

var query = (from name in some.Table
            select new Data { ColumnName = name });
MethodOp(query);
...
MethodOp(IEnumerable<Data> enumerable);

In diesem Fall wählen Sie jedoch nur ein einzelnes Feld aus, sodass es möglicherweise einfacher ist, das Feld direkt auszuwählen. Dadurch wird die Abfrage als Feldtyp eingegeben IEnumerable. In diesem Fall Spaltenname.

var query = (from name in some.Table select name);  // IEnumerable<string>
JaredPar
quelle
Mein Beispiel war eines, aber meistens ist es mehr. Ihre Antwort durch Werke (und jetzt ganz offensichtlich). Ich brauchte nur eine Mittagspause, um darüber nachzudenken
;-)
Zu Ihrer Information
Information Shog9
Eine Einschränkung ist, dass beim Erstellen einer geeigneten Klasse das EqualsVerhalten geändert wird. Dh du musst es implementieren. (Ich wusste von dieser Diskrepanz, konnte sie aber während eines Refactorings immer noch vergessen.)
LosManos
11

Sie können einen anonymen Typ nicht an eine nicht generische Funktion übergeben, es sei denn, der Parametertyp ist object.

public void LogEmployees (object obj)
{
    var list = obj as IEnumerable(); 
    if (list == null)
       return;

    foreach (var item in list)
    {

    }
}

Anonyme Typen sind für die kurzfristige Verwendung innerhalb einer Methode vorgesehen.

Von MSDN - Anonyme Typen :

Sie können ein Feld, eine Eigenschaft, ein Ereignis oder den Rückgabetyp einer Methode nicht als anonym deklarieren. Ebenso können Sie einen formalen Parameter einer Methode, Eigenschaft, eines Konstruktors oder eines Indexers nicht als anonym deklarieren. Um einen anonymen Typ oder eine Sammlung, die anonyme Typen enthält, als Argument an eine Methode zu übergeben, können Sie den Parameter als Typobjekt deklarieren . Dies macht jedoch den Zweck einer starken Eingabe zunichte.

(Hervorhebung von mir)


Aktualisieren

Sie können Generika verwenden, um das zu erreichen, was Sie wollen:

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {

    }
}
Oded
quelle
4
Wenn Sie keine anonymen Typen (oder Sammlungen eines anonymen Typs) an Methoden übergeben könnten, würde die gesamte LINQ fehlschlagen. Sie können, es ist nur so, dass die Methode vollständig generisch sein muss und nicht die Eigenschaften des anonymen Typs verwendet.
Jon Skeet
2
re object- or dynamic; p
Marc Gravell
Wenn Sie mit "as" gießen, sollten Sie überprüfen, ob die Liste null ist
Alex
"kann"! = "muss". Die Verwendung objectist nicht das Gleiche wie eine generische Methode im anonymen Typ gemäß meiner Antwort.
Jon Skeet
8

Normalerweise tun Sie dies mit Generika, zum Beispiel:

MapEntToObj<T>(IQueryable<T> query) {...}

Der Compiler sollte dann Tbeim Aufrufen auf das schließen MapEntToObj(query). Ich bin mir nicht ganz sicher, was Sie innerhalb der Methode tun möchten, daher kann ich nicht sagen, ob dies nützlich ist. Das Problem ist, dass MapEntToObjSie die Methode immer noch nicht benennen können T- Sie können auch:

  • Rufen Sie andere generische Methoden mit auf T
  • Verwenden Sie Reflexion über T, um Dinge zu tun

Ansonsten ist es ziemlich schwierig, anonyme Typen zu manipulieren - nicht zuletzt, weil sie unveränderlich sind ;-p

Ein weiterer Trick (beim Extrahieren von Daten) besteht darin, auch einen Selektor zu übergeben - dh so etwas wie:

Foo<TSource, TValue>(IEnumerable<TSource> source,
        Func<TSource,string> name) {
    foreach(TSource item in source) Console.WriteLine(name(item));
}
...
Foo(query, x=>x.Title);
Marc Gravell
quelle
1
Ich habe etwas Neues gelernt und wusste nicht, dass anonyme Typen unveränderlich sind! ;)
Annie Lagang
1
@AnneLagang das liegt wirklich am Compiler, da es sie generiert. In VB.NET können Anontypen veränderbar sein.
Marc Gravell
1
Zu Ihrer
Information
7

Sie können Generika mit dem folgenden Trick verwenden (Casting in einen anonymen Typ):

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {
        var typedItem = Cast(item, new { Name = "", Id = 0 });
        // now you can use typedItem.Name, etc.
    }
}

static T Cast<T>(object obj, T type)
{
    return (T)obj;
}
Stanislav Basovník
quelle
6

Zu diesem Zweck kann auch "dynamisch" verwendet werden.

var anonymousType = new { Id = 1, Name = "A" };

var anonymousTypes = new[] { new { Id = 1, Name = "A" }, new { Id = 2, Name = "B" };

private void DisplayAnonymousType(dynamic anonymousType)
{
}

private void DisplayAnonymousTypes(IEnumerable<dynamic> anonymousTypes)
{
   foreach (var info in anonymousTypes)
   {

   }
}
Dinesh Kumar P.
quelle
1
Das ist die richtige Antwort! Es braucht nur mehr Liebe :)
Korayem
2

Anstatt einen anonymen Typ zu übergeben, übergeben Sie eine Liste eines dynamischen Typs:

  1. var dynamicResult = anonymousQueryResult.ToList<dynamic>();
  2. Methodensignatur: DoSomething(List<dynamic> _dynamicResult)
  3. Aufrufmethode: DoSomething(dynamicResult);
  4. getan.

Vielen Dank an Petar Ivanov !

nützlichBee
quelle
0

Wenn Sie wissen, dass Ihre Ergebnisse eine bestimmte Schnittstelle implementieren, können Sie die Schnittstelle als Datentyp verwenden:

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {

    }
}
Alex
quelle
0

Ich würde IEnumerable<object>als Typ für das Argument verwenden. Allerdings kein großer Gewinn für die unvermeidliche explizite Besetzung. Prost

Mario Vernari
quelle