Jak przekazać anonimowe typy jako parametry?

143

Jak mogę przekazać anonimowe typy jako parametry do innych funkcji? Rozważmy ten przykład:

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

Zmienna querytutaj nie ma silnego typu. Jak zdefiniować LogEmployeesfunkcję, aby ją zaakceptować?

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

    }
}

Innymi słowy, czego powinienem używać zamiast ?znaków.

Saeed Neamati
źródło
1
Lepsze inne zduplikowane pytanie, które dotyczy przekazywania parametrów zamiast zwracania danych: stackoverflow.com/questions/16823658/ ...
Rob Church

Odpowiedzi:

183

Myślę, że powinieneś stworzyć klasę dla tego anonimowego typu. Moim zdaniem byłoby to najbardziej sensowne. Ale jeśli naprawdę nie chcesz, możesz użyć dynamiki:

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

Zauważ, że nie jest to silnie wpisane, więc jeśli na przykład nazwa zmieni się na EmployeeName, nie będziesz wiedział, że wystąpił problem do czasu uruchomienia.

Tim S.
źródło
Sprawdziłem to jako poprawną odpowiedź ze względu na dynamicużycie. Naprawdę mi się przydała. Dzięki :)
Saeed Neamati
1
Zgadzam się, że gdy dane zaczną być przekazywane, normalnie może / powinien być preferowany sposób bardziej uporządkowany, aby nie wprowadzać trudnych do znalezienia błędów (omijasz system typów). Jeśli jednak chcesz znaleźć kompromis, innym sposobem jest po prostu przekazanie ogólnego Słownika. Inicjatory słownika języka C # są obecnie bardzo wygodne w użyciu.
Jonas
W niektórych przypadkach potrzebujesz implementacji ogólnej, a przekazywanie twardych typów oznacza prawdopodobnie przełączanie lub implementację fabryczną, która zaczyna nadymać kod. Jeśli masz naprawdę dynamiczną sytuację i nie masz nic przeciwko odrobinie refleksji nad danymi, które otrzymujesz, to jest to idealne rozwiązanie. Dzięki za odpowiedź @Tim S.
Larry Smith
42

Możesz to zrobić w ten sposób:

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

    }
}

... ale nie będziesz miał wiele do zrobienia z każdym przedmiotem. Możesz wywołać ToString, ale nie będziesz mógł używać (powiedzmy) Namei Idbezpośrednio.

Jon Skeet
źródło
2
Z wyjątkiem tego, że możesz użyć where T : some typena końcu pierwszej linii, aby zawęzić typ. W tym momencie jednak oczekiwanie określonego typu wspólnego interfejsu byłoby bardziej sensowne, gdybyśmy oczekiwali interfejsu. :)
CassOnMars
9
@d_r_w: Nie możesz jednak używać where T : some typez typami anonimowymi, ponieważ nie implementują żadnego rodzaju interfejsu ...
Jon Skeet
@dlev: Nie możesz tego zrobić, foreach wymaga, aby zmienna była iterowana na implementacji GetEnumerator, a typy anonimowe tego nie gwarantują.
CassOnMars
1
@Jon Skeet: dobra uwaga, mój mózg jest dziś rano słabo rozwinięty.
CassOnMars
1
@JonSkeet. Przypuszczam, że możesz użyć odbicia, aby nadal uzyskiwać dostęp / ustawiać właściwości, jeśli T jest typem anonimowym, prawda? Mam na myśli przypadek, w którym ktoś pisze instrukcję „Select * from” i używa anonimowej (lub zdefiniowanej) klasy do zdefiniowania, które kolumny z mapy wyników zapytania do tych samych nazwanych właściwości w twoim anonimowym obiekcie.
C. Tewalt
19

Niestety to, co próbujesz zrobić, jest niemożliwe. Pod maską zmienna zapytania jest wpisywana IEnumerablejako typu anonimowego. Nazwy typów anonimowych nie mogą być reprezentowane w kodzie użytkownika, dlatego nie ma możliwości uczynienia ich parametrem wejściowym dla funkcji.

Najlepszym rozwiązaniem jest utworzenie typu i użycie go jako wyniku zapytania, a następnie przekazanie go do funkcji. Na przykład,

struct Data {
  public string ColumnName; 
}

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

W tym przypadku jednak wybierasz tylko jedno pole, więc może być łatwiej po prostu wybrać pole bezpośrednio. Spowoduje to wpisanie zapytania jako IEnumerabletypu pola. W tym przypadku nazwa kolumny.

