Come passare i tipi anonimi come parametri?


143

Come posso passare tipi anonimi come parametri ad altre funzioni? Considera questo esempio:

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

La variabile queryqui non ha un tipo forte. Come devo definire la mia LogEmployeesfunzione per accettarla?

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

    }
}

In altre parole, cosa dovrei usare al posto dei ?segni.


1
Meglio un'altra domanda duplicata che si occupa del passaggio dei parametri piuttosto che della restituzione dei dati: stackoverflow.com/questions/16823658/…
Rob Church

Risposte:


183

Penso che dovresti fare un corso per questo tipo anonimo. Secondo me sarebbe la cosa più sensata da fare. Ma se davvero non vuoi, potresti usare la dinamica:

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

Si noti che questo non è fortemente tipizzato, quindi se, ad esempio, Nome cambia in EmployeeName, non si saprà che c'è un problema fino al runtime.


Ho controllato questa come risposta corretta, a causa dynamicdell'uso. Sono davvero tornato utile per me. Grazie :)
Saeed Neamati,

1
Sono d'accordo che una volta che i dati iniziano a essere trasferiti, un modo più strutturato può / dovrebbe normalmente essere preferito per non introdurre bug difficili da trovare (stai evitando il sistema di tipi). Tuttavia, se si desidera trovare un compromesso, un altro modo è semplicemente quello di passare un dizionario generico. Gli inizializzatori del dizionario C # sono abbastanza comodi da usare in questi giorni.
Jonas,

Ci sono alcuni casi in cui si desidera un'implementazione generica e il passaggio di tipi rigidi significa possibilmente il passaggio o l'implementazione di fabbrica che inizia a gonfiare il codice. Se hai una situazione veramente dinamica e non ti dispiace un po 'di riflessione per gestire i dati che stai ricevendo, allora questo è perfetto. Grazie per la risposta @ Tim S.
Larry Smith,

42

Puoi farlo in questo modo:

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

    }
}

... ma non dovrai fare molto con ogni oggetto. Potresti chiamare ToString, ma non sarai in grado di usare (diciamo) Namee Iddirettamente.


2
Tranne che puoi usare where T : some typealla fine della prima riga per restringere il tipo. A quel punto, tuttavia, aspettarsi un certo tipo di interfaccia comune avrebbe più senso aspettarsi un'interfaccia. :)
CassOnMars

9
@d_r_w: Non puoi usarlo where T : some typecon tipi anonimi, poiché non implementano alcun tipo di interfaccia ...
Jon Skeet

@dlev: non puoi farlo, foreach richiede che la variabile sia ripetuta su Implement GetEnumerator e che i tipi anonimi non lo garantiscano.
CassOnMars

1
@Jon Skeet: buon punto, il mio cervello è sottodimensionato questa mattina.
CassOnMars

1
@JonSkeet. Suppongo che potresti usare la riflessione per accedere ancora / impostare le proprietà se T è un tipo anonimo giusto? Sto pensando a un caso in cui qualcuno scrive un'istruzione "Seleziona * da" e usa una classe anonima (o definita) per definire quali colonne dalla mappa dei risultati della query alle stesse proprietà con nome sull'oggetto anonimo.
C. Tewalt,

19

Sfortunatamente, quello che stai cercando di fare è impossibile. Sotto il cofano, la variabile di query viene digitata per essere IEnumerabledi tipo anonimo. I nomi di tipo anonimo non possono essere rappresentati nel codice utente, pertanto non è possibile renderli un parametro di input per una funzione.

La soluzione migliore è creare un tipo e utilizzarlo come ritorno dalla query, quindi passarlo alla funzione. Per esempio,

struct Data {
  public string ColumnName; 
}

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

In questo caso, tuttavia, stai solo selezionando un singolo campo, quindi potrebbe essere più semplice selezionare direttamente il campo. Ciò causerà la digitazione della query come IEnumerabledi tipo di campo. In questo caso, il nome della colonna.

var query = (from name in some.Table select name);  // IEnumerable<string>

Il mio esempio è stato uno, ma il più delle volte è di più. La tua risposta attraverso funziona (e ora è abbastanza ovvia). Avevo solo bisogno di una pausa per pranzo per pensarci ;-)
Tony Trembath-Drake,


Un avvertimento è che quando si crea una classe corretta Equalscambia il comportamento. Cioè devi implementarlo. (Conoscevo questa discrepanza ma riuscivo comunque a dimenticarmene durante un refactoring.)
LosManos

11

Non è possibile passare un tipo anonimo a una funzione non generica, a meno che non sia il tipo di parametro object.

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

    foreach (var item in list)
    {

    }
}

I tipi anonimi sono destinati all'uso a breve termine all'interno di un metodo.

Da MSDN - Tipi anonimi :

Non è possibile dichiarare un campo, una proprietà, un evento o il tipo restituito di un metodo con un tipo anonimo. Allo stesso modo, non è possibile dichiarare un parametro formale di un metodo, proprietà, costruttore o indicizzatore con un tipo anonimo. Per passare un tipo anonimo o una raccolta che contiene tipi anonimi, come argomento a un metodo, è possibile dichiarare il parametro come oggetto type . Tuttavia, fare questo sconfigge lo scopo della digitazione forte.

(enfatizzare il mio)


Aggiornare

Puoi usare generici per ottenere ciò che desideri:

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

    }
}

4
Se non potessi passare tipi anonimi (o raccolte di un tipo anonimo) ai metodi, l'intero LINQ fallirebbe. Puoi, è solo che il metodo deve essere completamente generico, non usando le proprietà del tipo anonimo.
Jon Skeet,

2
ri object- oppure dynamic; p
Marc Gravell

Se si esegue il casting con "as", è necessario verificare se l'elenco è null
Alex

"can"! = "have to". L'utilizzo objectnon equivale a rendere un metodo generico nel tipo anonimo, secondo la mia risposta.
Jon Skeet,

8

Normalmente, lo fai con generici, ad esempio:

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

Il compilatore dovrebbe quindi dedurre Tquando si chiama MapEntToObj(query). Non sono sicuro di cosa tu voglia fare all'interno del metodo, quindi non posso dire se questo sia utile ... il problema è che dentro di MapEntToObjte non puoi ancora nominare il T- puoi anche:

  • chiama altri metodi generici con T
  • usa la riflessione Tper fare le cose

ma a parte questo, è abbastanza difficile manipolare i tipi anonimi - non ultimo perché sono immutabili ;-p

Un altro trucco (durante l' estrazione di dati) è anche passare un selettore, ovvero qualcosa del tipo:

Foo<TSource, TValue>(IEnumerable<TSource> source,
        Func<TSource,string> name) {
    foreach(TSource item in source) Console.WriteLine(name(item));
}
...
Foo(query, x=>x.Title);

1
Ho imparato qualcosa di nuovo, non sapevo che i tipi anonimi sono immutabili! ;)
Annie Lagang,

1
@AnneLagang dipende davvero dal compilatore, dal momento che li genera. In VB.NET, i tipi anon possono essere mutabili.
Marc Gravell


7

Puoi usare i generici con il seguente trucco (passando al tipo anonimo):

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

6

"dinamico" può essere utilizzato anche per questo scopo.

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

   }
}

1
Questa è la risposta esatta! Ha solo bisogno di più amore :)
Korayem

2

Invece di passare un tipo anonimo, passa un Elenco di un tipo dinamico:

  1. var dynamicResult = anonymousQueryResult.ToList<dynamic>();
  2. Firma del metodo: DoSomething(List<dynamic> _dynamicResult)
  3. Metodo di chiamata: DoSomething(dynamicResult);
  4. fatto.

Grazie a Petar Ivanov !


0

Se sai che i tuoi risultati implementano una determinata interfaccia, puoi utilizzare l'interfaccia come tipo di dati:

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

    }
}

0

Vorrei usare IEnumerable<object>come tipo per l'argomento. Comunque non è un grande guadagno per l'inevitabile cast esplicito. Saluti

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.