Як передавати анонімні типи як параметри?


143

Як я можу передавати анонімні типи як параметри іншим функціям? Розглянемо цей приклад:

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

Змінна queryтут не має сильного типу. Як я повинен визначити свою LogEmployeesфункцію, щоб прийняти її?

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

    }
}

Іншими словами, що я повинен використовувати замість ?знаків.


1
Краще інший повторюваний питання, що стосується проходження параметрів, а не повернення даних: stackoverflow.com/questions/16823658/…
Rob Church

Відповіді:


183

Я думаю, вам слід скласти клас для цього анонімного типу. На мою думку, це було б найрозумніше. Але якщо ви цього не хочете, ви можете використовувати динаміку:

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

Зауважте, що це не сильно набрано тексту, тому якщо, наприклад, Ім'я зміниться на EmployeeName, ви не знатимете, що існує проблема до часу виконання.


Я перевірив це як правильну відповідь через dynamicвикористання. Я справді став для мене корисним. Дякую :)
Saeed Neamati

1
Я погоджуюсь, що як тільки дані починають передаватися навколо, зазвичай / слід віддавати перевагу більш структурованому способу, щоб не вводити важко знайти помилки (ви переходите до типу типу). Однак якщо ви хочете знайти компроміс, інший спосіб - просто пройти загальний словник. Ініціалізатори словника C # досить зручні для використання в наші дні.
Йонас

Є деякі випадки, коли потрібно загальну реалізацію, а передача жорстких типів означає можливе перемикання або заводську реалізацію, яка починає розмивати код. Якщо у вас по-справжньому динамічна ситуація і ви не заперечуєте мало роздумів, щоб розібратися з отриманими даними, то це ідеально. Дякую за відповідь @Tim S.
Ларрі Сміт

42

Ви можете це зробити так:

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

    }
}

... але вам не вдасться багато зробити з кожним предметом. Ви можете зателефонувати в ToString, але ви не зможете користуватися (скажімо) Nameта Idбезпосередньо.


2
Крім того, ви можете використовувати where T : some typeв кінці першого рядка, щоб звузити тип. На той момент, однак, очікувати певного типу загального інтерфейсу було б більш сенсом очікувати інтерфейс. :)
CassOnMars

9
@d_r_w: Хоча ти не можеш використовувати where T : some typeанонімні типи, оскільки вони не реалізують будь-який інтерфейс ...
Jon Skeet

@dlev: Ви цього не можете зробити, foreach вимагає, щоб змінна, повторена на GetEnumerator, і анонімні типи цього не гарантували.
CassOnMars

1
@ Jon Skeet: Добре, мозок мій недостатньо сильний сьогодні вранці.
CassOnMars

1
@JonSkeet Я думаю, ви могли б використовувати відображення для доступу та встановлення властивостей, якщо T - анонімний тип права? Я думаю про випадок, коли хтось пише оператор "Select * from" і використовує анонімний (або визначений) клас, щоб визначити, які стовпці з карти результатів запиту до тих самих названих властивостей вашого анонімного об'єкта.
C. Tewalt

19

На жаль, те, що ви намагаєтеся зробити, неможливо. Під капотом змінна запиту вводиться IEnumerableяк анонімний тип. Імена анонімних типів не можуть бути представлені в коді користувача, тому немає можливості зробити їх вхідним параметром функції.

Ваша найкраща ставка - створити тип і використовувати його як повернення запиту, а потім передати його у функцію. Наприклад,

struct Data {
  public string ColumnName; 
}

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

Однак у цьому випадку ви вибираєте лише одне поле, тому просто вибирати це поле може бути простіше. Це призведе до того, що запит буде введено як IEnumerableтип поля. У цьому випадку назва стовпця.

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

Мій приклад був один, але в більшості випадків це більше. Ваша відповідь через твори (і цілком очевидно зараз). Мені просто потрібна перерва на обід, щоб подумати, хоча ;-)
Tony Trembath-Drake


Застереження полягає в тому, що при створенні належного класу Equalsзмінюється поведінка. Тобто ви повинні це здійснити. (Я знав про цю невідповідність, але все-таки вдалося забути про це під час рефакторингу.)
LosManos,

11

Ви не можете передати анонімний тип до загальної функції, якщо не вказано тип параметра object.

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

    foreach (var item in list)
    {

    }
}

Анонімні типи призначені для короткочасного використання в межах методу.

Від MSDN - анонімні типи :

Ви не можете оголосити поле, властивість, подію чи тип повернення методу анонімним. Аналогічно, ви не можете оголосити формальний параметр методу, властивості, конструктора чи індексатора як анонімного типу.Для передачі анонімного типу або колекції, що містить анонімні типи, в якості аргументу методу, ви можете оголосити параметр об'єктом типу . Однак це перемагає мету сильного набору тексту.

(наголос мій)


Оновлення

Ви можете використовувати дженерики, щоб досягти бажаного:

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

    }
}

4
Якщо ви не можете передати анонімні типи (або колекції анонімного типу) методам, цілий LINQ вийде з ладу. Ви можете, це просто те, що метод повинен бути повністю загальним, не використовуючи властивості анонімного типу.
Джон Скіт

2
re object- or dynamic; p
Марк Гравелл

Якщо ви робите "з", ви повинні перевірити, чи список недійсний
Алекс

"може"! = "доведеться". Використання objectне те саме, що зробити метод загальним для анонімного типу, як на мою відповідь.
Джон Скіт

8

Зазвичай ви робите це за допомогою дженериків, наприклад:

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

Потім компілятор повинен зробити висновок про Tчас дзвінка MapEntToObj(query). Не зовсім впевнений, що ви хочете зробити всередині методу, тому я не можу сказати, чи корисно це ... проблема полягає в тому, що всередині MapEntToObjви все ще не можете назвати ім'я T- ви можете:

  • викликати інші загальні методи за допомогою T
  • використовувати роздуми над тим, Tщоб робити речі

але крім цього, досить важко маніпулювати анонімними типами - не в останню чергу тому, що вони непорушні ;-p

Ще одна хитрість (при вилученні даних) - це також передача селектора - тобто щось на зразок:

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
Дізналися щось нове, не знали, що анонімні типи незмінні! ;)
Енні Лаган

1
@AnneLagang справді залежить від компілятора, оскільки він їх генерує. У VB.NET анонни можуть бути зміненими.
Марк Гравелл


7

Ви можете використовувати дженерики із наступним фокусом (переклад на анонімний тип):

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

Для цього також можна використовувати "динамічний".

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
Це правильна відповідь! Просто потрібно більше кохання :)
Кораєм

2

Замість передачі анонімного типу передайте Список динамічного типу:

  1. var dynamicResult = anonymousQueryResult.ToList<dynamic>();
  2. Підпис методу: DoSomething(List<dynamic> _dynamicResult)
  3. Спосіб виклику: DoSomething(dynamicResult);
  4. зроблено.

Дякую Петру Іванову !


0

Якщо ви знаєте, що ваші результати реалізують певний інтерфейс, ви можете використовувати інтерфейс як тип даних:

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

    }
}

0

Я б використовував IEnumerable<object>як тип для аргументу. Однак не великий виграш для неминучого явного акторського складу. Ура

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.