Функции языка, необходимые для LINQ




Language Integrated Query построен на базе функций языка, многие из которых являются новыми для VB.NET и C#. Давайте же рассмотрим их.

Деревья выражений и λ-выражения

Примечание

Поскольку λ-выражения для VB.NET ещё не реализованы в текущем Community Technology Preview, примеры кода для него не даны. Статья будет обновлена с примерами кода, как только это станет возможным.

Как было сказано раннее, λ-выражения являются ещё более мощным способом сократить объём кода, чем анонимные методы. Например, λ-выражение

Код C# 3.0

forum => forum.Length == 7

эквивалентно анонимному методу

Код C# 3.0

delegate (string forum) {return forum.Length == 7;}

или обычному

Код C# 3.0

bool MyLambda (string forum)

{

return forum.Length == 7;

}

Код VB.NET 9.0

Function MyLambda (forum As String)

Return forum.Length = 7

End Function

 

Но это ещё не всё. λ-выражения приводятся к делегату System.Query.Func<>, который может быть представлен в следующем виде:

· public delegate T Func<T> ()

· public delegate T Func<A0, T> (A0 arg0)

· public delegate T Func<A0, A1, T> (A0 arg0, A1 arg1)

· public delegate T Func<A0, A1, A2, T> (A0 arg0, A1 arg1, A2 arg2)

· public delegate T Func<A0, A1, A2, A3, T> (A0 arg0, A1 arg1, A2 arg2, A3 arg3)

 

Таким образом, код, который мы использовали для запроса, можно переписать так:

Код C# 3.0

Func<string, bool> filter = (forum => forum.Length == 7);

Func<string, string> extract = (forum => forum);

Func<string, string> project = (forum => forum.ToUpper());

 

IEnumerable<string> expr = names.Where(filter)

.OrderBy(extract)

.Select(project);

Без использования λ-выражений этот код выглядел бы даже так:

Код C# 3.0

Func<string, bool> filter = delegate (string forum) {

return forum.Length == 7;

};

 

Func<string, string> extraction = delegate (string forum) {

return forum;

};

 

Func<string, string> projection = delegate (string forum) {

return forum.ToUpper();

};

 

IEnumerable<string> expr = names.Where(filter)

.OrderBy(extraction)

.Select(projection);

На самом деле компилятору абсолютно всё равно, какой синтаксис вы используете. Более того, неужели вы думаете, что я смог удержаться и не глянуть Reflector’ом на то, что генерирует компилятор, когда встречает λ-выражения;-)

Смотрите сами.

В нашем классе появились два метода, названные соответственно _Lambda_Main_9_13 и _Lambda_Main_B_12. Как нетрудно догадаться, это и есть наши λ-выражения, которые компилятор превратил в private-методы. Однако, это не единственный способ использовать λ-выражения.

С приходом LINQ появилось новое пространство имён System.Expressions. В нём определён тип Expression<T>, который позволяет строить деревья выражений (expression trees). Вся суть в том, что при присвоении λ-выражения переменной типа Expression<T> метод не генерируется: λ-выражение сохраняется в бинарном виде. Вот что сказано по этому поводу в MSDN: Expression trees are efficient in-memory data representations of lambda expressions and make the structure of the expression transparent and explicit. Возникает здравый вопрос: зачем же это нужно? Всё очень просто. При проектировании DLinq было необходимо преобразовывать λ-выражение, переданное оператору Where, в SQL-запрос. Разумеется, тело метода трудно обратно декомпилировать и транслировать в SQL, поэтому и ввели деревья выражений.

Допустим, у нас есть два выражения:

Код C# 3.0

Func<string, bool> isVingradFunc = (s => s == "Vingrad");

Expression<Func<string,bool>> isVingradExpr = (s => s == "Vingrad");

В таком случае, этот код скомпилируется:

Код C# 3.0

bool isVingrad = isVingradFunc("NotVingrad");

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

Код C# 3.0

bool isVingrad = isVingradExpr("NotVingrad");

Лично меня очень впечатлила возможность вот такого «разбора» λ-выражения:

Код C# 3.0

Expression<Func<string,bool>> isVingrad = (s => s == "Vingrad");

Console.WriteLine("Number of parameters: {0}", isVingrad.Parameters.Count);

foreach (ParameterExpression p in isVingrad.Parameters)

Console.WriteLine(" Parameter {0} of type {1};", p.Name, p.Type);

Console.WriteLine("Return type: {0}", isVingrad.Body.Type);

MethodCallExpression body = (MethodCallExpression)isVingrad.Body;

Console.WriteLine("Expression calls {0} method", body.Method.Name);

ConstantExpression secondPart = (ConstantExpression)body.Parameters[1];

Console.WriteLine("Second part of expression: {0}", secondPart.Value);

