Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Автоматическое создание методов для свойств объектов



Часто методы доступа к свойствами объектов — get и set — имеют очень простую реализацию и служат для получения значения соответствующего поля внутри объекта (get) или присвоения этому полю нового значения (set). Создание свойств со стандартными методами доступа может существенно упростить написание кода и сделать его более понятным, т.к. в этом случае компилятор C# автоматически генерирует весь необходимый код. Рассмотрим следующий пример. Пусть у нас есть класс Point, содержащий два свойства:

public class Point

{

private int _x;

private int _y;

public int X { get { return _x; } set { _x = value; } }

public int Y { get { return _y; } set { _y = value; } }

}

Для упрощения описания этого класса мы можем воспользоваться автоматическим созданием свойств и в этом случае описание класса Point будет выглядеть так:

public class Point

{

public int X {get; set; }

public int Y {get; set; }

}

Так как теперь компилятор берет на себя всю работу по реализации методов get и set, мы можем инициализировать наш объект как обычно:

GroundZero p = new Point();

p.X = 0;

p.Y = 0;

Создание свойств описанным выше методом требует, чтобы у свойств были методы доступа get и set, но если мы захотим добавить собственные методы, мы можем воспользоваться подходом, описанным ниже.

Предположим, что у нас есть класс Customer, описанный следующим образом:

public class Customer

{

public string Name { get; set; }

public string City { get; set; }

}

Теперь предположим, что мы хотим добавить еще одно свойство, которое вне класса будет доступно только для чтения. Метод автоматического создания свойств позволяет решить эту задачу путем использования модификатора – в нашем примере это будет модификатор private для метода set:

public class Customer

{

public int CustomerID { get; private set; }

public string Name { get; set; }

public string City { get; set; }

}

Инициализация объектов и коллекций

Следующее расширение синтаксиса языка C#, которое мы рассмотрим, связано с упрощением инициализации объектов и коллекций. При объявлении объекта или коллекции в C# имеется возможность использования т.н. инициализатора, который будет задавать начальные значения членов вновь создаваемых объектов или коллекций. Новый синтаксис позволяет объединить в одном шаге и создание, и инициализацию объекта или коллекции. Рассмотрим пример использования инициализатора для объекта.

Пусть у нас есть класс Point, содержащий два свойства:

public class Point

{

private int _x;

private int _y;

public int X { get { return _x; } set { _x = value; } }

public int Y { get { return _y; } set { _y = value; } }

}

Инициализатор объекта состоит из последовательности инициализаторов членов класса, заключенных в фигурные скобки { } и разделенные запятыми. Каждый инициализатор члена класса присваивает значение полю или свойству объекта. Для нашего класса Point инициализация с использованием нового синтаксиса будет выглядеть так:

Point p = new Point { X = 3, Y = 99 };

Такой компактный синтаксис инициализации объекта семантически эквивалентен вызову конструктора класса и присвоению значения каждому члену класса.

Отметим, что при таком способе инициализации не обязательно указывать в списке все поля объекта – не указанные поля получат значения, присваиваемые по умолчанию.

В языке C# объекты, которые реализуют интерфейс System.Collections. Generic.IEnumerable< T> и имеют метод Add(), могут быть инициализированы с использованием инициализатора коллекций. Используя наш класс Point, мы можем создать коллекцию точек, описывающую какую-то геометрическую фигуру:

List< Point> Square = new List< Point>

{

new Point { X=0, Y=5 },

new Point { X=5, Y=5 },

new Point { X=5, Y=0 },

new Point { X=0, Y=0 }

};

Типизация переменных и массивов

Следующее расширение синтаксиса языка C#, которое мы рассмотрим, связано с типизацией переменных и массивов и позволяет определить тип локальной переменной или элемента массива по выражению, которое используется для инициализации. Для задания переменных, тип которых может быть определен компилятором автоматически, используется конструкция var, а для использования аналогичных возможностей для массивов - синтаксис new[]{…} - обратите внимание на отсутствие указания типа. Сначала приведем «стандартный» синтаксис, который мы использовали для задания и инициализации переменных и массивов в предыдущих версиях языка:

int i = 43;

string s = “...This is only a test...”;

int[] numbers = new int[] { 4, 9, 16};

Используя механизмы типизации для переменных и массивов, мы можем инициализировать переменные и массивы более простым способом:

var i = 43;

var s = “...This is only a test...”;

var numbers = new [] { 4, 9, 16 };

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

var x; // Ошибка: тип не определен

x = new int[] { 1, 2, 3 };

Также мы всегда должны использовать синтаксис new[]{…} при создании инициализируемых массивов — при выполнении следующего кода мы получим ошибку:

var x = {1, 2, 3};

для исправления которой код нужно переписать следующим образом:

var x = new [] {1, 2, 3};

Используя конструкцию var мы можем упростить код некоторых комплексных выражений. Например, вместо использования в механизме итерации foreach четкого указания типа, как показано в следующем примере,

Console.WriteLine(“Customers”);

foreach (Customer c in customers)

Console.WriteLine(c);

мы можем использовать конструкцию var:

Console.WriteLine(“Customers: ”);

foreach (var c in customers)

Console.WriteLine(c);

Методы-расширения

Методы-расширения представляют собой способ расширения существующих типов, а также типов, создаваемых пользователями. Используя этот способ разработчики могут добавлять к существующим типам новые методы, которые будут вызываться с использованием стандартного синтаксиса. Методы-расширения – это статические методы, объявляемые с использованием ключевого слова this в качестве модификатора первого параметра метода. Приведем следующий пример. Предположим, что нам нужна функция сравнения двух объектов типа Customer. В C# 2.0 мы могли бы написать следующий код:

public static class Extensions

{

public static bool Compare(Customer customer1, Customer customer2)

{

if (customer1.Name == customer2.Name & & customer1.City == customer2.City )

{

return true;

}

return false;

}

}

Вызов этого метода может выглядеть так:

foreach (var c in customers)

{

if (Extensions.Compare(newCusomter, c))

{

Console.WriteLine(“Already in the list”);

return;

}

}

В C# 3.0 можно реализовать ту же функциональность с помощью метода-расширения, который будет в контексте объекта вызываться с использованием стандартного синтаксиса. Добавим ключевое слово this в качестве модификатора первого параметра метода Compare();

public static class Extensions

{

public static bool Compare(this Customer customer1, Customer customer2)

{

...

}

}

Тогда наш код проверки двух объектов будет выглядеть следующим образом:

foreach (var c in customers)

