Как ссылаться на элемент из анонимного типа

У меня есть такой код

var results =   (from c in Customers  
                    join o in Orders  
                    on c.Id equals o.CustomerId  
                    join p in Products  
                    on p.Id equals o.ProductId  
                    select new   
                    {  
                        CustomerId = c.Id,     // this is a GUID  
                        OrderId = o.Id,        // this is a GUID    
                        ProductName = p.ProductName,  
                     }).ToList();  

Допустим, я хочу получить список всех идентификаторов клиентов, которые заказывают продукт с именем = foo. Моя проблема заключается в том, что, поскольку это анонимный тип, как я могу ссылаться на имя продукта в любом запросе Linq, который я хочу выполнить по результатам?

4 ответа

Решение
var filteredResults = results.Where(r => r.ProductName == "X");

Вывод типа компилятора позаботится об этом за вас. Полный ответ на ваш вопрос:

var customerIds = results
    .Where(r => r.ProductName == "X")
    .Select(r => r.CustomerId)
    .Distinct()
    .ToList();

или же

var customerIds = (from r in results
                  where r.ProductName == "X"
                  select r.CustomerId)
    .Distinct()
    .ToList();

РЕДАКТИРОВАТЬ

Некоторые размышления о выводе типа

Чтобы выбрать длину из последовательности строк с именем list, ты можешь позвонить Select либо используя классический синтаксис статического метода, либо как метод расширения:

Enumerable.Select<string, int>(list, s => s.Length)
list.Select<string, int>(s => s.Length)

Благодаря выводу типа вам не нужны аргументы типа:

Enumerable.Select(list, s => s.Length)
list.Select(s => s.Length)

В этом случае компилятор может доказать, что аргументы типа string а также int просматривая аргументы метода, и он предоставляет эти аргументы типа от вашего имени без необходимости вводить их в исходный код.

Для анонимных типов вы не можете предоставить первый аргумент типа, потому что у типа нет имени для использования в исходном коде (в конце концов, это означает "анонимный": "без имени"). (Таким образом, вы можете видеть, что анонимные типы и вывод типов были как критически важными, так и тесно связанными предпосылками для реализации linq в первую очередь.)

Если вы посмотрите IL для приведенного выше примера анонимного типа, вы увидите, что компилятор на самом деле дал типу имя (которое содержит символы, недопустимые в идентификаторах C#). Когда вы звоните Selectкомпилятор выводит из типа перечисляемого (IEnumerable<CrazilyNamedAnonymousType>) что первый аргумент типа должен быть анонимным, и, как и в случае со строковым примером, он предоставляет это значение от вашего имени.

В продолжение вашего комментария:

Я запутался по поводу объема анонимных типов

Сначала давайте четко определим "сферу". Область видимости типа определяется как область текста программы, в которой тип может указываться его неквалифицированным именем.

С этим определением очевидно, что является областью действия анонимного типа. В программном тексте нет области, в которой анонимный тип может быть указан по имени, потому что у него нет имени. Анонимные типы вообще не имеют области видимости. Вам не нужно беспокоиться о его масштабах; это не имеет смысла.

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

Я не уверен, что будет видимость этого типа в рамках метода или области видимости.

Опять же, давайте четко определим наши термины. Область действия объекта может включать в себя объекты, которые определяют пространства объявлений. Эти пространства объявлений могут объявлять сущности с тем же именем, что и исходная сущность. Эти объекты имеют свои собственные области, которые могут быть вложены в область действия исходного объекта.

В этой ситуации более вложенный объект может "скрыть" менее вложенный объект. Сущность, которая не скрыта таким образом, называется "видимой" в определенном текстовом месте.

Анонимный тип не имеет области видимости и, очевидно, не может быть скрыт по имени, потому что у него нет имени. Вопрос о том, является ли анонимный тип "видимым" или нет, не имеет смысла делать; "видимость" имеет смысл только для вещей, которые имеют имена.

Я думаю, что, учитывая, что этот тип нигде не объявлен, как компилятор может определить, о каком типе я говорю?

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

Затем компилятор может выдавать один тип для каждого из уникальных анонимных типов, которые вы использовали в вашей сборке. Детали того, как это происходит, увлекательны (*). Я предлагаю вам покопаться в вашей сборке с ILDASM, чтобы посмотреть, как мы это сделаем, если вам интересно.

Если вы создаете "одинаковый" анонимный тип - одинаковые имена, типы и в одинаковом порядке - в двух разных сборках, то анонимные типы не будут рассматриваться как один и тот же тип. Анонимные типы не предназначены для использования через границы сборки.


(*) Мне.

В методе, который генерирует этот анонимный типизированный результат, вы можете продолжать ссылаться на результаты так же, как если бы вы определили конкретный тип.

var customersWithFoo = results.Where(r => r.ProductName == "foo")
                              .Select(r => r.CustomerId);

Если вы возвращаете исходный результат запроса из этого метода, а затем хотите запросить его дополнительно или иным образом программно получить доступ к элементам, определите тип.

class QueryResult 
{
   /* relevant properties */
}

А затем спроектируйте этот тип в исходном запросе и верните последовательность этого типа из вашего метода.

public IEnumerable<QueryResult> GetResults()
{
     var results = ...
                   select new QueryResult 
                   {
                       // properties
                   };

     return results;
}

Анонимные типы будут отображаться в intellisense, как и "нормальные" типы. Во время компиляции создается конкретный класс, который представляет ваш анонимный тип, поэтому во время выполнения разница будет очень незначительной.

Вот запрос из вашего набора результатов:

var fooOrders = (from x in results 
                where x.ProductName == "foo"
                select x.CustomerId);
Другие вопросы по тегам