Console.WriteLine("Expression.ToString method: {0}", isVingrad);

Этот код выводит

Number of parameters: 1

Parameter s of type System.String;

Return type: System.Boolean

Expression calls op_Equality method

Second part of expression: Vingrad

Expression.ToString method: |s| op_Equality(s, "Vingrad")

 

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

Для начала нам необходимо познакомиться с методами-расширениями (extension methods). Метод-расширение позволяет нам добавить свой метод ко всем классам. Выглядит примерно так:

 

 

Код C# 3.0

namespace MyExtensions

{

using System.Runtime.CompilerServices;

 

public static class MyExtension

{

[Extension]

public static string GetTypeFullName(this object source)

{

return source.GetType().FullName;

}

 

[Extension]

public static int MakeDouble(this int num)

{

return num * 2;

}

 

}

}

Использование:

Код C# 3.0

namespace ExtensionsTest

{

using MyExtensions;

class Program

{

static void Main(string[] args)

{

object o = new object();

int i = 5;

Console.WriteLine(o.GetTypeFullName());

//Console.WriteLine(MyExtension.GetTypeFullName(o));

Console.WriteLine(i.MakeDouble());

//Console.WriteLine(MyExtension.MakeDouble(i));

Console.Read();

}

}

}

Короче, весьма удобная штука. Но у них ещё одна функция – именно они позволяют нам использовать операторы LINQ. По сути, стандартные операторы Select, Where, OrderBy являются методами-расширениями, определёнными в классе System.Query.OrderedSequence (где-то мы его видели?). Давайте взглянем на методы-расширения более детально. Вот несколько их особенностей:

· Классы, определяющие методы-расширения, должны быть статическими.

· Методы-расширения должны быть объявлены с атрибутом [System.Runtime.CompilerServices.Extension].

· В метод-расширение всегда передаётся параметр, помеченный ключевым словом this – ссылка на экземпляр класса, к которому это расширение применяется. Кстати, тип объекта, к которому расширение можно применить, можно ограничить, просто определив тип этого параметра, отличный от object, как показано в примере с MakeDouble().

· Для импорта методов-расширений, определённых в каком-то классе, необходимо объявить пространство имён, в котором находится этот класс, в директиве using.

 

После того, как мы ознакомились с концепцией методов-расширений в общих чертах, давайте взглянем на конкретный пример реализации запроса:

 

Код C# 3.0

namespace System.Query

{

using System;

using System.Collections.Generic;

 

public static class Sequence {

public static IEnumerable<T> Where<T>(

this IEnumerable<T> source, //Источник – перечисление

Func<T, bool> predicate) { //Функция-условие

 

foreach (T item in source) //Для каждого объекта в источнике

if (predicate(item)) //Если условие выполняется

yield return item; //Добавить в перечисление-результат

}

}

}

Как видно, тип объекта, к которому применяется расширение, ограничен типом IEnumerable<T> (перечислением). Теперь мы можем написать такой код:

Код C# 3.0

IEnumerable<string> coolForum = forums.Where(f => f.Length == 7);

Он будет эквивалентным следующему коду:

Код C# 3.0

IEnumerable<string> coolForum = Sequence.Where(forums,

f => f.Length == 7);

Поскольку, чтобы компилятор «заметил» присутствие статических типов с методами-расширениями, необходимо объявить их в директиве using (Imports в VB), для использования стандартных операторов запросов, как нетрудно догадаться, необходимо написать

 

 

Код C# 3.0

using System.Query; // делает видимыми операторы запросов

Код VB.NET 9.0

Imports System.Query // делает видимыми операторы запросов

 

Все операторы запросов работают с перечислениями IEnumerable<T>, кроме оператора OfType, который работает с перечилениями в стиле 1.0/1.1 фреймворка, «приводя их в порядок», то есть к виду IEnumerable<T>. Разумеется, он также может применяться и к перечислениям вида IEnumerable<T>, позволяя выбирать из перечислений только объекты определённого типа.

Вот пример приведения коллекции из вида 1.0 к виду IEnumerable<T>:

Код C# 3.0

// коллекция в «классическом» виде не может использоваться с запросами...

IEnumerable classic = new OlderCollectionType();

 

//... поэтому мы представим её в виде IEnumerable<object>

IEnumerable<object> modern = classic.OfType<object>();

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

Код C# 3.0

IEnumerable<object> stuff = new object[]{"Vingrad", 1, true, "RSDN"};

IEnumerable<string> forums = stuff.OfType<string>(); //отбираем только строки

Собственно говоря, вот стандартная реализация оператора OfType.

Код C# 3.0

public static IEnumerable<T> OfType<T>(this IEnumerable source) {

foreach (object item in source)

if (item is T)

yield return (T)item;

}



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2019-06-26 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: