Как передать анонимные типы в качестве параметров?


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/…
Роб Черч

Ответы:


183

Я думаю, что вы должны сделать класс для этого анонимного типа. Это было бы наиболее разумно, по моему мнению. Но если вы действительно не хотите, вы можете использовать динамику:

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

Обратите внимание, что это не является строго типизированным, поэтому, если, например, имя изменяется на EmployeeName, вы не будете знать, что есть проблема до времени выполнения.


Я проверил это как правильный ответ, из-за dynamicиспользования. Я очень пригодился для меня. Спасибо :)
Саид Нимати

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с анонимными типами, поскольку они не реализуют какой-либо интерфейс ...
Джон Скит

@dlev: Вы не можете этого сделать, потому что foreach требует, чтобы переменная повторялась в реализации GetEnumerator, а анонимные типы не гарантировали этого.
CassOnMars

1
@ Джон Скит: хорошая точка зрения, мой мозг недостаточно силен этим утром.
CassOnMars

1
@JonSkeet. Я полагаю, вы могли бы использовать отражение, чтобы по-прежнему получать доступ / устанавливать свойства, если T является анонимным типом, верно? Я имею в виду случай, когда кто-то пишет оператор «Select * from» и использует анонимный (или определенный) класс, чтобы определить, какие столбцы из таблицы результатов запроса совпадают с именованными свойствами вашего анонимного объекта.
С. Тевальт

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>

Мой пример был один, но в большинстве случаев это больше. Ваш ответ через работы (и вполне очевидный сейчас). Мне просто нужен перерыв на обед, чтобы подумать об этом ;-)
Тони Трембат-Дрейк

К вашему сведению: объединено со stackoverflow.com/questions/775387/…
Shog9

Предостережение заключается в том, что при создании правильного класса 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
ре object- или dynamic; п
Марк Грэвелл

Если приведение с «как» вы должны проверить, если список равен нулю
Алекс

"может"! = "должен". 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 анон-типы могут быть изменяемыми.
Марк Гравелл

1
К вашему сведению: объединено со stackoverflow.com/questions/775387/…
Shog9

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
Это правильный ответ! Это просто нужно больше любви :)
Korayem

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.