Советы по использованию LINQ

C Sharp > Советы по использованию LINQ
28.11.2016 12:53:18



Статья:

Используйте ключевое слово var, когда запутались

Ключевое слово var необходимо использовать при захвате последовательности от анонимных классов в переменную, иногда это удобный способ заставить код компилироваться, когда возникает путаница со сложными обобщенными типами. Хотя предпочтительнее подход к разработке, при котором точно известно, какого типа данные содержатся в последовательности — в том смысле, что для IEnumerable<T> должен быть известен тип T — иногда, особенно в начале работы с LINQ, это может вводить в заблуждение. Если обнаруживается, что код не компилируется из-за несоответствия типов данных, попробуйте заменить явно установленные типы переменных на указанные с применением ключевым словом var.

 

Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); var orders = db.Customers .Where(с => c.Country == "USA" && c.Region == "WA") .SelectMany(с => c.Orders); Console.WriteLine(orders.GetType());

Используйте операции Cast или OfType для унаследованных коллекций

Вы обнаружите, что большинство стандартных операций запросов LINQ могут быть вызваны на коллекциях, реализующих интерфейс IEnumerable<T>. Ни одна из унаследованных коллекций C# из пространства имен System.Collections не реализует IEnumerable<T>. Поэтому возникает вопрос: как использовать LINQ с унаследованными коллекциями?

Есть две стандартные операции запросов, специально предназначенные для этой цели - Cast и OfType. Обе они могут использоваться для преобразования унаследованных коллекций в последовательности IEnumerable<T>. Ниже показан пример:

// Унаследованная коллекция ArrayList arr = new ArrayList(); arr.Add("one"); arr.Add("two"); arr.Add("three"); // Приведем коллекцию к типу IEnumerable с помощью LINQ IEnumerable<string> numbers1 = arr.Cast<string>().Where(n => n.Length < 4); // То же самое с помощью операции OfType IEnumerable<string> numbers2 = arr.OfType<string>().Where(n => n.Length < 4);

Разница между двумя операциями состоит в том, что Cast пытается привести все элементы в коллекции к указанному типу, помещая их в выходную последовательность. Если в коллекции есть объект типа, который не может быть приведен к указанному генерируется исключение. Операция OfType пытается поместить в выходную последовательность только те элементы, которые могут быть приведены к указанному типу. Отсюда вытекает следующее правило.

Отдавайте предпочтение операции OfType перед Cast

Одной из наиболее важных причин добавления обобщений в C# была необходимость предоставить языку возможность создавать коллекции со статическим контролем типов. До появления обобщений приходилось создавать собственные специфические типы коллекций для каждого типа данных, которые нужно было в них хранить — отсутствовал способ гарантировать, что каждый элемент, помещаемый в унаследованную коллекцию, имеет один и тот же корректный тип. Ничто не могло помешать коду добавить объект TextBox в ArrayList, предназначенный для хранения только объектов Label.

С появлением обобщений в версии C# 2.0 разработчики получили в свои руки способ явно устанавливать, что коллекция может содержать только элементы заданного типа. Хотя операции OfType и Cast могут работать с унаследованными коллекциями, Cast требует, чтобы каждый объект в коллекции относился к правильному типу, что было фундаментальным недостатком унаследованных коллекций, из-за которого появились обобщения.

Когда используется операция Cast и любой из объектов в коллекции не может быть приведен к указанному типу данных, генерируется исключение. С другой стороны, с помощью операции OfType в выходной последовательности IEnumerable<T> будут сохранены только объекты указанного типа, и никаких исключений генерироваться не будет. При лучшем сценарии все объекты относятся к правильному типу, поэтому все попадают в выходную последовательность. В худшем сценарии некоторые элементы будут пропущены, но в случае применения операции Cast они привели бы к исключению.

Не рассчитывайте на безошибочность запросов

Запросы LINQ часто являются отложенными и не выполняются сразу при вызове. Например, рассмотрим следующий фрагмент кода:

string[] greetings = {"one" , "two", "Hello LINQ :)"}; var items = from s in greetings where s.EndsWith("LINQ :)") select s; foreach (var item in items) Console.WriteLine(item) ;

Хотя может показаться, что запрос выполняется при инициализации переменной items, на самом деле это не так. Поскольку операции Where и Select являются отложенными, запрос на самом деле не выполняется в этой точке. Запрос просто вызывается, объявляется или определяется, но не выполняется. Все начинает происходить тогда, когда из него извлекается первый результат. Это обычно происходит при перечислении переменной с результатами запроса. В этом примере результат запроса не востребован до тех пор, пока не запустится оператор foreach. Такое поведение запроса позволяет называть его отложенным.

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

Используйте преимущество отложенных запросов

Следует отметить, что отложенный запрос, который в конечном итоге возвращает IEnumerable<T>, может перечисляться снова и снова, получая последние данные из источника. В этом случае не нужно ни вызывать, ни, как отмечалось ранее, объявлять запрос заново.

В большинстве примеров кода вы увидите вызов запроса и возврат IEnumerable<T> для некоторого типа Т, сохраняемый в переменной. Затем обычно запускается оператор foreach на последовательности IEnumerable<T>. Это реализовано для демонстрационных целей. Если код выполняется много раз, повторный вызов запроса — лишняя работа. Более оправданным может быть наличие метода инициализации запроса, который вызывается однажды в жизненном цикле контекста, и в котором конструируются все запросы. Затем можно выполнить перечисление конкретной последовательности, чтобы получить последнюю версию результатов из запроса.

Используйте свойство Log из DataContext

При работе с LINQ to SQL не забывайте, что класс базы данных, генерируемый SQLMetal, унаследован от System.Data.Linq.DataContext. Это значит, что сгенерированный класс DataContext имеет некоторую полезную встроенную функциональность, такую как свойство Log типа TextWriter.

Одна из полезных возможностей объекта Log состоит в том, что он выводит эквивалентный SQL-оператор запроса IQueryable<T> до подстановки параметров. Случалось ли вам сталкиваться с отказом кода в рабочей среде, который, как вам кажется, вызван данными? Не правда ли, было бы хорошо запустить запрос на базе данных, вводя его в SQL Enterprise Manager или Queiy Analyzer, чтобы увидеть в точности, какие данные он возвращает? Свойство Log класса DataContext выводит запрос SQL:

Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=SSPI;"); db.Log = Console.Out; IQueryable<Order> orders = from c in db.Customers from o in c.Orders where c.Country == "USA" && c.Region == "WA" select o; foreach (Order item in orders) Console.WriteLine("{0} - {1} - {2}", item.OrderDate, item.OrderID, item.ShipName);