{

if (newOrder.Compare(c))

{

Console.WriteLine(“Already in the list”);

return;

}

...

Запомним следующее простое правило: методы-расширения доступны только в том случае, когда они объявлены в статическом классе и находятся в области видимости соответствующего пространства имен. Эти методы будут доступны в качестве дополнительных методов для типов, указанных в качестве первого параметра метода. Методы-расширения могут быть добавлены к любому типу, включая такие встроенные типы, как List< T> и Dictionary< K, V>. Рассмотрим пример расширения функциональности стандартного типа List< T> – добавим к нему метод Append(), который будет объединять два элемента типа List< T> в один:

public static class Extensions

{

public static List< T> Append< T> (this List< T> a, List< T> b)

{

var newList = new List< T> (a);

newList.AddRange(b);

return newList;

}

}

Вызов нового метода-расширения для стандартного типа может выглядеть так:

 

{

...

var addedCustomers = new List< Customer>

{

new Customer { Name = “Paolo Accorti”, City = “Torino” },

new Customer { Name = “Diego Roel”, City = “Madrid” }

};

customers.Append(addedCustomers);

...

}

Методы-расширения предоставляют в распоряжение разработчиков элегантный способ расширения функциональности типов таким образом, что добавленные функции становятся частью типа. Используя методы-расширения можно добавлять новую функциональность к уже откомпилированным классам, включая классы, созданные пользователем и стандартные классы.NET Framework.

Лямбда-выражения

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

В C# 2.0 появились анонимные методы, позволявшие вставлять блоки кода в тех местах, где было возможно использование делегатов. Например:

var innerPoints = points.FindAll(delegate(Point p)

{ return (p.X > 0 & & p.Y > 0); });

Метод FindAll() ожидает параметр в виде делегата. В нашем примере делегат определяет, являются ли координаты x и y положительными, т. е. относится ли точка с заданными координатами к первому квадранту картезианской поверхности.

В C# 3.0 появились лямбда-выражения, которые позволяют использовать более простой синтаксис для написания анонимных методов. Таким образом, предыдущий пример можно переписать так:

var innerPoints = points.FindAll( p => p.X > 0 & & p.Y > 0);

Лямбда-выражение пишется как список параметров, за которым следует символ =>, а за ним – код самого выражения. Например:

(int x) => { return x + 1; }

Параметры лямбда-выражения могут быть непосредственно или опосредованно типизованы:

(int x) => x + 1

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

x => x + 1

(x, y) => x * y;

Предположим, что мы хотим найти в списке объектов Customer все объекты с определенным значением поля City. Для этого мы можем использовать метод FindAll() класса List< T>. Напишем метод FindCustomersByCity(), в котором будем использовать анонимные методы и делегаты используя синтаксис C# 2.0:

public static List< Customer> FindCustomersByCity(

List< Customer> customers,

string city)

{

return customers.FindAll(delegate(Customer c){

return c.City == city;

});

}

Вызов этого метода будет выглядеть следующим образом:

{

var customers = CreateCustomers();

foreach (var c in FindCustomersByCity(customers, “London”))

Console.WriteLine(c);

}

Теперь воспользуемся возможностями C# 3.0 и заменим анонимный метод эквивалентным лямбда" выражением:

public static List< Customer> FindCustomersByCity(

List< Customer> customers, string city)

{

return customers.FindAll(c => c.City == city);

}

Как мы увидели выше, использование лямбда-выражений в некоторых случаях упрощает написание кода. В приведенном выше примере список параметров состоит из нетипизованного параметра ‘c’ и, таким образом, явного указания типа не требуется. В качестве альтернативы мы можем написать следующий код:

return customers.FindAll((Customer c) => c.City == city);

 

LINQ – язык интегрированных запросов на языке C#

 

LINQ представляет собой набор расширений языка, поддерживающий формирование запросов данных способом, безопасным по типам. Запрашиваемые данные могут быть представлены в форме XML (запросы LINQ к XML), баз данных (ADO.NET с поддержкой LINQ, куда входят LINQ к SQL, LINQ к наборам данных и LINQ к экземплярам), объектов (LINQ к объектам) и т.д. Архитектура LINQ показана на рис.1.

Рисунок 1 Архитектура LINQ

Рассмотрим пример интегрированного запроса:

var contacts =

from c in customers

where c.State == " WA"

select new { c.Name, c.Phone };

Здесь из некоторого источника данных customers выбираются имена и телефоны клиентов, проживающих в штате Вашингтон. Запрос очень похож на язык SQL, однако использует строго типизированный синтаксис.

Этот же запрос можно записать и в другом виде – используя лямбда-выражения и методы расширения (см. рис. 2). Именно к такому виду приведёт написанный ранее код компилятор. Ниже будут рассмотрены все эти нововведения в язык.

Рисунок 2 Два способа написания запроса LINQ на языке C# 3.0

 

Язык LINQ построен на использовании стандартных операций запросов, которые представляют собой методы, предназначенные для операций с последовательностями наподобие коллекций, которые реализуют интерфейсы IEnumerable и IQueryable. Как описывалось ранее, когда компилятор С# встречает выражение запроса, то обычно преобразует его в последовательность или цепочку запросов расширяющих методов, реализующих нужное поведение.

Самое большое преимущество такого подхода связано с расширяемостью LINQ. Это значит, что можно определить собственный набор расширяющих методов, а компилятор сгенерирует их вызовы при компиляции выражения запроса LINQ. Например, предположим, что вместо импортирования пространства имен System.Linq и решено предоставить собственную реализацию Where и Select. Это можно сделать следующим образом:

using System;

using System.Collections.Generic;

public static class MySqoSet

{

public static IEnumerable< T> Where< T> (

this IEnumerable< T> source,

System.Func< T, bool> predicate ) {

Console.WriteLine ( " Вызвана собственная реализация Where." );

return System.Linq.Enumerable.Where ( source, predicate );

}

public static IEnumerable< R> Select< T, R> (

this IEnumerable< T> source,

System.Func< T, R> selector ) {

Console.WriteLine ( " Вызвана собственная реализация Select." );

return System.Linq.Enumerable.Select ( source, selector );

}

}

public class CustomSqo

{

static void Main() {

int[] numbers = { 1, 2, 3, 4 };

var query = from x in numbers

where x % 2 == 0

select x * 2;

foreach ( var item in query ) {

Console.WriteLine ( item );

}

}

}

Обратите внимание, что импортировать пространство имен System. Linq не понадобилось. Помимо дополнительного удобства это подтверждает слова о том, что отказ от импорта пространства имен System.Linq предотвращает автоматическое нахождение компилятором расширяющих методов System.Linq.Enumerable. В статическом классе MySqoSet предоставлена собственная реализация стандартных операций запросов Where и Select, которые просто протоколируют сообщение и затем пересылают его операциям из Enumerable.

Большинство стандартных операций запросов LINQ могут быть вызваны на коллекциях, реализующих интерфейс IEnumerable< T>. Ни одна из унаследованных коллекций С# из пространства имен System.Collection не реализует IEnumerable< T>. Поэтому возникает вопрос— как использовать LINQ с унаследован-ными коллекциями из вашего существующего кода?

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

// Построим унаследованную коллекцию. ArrayList arrayList = new ArrayList ();

// Конечно, можно было бы использовать здесь инициализацию коллекций, //но это не работает с унаследованными коллекциями. arrayList.Add(" Adams" ); arrayList.Add(" Arthur" ); arrayList.Add(" Buchanan" );

I£ numerable< string> names = arrayList.Cast< string> ().Where (n => n.Length < 7); foreach(3tring name in names) Console.WriteLine(name);

 

В листинге представлен пример использования операции OfType.

// Построим унаследованную коллекцию. ArrayList arrayList = new ArrayList();

// Конечно, можно было бы использовать здесь инициализацию коллекций, // но это не работает с унаследованными коллекциями. arrayList.Add(" Adams" ); arrayList.Add(" Arthur" ); arrayList.Add(" Buchanan" );

IEnumerable< string> names = arrayList.OfType< string> ().Where (n => n.Length < 1); foreach(string name in names) Console.WriteLine(name);

 

Оба примера дают одинаковый результат:

Adams

Arthur

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

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

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

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

var items =

from s in greetings

where s.EndsWith(" LINQ" )

select s;

foreach (var item in items)

Console.WriteLine(item);

 

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

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

Рассмотрим код в листинге:

string[J strings = { " one", " two", null, " three" };

Console.WriteLine(" Before Where () is called." );

IEnumerable< string> ieStrings = strings.Where (s => s.Length == 3);

Console.WriteLine (" After Where () is called." );

foreach (string s in ieStrings)

Console.WriteLine (" Processing " + s);

 

Я знаю, что третий элемент в массиве строк — null, и я не могу вызвать null. Length без генерации исключения. Выполнение кода благополучно пройдет строку, где вызы-вается запрос. И все будет хорошо до тех пор, пока я не начну перечисление последовательности ieStrings, и не доберусь до третьего элемента, где возникнет исключение. Вот результат этого кода:

Before Where () is called.

After Where() is called.

Processing one

Processing two

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.

 

Как видите, я вызвал операцию Where без исключения. Оно не появилось до тех пор, пока я не попытался в перечислении обратиться к третьему элементу последова-гельности, где и возникло исключение. Теперь представьте, что последовательность ieStrings передана функции, которая дальше выполняет перечисление последовательности - возможно, чтобы наполнить выпадающий список или какой-то другой элемент управления. Легко подумать, что исключение вызвано сбоем в этой функции, а не самим запросом LINQ.

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

Несколько из стандартных операций запросов прототипированы на прием делегата Func в качестве аргумента. Это предотвращает явное объявление типов делегатов. Ниже приведены объявления делегата Func.

public delegate TR Func< TR> ();

public delegate TR Func< T0, TR> (TO a0);

public delegate TR Func< T0, Tl, TR> (TO a0, T1 al);

public delegate TR Func< T0, Tl, T2, TR> (TO a0, Tl al, T2 a2);

public delegate TR Func< T0, Tl, T2, T3, TR> (TO a0, Tl al, T2 a2, T3 a3);

 

В каждом объявлении TR ссылается на возвращаемый тип данных. Обратите внимание, что тип возвращаемого аргумента TR присутствует в конце шаблона параметра типа каждой перегрузки делегата Func. Другие параметры типа – Т0, Tl, T2 и ТЗ — ссылаются на входные параметры, переданные методу. Существует множество объявлений, потому что некоторые стандартные операции запросов имеют аргументы-делегаты, требующие больше параметров, чем другие. Взглянув на объявления, вы можете увидеть, что ни одна из стандартных операций запросов не имеет аргумента-делегата, требующего более четырех входных параметров.

Давайте взглянем на один из прототипов операции Where:

 

public static IEnumerable< T> Where< T> (

this IEnumerable< T> source,

Func< T, bool> predicate);

 

Аргумент-предикат специфицирован как Func< T, bool>. Отсюда вы можете видеть, что метод-предикат или лямбда-выражение должны принимать один аргумент — параметр Т, и возвращать bool. Вы знаете это потому, что вам известен тип возврата, специфицированный в конце списка параметров шаблона.

// Создать массив целых чисел.

int[] ints = new int[] { 1, 2, 3, 4, 5, 6 };

// Объявление нашего делегата.

Func< int, bool> GreaterThanTwo = i => i > 2;

// Выполнить запрос... но не совсем - не забывайте об отложенных запросах! IEnumerable< int> intsGreaterThanTwo = ints.Where(GreaterThanTwo);

// Отобразить результаты.

foreach(int i in intsGreaterThanTwo) Console.WriteLine(i);

Этот код вернет следующие результаты:

ОБОРУДОВАНИЕ

Персональный компьютер: процессо­р с частотой 1, 6ГГц или выше, 1024 МБ ОЗУ, жесткий диск со скоростью 5400 об/мин, видеоадаптер с поддержкой DirectX 9 и с разрешением 1280 х 1024 (или более высоким), операционная система Windows 7, интегрированные среды разработки приложений Visual Studio 2010 и Visual Studio Team System 2008 с комплектами документации MSDN, каталог Tprog\Lab7, содержащий исходные файлы проектов, файл Labtprog7.doc (методи­ческие указания к данной лаборатор­ной работе), не менее 200 Mб свободной памяти на логическом диске, со­держащем каталог Tprog\Lab7.

 

ЗАДАНИЕ НА РАБОТУ

 

4.1. Ознакомиться с основнымим расширениями языка в C# 3.0 и с технологией LINQ для создания интегрированных запросов на языке C#.

4.2. Выполнить приложения на языке LINQ, находящиеся в каталоге Tprog\Lab7.

 

ПОРЯДОК ВЫПОЛНЕНИЯ РАБОТЫ

 

5.1. Проверить наличие на компьютере необходимого аппаратного оборудования и программного обеспечения, наличие 200 Мб свободной памяти на логическом диске, содержащем каталог Tprog\Lab7, наличие файла Labtprog6.doc и исходных файлов в каталоге Tprog\Lab7.

5.2. Создать личный каталог и перекопировать в него выполняемые во время лабораторной работы проекты.

5.3. Поочередно открыть проекты приложений и выполнить их. Проверить правильность работы программ, содержащих запросы на языке LINQ.

 

ОФОРМЛЕНИЕ ОТЧЕТА

 

Отчет должен содержать:

· цель работы;

· тексты исходных файлов приложений, выполняемых на лабораторной работе;

· краткая информация о технологии LINQ..

 

КОНТРОЛЬНЫЕ ВОПРОСЫ

 

 

БИБЛИОГРАФИЧЕСКИЙ СПИСОК

 

1. Раттц-мл. Д.С. LINQ: язык интегрированных запросов в C# 2008 для профессионалов. - М.: ООО " И.Д. Вильямс", 2008. – 560 с.

2. Нэш Т. C# 2010: ускоренный курс для профессионалов.: Пер. с англ. — М.: ООО " И.Д. Вильяме", 2010..— 592с.: ил.

3. Троелсен Э. Язык программирования С# 2008 и платформа.NET 3.5, 4-е изд.: Пер. с англ. — М.: ООО " И.Д. Вильяме", 2010. — 1344 с.: ил.

4. Дейтел, Х.М. C#: пер.с англ. / Х.М.Дейтел [и др.].— СПб.: БХВ-Петербург, 2006. - 1056с.

5. Жарков, В.А. Visual C# 2005 в учебе, науке и технике / В.А.Жарков.— М.: Жарков Пресс, 2007. - 818с.

6. MSDN 2010. Электронная документация Microsoft для разработчиков программного обеспечения. – 500000 с.

 


ЛАБОРАТОРНАЯ РАБОТА № 8

Основные операции языка интегрированных запросов LINQ

 

ЦЕЛЬ И ЗАДАЧИ РАБОТЫ

 

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

 

ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ

 

7.1. Отложенные операции

 

Отложенная операция всегда возвращает тип IEnumerable< T>, по этому признаку их легко отличать. Отложенные операции, рассматриваемые нами, по их назначению можно классифицировать на операции ограничения (Where), проекции (Select, SelectMany), разбиения (Take, TakeWhile, Skip, SkipWhile), конкатенации (Concat), упорядочивания (OrderBy, OrderByDescending, ThenBy, ThenByDescending, Reverse), соединения (Join, JoinGroup), множеств (Distinct, Union, Intersect), преобразования (Cast, OfType, AsEnumerable), элементов (DefaultIfEmpty), генерации (Range, Empty).

 

Операции ограничения

 

Операции ограничения (restriction) используются для включения или исключения элементов из входной последовательности. Операция Where используется для фильтрации элементов в последовательность.

Операция Where имеет два прототипа, описанных ниже. Первый прототип Where:

 

public static IEnumerable< T> Where< T> (

this IEnumerable< T> source,

Func< T, bool> predicate);

 

Второй прототип Where:

 

public static IEnumerable< T> Where< T> (

this IEnumerable< T> source,

Func< T, int, bool> predicate);

 

Второй прототип Where идентичен первому, но с тем отличием, что он указывает на то, что ваш делегат метода-предиката принимает дополнительный целочисленный аргумент. Этот аргумент будет номером-индексом элемента из входной последовательности.

Нумерация индекса начинается с нуля, поэтому индексом первого элемента будет 0. Последний элемент имеет номер, соответствующий количеству элементов в последовательности минус единица.

Пример использования Where:

 

IEnumerable< string> seq = names.Where((p, i=> (I & 1) == 1);

 

 

Операции проекции

 

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

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

Есть два прототипа этой операции, которые описаны ниже.

Первый прототип Select:

 

public static IEnumerable< S> Select< T, S> (

this IEnumerable< T> source,

Func< T, S> selector);

 

Этот прототип Select принимает входную последовательность и делегат метода-селектора в качестве входных параметров, а возвращает объект, который при перечислении проходит по входной последовательности и порождает последовательность элементов типа S. Как упоминалось ранее, Т и S могут быть как одного типа, так и разных.

При вызове Select вы передаете делегат метода-селектора через аргумент selector. Ваш метод-селектор должен принимать тип Т в качестве входного, где Т - тип элементов, содержащихся во входной последовательности, и возвращать элемент типа S. Операция Select вызовет ваш метод-селектор для каждого элемента входной последовательности, передав ему этот элемент. Ваш метод-селектор выберет часть входного элемента, который его интересует, создаст новый элемент — возможно, иного типа (даже анонимного) — и вернет его.

Второй прототип Select:

 

public static IEnumerable< S> Select< T, S> (

this IEnumerable< T> source,

Func< T, int, S> selector);

 

В этом прототипе операции Select методу-селектору передается дополнительный целочисленный параметр. Это будет индекс, начинающийся с нуля, входного элемента во входной последовательности.

Примеры использования Select:

 

var seq = names.Select(p => p.Length);

var seq = names.Select(p => new {p, p.Length});

 

Обратите внимание, что лямбда-выражение создает экземпляр нового анонимного типа. Компилятор динамически сгенерирует анонимный тип, который будет содержать string p и int p.Length, и метод-селектор вернет этот вновь созданный объект.

У меня нет имени типа, по которому можно было бы на него сослаться. Поэтому я не могут присвоить выходную последовательность Select экземпляру IEnumerable< int> определенного известного типа, как делал это в первом примере, где присваивал ее ие-ременной типа IEnumerable< int>, представляющей выходную последовательность. Поэтому я присваиваю выходную последовательность переменной, специфицированной с ключевым словом var.

 

Операции разбиения

 

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

Операция Take возвращает указанное количество элементов из входной последовательности, начиная с ее начала.

Прототип Take:

 

public static IEnumerable< T> Take< T> (

this IEnumerable< T> source,

int count);

 

Этот прототип специфицирует, что Take принимает входную последовательность и целое число count, задающее количество элементов, которые нужно вернуть, и возвращает объект, который при перечислении порождает первые count элементов из входной последовательности.

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

Операция TakeWhile возвращает элементы из входной последовательности, пока истинно некоторое условие, начиная с начала последовательности. Остальные входные элементы пропускаются.

Операция TakeWhile имеет два прототипа, описанные ниже.

Первый прототип TakeWhile:

 

public static IEnumerable< T> TakeWhile< T> (

this IEnumerable< T> source,

Func< T, bool> predicate);

 

Второй прототип TakeWhile:

 

public static IEnumerable< T> TakeWhile< T> (

this IEnumerable< T> source,

Func< T, int, bool> predicate);

 

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

Исключение ArgumentNullException генерируется, если любой из аргументов равен null.

Примеры использования TakeWhile:

 

IEnumerable< string> seq = names.TakeWhile (s => s.Length< 10);

IEnumerable< string> seq = names

.TakeWhile((s, i) => s.Length< 10& & i< 5);

 

 

Операция конкатенации

 

Операция конкатенации позволяет объединить несколько однотипных входных последовательностей в одну выходную.

Операция Concat соединяет две входные последовательности одного и порождает одну выходящую последовательность.

Прототип Concat:

 

public static IEnumerable< T> SkipWhile< T> (

this IEnumerable< T> first,

IEnumerable< T> second);

 

 

Операции упорядочивания

 

Операции упорядочивания позволяют выстраивать входные последовательности в определенном порядке. Важно отметить, что и OrderBy, и OrderByDescending требуют входной последовательности типа IEnumerable< T>, и возвращают последовательность типа IOrderedEnumerable< T>. Вы не можете передать IOrderedEnumerable< T> в качестве входной последовательности операциям OrderBy и OrderByDescending. Это значит, что вы не можете передавать возвращенную последовательности ни из OrderBy, ни из OrderByDescending в последующие вызовы операции OrderBy или OrderByDescending.

Если вам нужно больше упорядочивания, чем это возможно сделать за один вызов операции OrderBy или OrderByDescending, вы должны последовательно вызывать операции ThenBy или ThenByDescending, потому что они принимают в качестве входной последовательности IOrderedEnumerable< T> и возвращают в качестве выходной IOrderedEnumerable< T> последовательности.

Например, следующая последовательность вызовов недопустима:

 

inputSequence.OrderBy(s => s.LastName).OrderBy(s => s.FirstName)...

 

Вместо нее вы должны использовать такую:

 

inputSequence.OrderBy(s => s.LastName).ThenBy(s => s.FirstName)...

 

Операция OrderBy позволяет упорядочить входную последовательность на основе метода keySelector, который возвращает ключевое значение для каждого входного элемента, и упорядоченная выходная последовательность IOrderedEnumerable< T> порождается в порядке возрастания на основе значений возвращенных ключей.

Сортировка, выполненная операцией OrderBy, определена как нестабильная Это значит, что она не предохраняет входной порядок элементов. Если два входных элемента поступают в операцию OrderBy в определенном порядке, и ключевые значения этих двух элементов совпадают, их расположение в выходной последовательности может остаться прежним или поменяться, причем ни то, ни другое не гарантировано. Даже если может показаться, то все стабильно, но поскольку порядок определен как нестабильный, вы всегда должны исходить из этого. Это значит, что вы никогда не должны полагаться на порядок элементов, выходящих из операций OrderBy или OrderByDescending, для любого поля кроме специфицированного в вызове метода. Сохранение любого порядка, присутствующего в последовательности, переданной любой из этих операций, не может быть гарантировано.

Операция OrderBy имеет два прототипа, описанных ниже.

Первый прототип OrderBy:

 

public static IOrderedEnumerable< T> OrderBy< T, K> (

this IEnumerable< T> source,

Func< T, K> keySelector)

where

К: IComparable< K>;

 

В этом прототипе OrderBy входная последовательность source передается в операцию OrderBy наряду с делегатом метода keySelector, и возвращается объект, который при перечислении проходит входную коллекцию source, собирая все элементы и передавая каждый из них методу keySelector, тем самым извлекая каждый ключ и упорядочивая последовательность на основе этих ключей.

Методу keySelector передается входной элемент типа Т и возвращается поле внутри элемента, которое используется в качестве значения ключа типа К этого входного элемента. Типы К и Т могут быть одинаковыми или разными. Тип значения, возвращенного методом keySelector, должен реализовывать интерфейс IComparable.

OrderBy имеет и второй прототип, который выглядит следующим образом:

 

public static IOrderedEnumerable< T> OrderBy< T, K> (

this IEnumerable< T> source,

Func< T, K> keySelector,

IComparer< K> comparer);

 

Этот прототип такой же, как первый, за исключением того, что он позволяет пе-редавать объект-компаратор. Если используется эта версия операции OrderBy, то нет необходимости в том, чтобы тип К реализовывал интерфейс IComparable.

Интерфейс IComparer:

 

 

interface IComparer< T> {

int Compare(T x, T y);

}

 

Интерфейс IComparer требует реализовать единственный метод по имени Compare. Этот метод будет принимать два аргумента одного и того же типа т и возвращать значение int меньше нуля, если первый аргумент меньше второго, ноль — если аргументы эквивалентны, и значение больше нуля, — если второй аргумент меньше первого.

В следующем примере создан класс MyCompare, реализующий интерфейс IComparer, который упорядочивает элементы исходя из частоты гласных букв:

 

MyComparer myComp = new MyComparer();

IEnumerable< string> seq = names

.OrderBy((s => s), myComp);

 

Операция ThenBy позволяет упорядочивать входную последовательность типа IOrderedEnumerable< T> на основе метода keySelector, который вернет значение ключа, и при этом порождается упорядоченная последовательность типа IOrderedEnumerable< T>.

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


Поделиться:



Популярное:

Последнее изменение этой страницы: 2016-05-03; Просмотров: 1088; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.279 с.)
Главная | Случайная страница | Обратная связь