var query = (from name in some.Table select name);  // IEnumerable<string>
JaredPar
źródło
Mój przykład był jeden, ale najczęściej jest to więcej. Twoja odpowiedź poprzez prace (i teraz całkiem oczywista). Potrzebowałem tylko przerwy na lunch, żeby to przemyśleć ;-)
Tony Trembath-Drake 22.04.2009
Do Twojej wiadomości: połączenie ze stackoverflow.com/questions/775387/ ...
Shog9
Zastrzeżenie jest takie, że kiedy tworzysz odpowiednią klasę, Equalszmienia się zachowanie. To znaczy musisz to zaimplementować. (Wiedziałem o tej rozbieżności, ale mimo to udało mi się o tym zapomnieć podczas refaktoryzacji.)
LosManos
11

Nie można przekazać typu anonimowego do funkcji innej niż ogólna, chyba że typ parametru to object.

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

    foreach (var item in list)
    {

    }
}

Typy anonimowe są przeznaczone do krótkotrwałego użycia w ramach metody.

Z MSDN - typy anonimowe :

Nie można zadeklarować pola, właściwości, zdarzenia lub zwracanego typu metody jako typu anonimowego. Podobnie nie można zadeklarować formalnego parametru metody, właściwości, konstruktora lub indeksatora jako typu anonimowego. Aby przekazać typ anonimowy lub kolekcję zawierającą typy anonimowe jako argument do metody, można zadeklarować parametr jako obiekt typu . Jednak robienie tego jest sprzeczne z celem silnego pisania.

(podkreślenie moje)


Aktualizacja

Możesz użyć typów ogólnych, aby osiągnąć to, co chcesz:

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

    }
}
Oded
źródło
4
Jeśli nie można przekazać anonimowych typów (lub kolekcji typu anonimowego) do metod, cała LINQ zakończy się niepowodzeniem. Możesz, po prostu metoda musi być całkowicie ogólna i nie może wykorzystywać właściwości typu anonimowego.
Jon Skeet
2
re object- lub dynamic; p
Marc Gravell
Jeśli przesyłasz z "as", powinieneś sprawdzić, czy lista jest pusta
Alex
„can”! = „must to”. Używanie objectto nie to samo, co tworzenie metody generycznej w typie anonimowym, zgodnie z moją odpowiedzią.
Jon Skeet
8

Zwykle robisz to za pomocą leków generycznych, na przykład:

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

Kompilator powinien następnie wywnioskować, Tkiedy wywołujesz MapEntToObj(query). Nie jestem do końca pewien, co chcesz zrobić w metodzie, więc nie mogę powiedzieć, czy jest to przydatne ... Problem polega na tym, że w środku MapEntToObjnadal nie możesz nazwać T- możesz albo:

  • wywołać inne metody ogólne z T
  • używaj refleksji Tdo robienia rzeczy

ale poza tym dość trudno jest manipulować typami anonimowymi - nie tylko dlatego, że są niezmienne ;-p

Kolejną sztuczką (przy wyodrębnianiu danych) jest również przekazanie selektora - czyli coś takiego:

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
źródło
1
Nauczyłem się czegoś nowego, nie wiedziałem, że anonimowe typy są niezmienne! ;)
Annie Lagang
1
@AnneLagang, które tak naprawdę zależy od kompilatora, ponieważ je generuje. W VB.NET typy anon mogą być modyfikowalne.
Marc Gravell
1
Do Twojej wiadomości: połączenie ze stackoverflow.com/questions/775387/ ...
Shog9
7

Możesz użyć typów ogólnych z następującą sztuczką (rzutowanie na typ anonimowy):

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
źródło
6

W tym celu można również użyć terminu „dynamiczny”.

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.
źródło
1
To jest właściwa odpowiedź! Po prostu potrzebuje więcej miłości :)
Korayem
2

Zamiast przekazywać anonimowy typ, przekaż Listę typu dynamicznego:

  1. var dynamicResult = anonymousQueryResult.ToList<dynamic>();
  2. Podpis metody: DoSomething(List<dynamic> _dynamicResult)
  3. Metoda połączenia: DoSomething(dynamicResult);
  4. Gotowe.

Dzięki Petar Ivanov !

przydatne
źródło
0

Jeśli wiesz, że twoje wyniki implementują pewien interfejs, możesz użyć interfejsu jako typu danych:

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

    }
}
Alex
źródło
0

Użyłbym IEnumerable<object>jako typu dla argumentu. Jednak nie jest to wielki zysk dla nieuniknionej wyraźnej obsady. Twoje zdrowie

Mario Vernari
źródło