Библиотека базовых классов (BCL) в Microsoft .NET Framework является основой этой инфраструктуры. Хотя некоторые из базовых конструкций стабильны и особо не меняются (например, System.Int32 и System.String), Microsoft по-прежнему прикладывает большие усилия в этой области. В статье рассматриваются наиболее крупные (и некоторые мелкие) усовершенствования BCL в .NET Framework 4.5.
Читая эту статью, пожалуйста, помните, что она написана по бета-версии .NET Framework 4.5, а не по конечной версии, поэтому любые API и другие средства могут быть изменены.
Если вас интересует обзор другой функциональности .NET Framework, например Windows Communication Foundation (WCF) или Windows Presentation Foundation (WPF), загляните на страницу MSDN Library «What’s New in the .NET Framework 4.5 Beta» (bit.ly/p6We9u).
Упрощение асинхронного программирования
Применение асинхронного ввода-вывода дает несколько преимуществ. Оно помогает избежать блокирования UI и может уменьшить количество используемых ОС потоков. И тем не менее, вы скорее всего не используете эти преимущества, так как до сих пор асинхронное программирование было весьма сложным. Самая крупная проблема заключалась в том, что Asynchronous Programming Model (APM) была построена вокруг пар методов Begin/End. Чтобы проиллюстрировать, как работает этот шаблон, рассмотрим следующий прямолинейный синхронный метод, который копирует поток данных (stream):
public void CopyTo(Stream source, Stream destination)
{
byte[] buffer = new byte[0x1000];
int numRead;
while ((numRead = source.Read(
buffer, 0, buffer.Length)) != 0)
{
destination.Write(buffer, 0, numRead);
}
}
Чтобы сделать этот метод асинхронным, используя ранее существовавшую модель APM, вам пришлось бы написать код, показанный на рис. 1.
Рис. 1. Асинхронное копирование потока данных старым способом
public void CopyToAsyncTheHardWay(Stream source, Stream destination)
{
byte[] buffer = new byte[0x1000];
Action<IAsyncResult> readWriteLoop = null;
readWriteLoop = iar =>
{
for (bool isRead = (iar == null); ; isRead = !isRead)
{
switch (isRead)
{
case true:
iar = source.BeginRead(buffer, 0, buffer.Length,
readResult =>
{
if (readResult.CompletedSynchronously) return;
readWriteLoop(readResult);
}, null);
if (!iar.CompletedSynchronously) return;
break;
case false:
int numRead = source.EndRead(iar);
if (numRead == 0)
{
return;
}
iar = destination.BeginWrite(buffer, 0,
numRead, writeResult =>
{
if (writeResult.CompletedSynchronously) return;
destination.EndWrite(writeResult);
readWriteLoop(null);
}, null);
if (!iar.CompletedSynchronously) return;
destination.EndWrite(iar);
break;
}
}
};
readWriteLoop(null);
}
Очевидно, что асинхронная версия не столь понятна, как ее синхронный эквивалент. Намерения программиста довольно трудно вычленить из стереотипного кода, необходимого для работы базовых конструкций языка программирования, например циклов, при использовании делегатов. Если вы еще не испугались, попробуйте добавить в этот код обработку исключений и поддержку отмены операций.
К счастью, в этом выпуске BCL введена новая модель асинхронного программирования, основанная на Task и Task<T>. В C# и Visual Basic добавлена полноценная языковая поддержка этой модели на основе новых ключевых слов async и await (кстати, F# уже поддерживает эту модель через асинхронные рабочие процессы и фактически послужил базисом для данной функциональности). В итоге, теперь компиляторы возьмут на себя большую часть стереотипного кода (если не весь), который вам приходилось писать самостоятельно. Новая языковая поддержка в сочетании с определенными API-дополнениями в .NET Framework позволяет писать асинхронные методы почти столь же легко, как и синхронный код. Смотрите сами: чтобы сделать метод CopyTo асинхронным, теперь достаточно ввести лишь выделенные полужирным изменения:
{Для верстки: в следующем листинге заменить цветовые выделения на полужирное начертание.}
public async Task CopyToAsync(Stream source,
Stream destination)
{
byte[] buffer = new byte[0x1000];
int numRead;
while ((numRead = await source.ReadAsync(
buffer, 0, buffer.Length)) != 0)
{
await destination.WriteAsync(buffer, 0, numRead);
}
}
Об асинхронном программировании с применением новых языковых средств можно рассказать много интересного, но я хотел бы сосредоточиться на том, какое влияние это окажет на BCL и ее пользователей. Если же вы хотите узнать все детали об этих языковых средствах, зайдите на страницу MSDN Library «Asynchronous Programming with Async and Await (C# and Visual Basic)» (bit.ly/nXerAc).
Чтобы создавать собственные асинхронные операции, вам понадобятся низкоуровневые строительные блоки. На их основе вы сможете создавать более сложные методы вроде CopyToAsync, показанного ранее. Этот метод требует в качестве строительных блоков только методы ReadAsync и WriteAsync класса Stream. В табл. 1 перечислены некоторые из самых важных асинхронных API, добавленных в BCL.
Табл. 1. Асинхронные методы в BCL
Тип | Методы |
System.IO.Stream | ReadAsync WriteAsync FlushAsync CopyToAsync |
System.IO.TextReader | ReadAsync ReadBlockAsync ReadLineAsync ReadToEndAsync |
System.IO.TextWriter | WriteAsync WriteLineAsync FlushAsync |
Пожалуйста, обратите внимание на то, что мы не добавляли асинхронные версии тех API, которые имеют очень малую гранулярность, такие как TextReader.Peek. Причина в том, что асинхронные API вносят некоторые издержки, и мы хотели избавить разработчиков от случайного движения в неправильном направлении. Это также означает, что мы специально решили воздержаться от предоставления асинхронных версий методов в BinaryReader или BinaryWriter. Чтобы задействовать эти API, мы рекомендуем использовать Task.Run для запуска новой асинхронной операции, а затем применять синхронные API внутри этих операций, но не делайте этого в каждом вызове метода на индивидуальной основе. Общее правило таково: старайтесь сделать свою асинхронную операцию как можно более короткой. Например, если вам нужно считать 1000 значений Int32 из потока данных с помощью BinaryReader, лучше запустить и ожидать один Task для синхронного чтения всей 1000 значений, чем индивидуально запускать и ожидать завершения выполнения 1000 Task, каждый из которых считывает единственное значение Int32.
Если вы хотите узнать больше о написании асинхронного кода, советую почитать блог Parallel Programming with .NET (blogs.msdn.com/b/pfxteam).
Интерфейсы наборов только для чтения
Одним из давних пожеланий к разработчикам BCL было введение интерфейсов наборов только для чтения (не путать с неизменяемыми наборами; детали см. во врезке «Концепции наборов только для чтения»). Наша позиция заключалась в том, что конкретно этот аспект (только для чтения) лучше всего моделируется с использованием шаблона дополнительных средств (optional feature pattern). В этом шаблоне предоставляется API, который позволяет потребителям проверять отсутствие поддержки определенного средства и генерировать NotSupportedException.
Преимущество этого шаблона в том, что он требует меньше типов, поскольку вам не нужно моделировать комбинации разных средств. Например, класс Stream предоставляет несколько средств, и все они выражаются через булевы аксессоры get (CanSeek, CanRead, CanWrite и CanTimeout). Это дает возможность обходиться в BCL единым типом для потоков данных и в то же время поддерживать все комбинации потоковых средств (streaming features).
Со временем мы пришли к выводу, что добавление интерфейса набора только для чтения стоит дополнительного усложнения. Прежде всего я хотел бы показать вам интерфейсы, а затем обсудить предлагаемые ими средства. На рис. 2 приведена схема классов интерфейсов изменяемых наборов, а на рис. 3 — соответствующие интерфейсы наборов только для чтения.
Рис. 2. Интерфейсы изменяемых наборов
Рис. 3. Интерфейсы наборов только для чтения
Заметьте, что IReadOnlyCollection<T> не вошел в бета-версию .NET Framework 4.5, поэтому не удивляйтесь, если вы его не найдете. В бета-версии IReadOnlyList<T> и IReadOnlyDictionary<TKey,TValue> наследуют напрямую от IEnumerable<T>, и в каждом интерфейсе определено собственное свойство Count.
IEnumerable<T> является ковариантным. То есть, если некий метод принимает IEnumerable<Shape>, вы можете вызывать его с IEnumerable<Circle> (при условии, что Circle наследует от Shape). Это полезно в сценариях с иерархиями типов и алгоритмами, которые могут оперировать с определенными типами, например в приложении, рисующем различные фигуры. IEnumerable<T> достаточен для большинства сценариев, где приходится иметь дело с наборами типов, но иногда требуются возможности, более широкие, чем обеспечивает этот интерфейс.
- Материализация IEnumerable<T> не позволяет выражать тот факт, что набор уже доступен («материализован») или что он вычисляется на каждой итерации цикла перебора по нему (скажем, если он представляет LINQ-запрос). Когда алгоритм требует множества итераций по набору, это может привести к падению производительности, если вычисление последовательности влечет за собой значительные издержки; также возможно появление трудноуловимых ошибок из-за расхождений идентичности при повторной генерации объектов на последующих проходах.
- Счетчик IEnumerable<T> не предоставляет счетчик. По сути, он мог бы и отсутствовать, поскольку последовательность может оказаться бесконечной. Однако во многих случаях достаточно хорошо подходит статический метод расширения Enumerable.Count. Во-первых, он по-особому обрабатывает известные типы наборов, такие как ICollection<T>, чтобы избежать итерации всей последовательности. А во-вторых, во многих случаях вычисление результата не является дорогостоящим. Но в зависимости от размера набора разница может оказаться существенной.
- Индексация IEnumerable<T> не разрешает произвольного доступа к элементам. Некоторые алгоритмы вроде быстрой сортировки (quick sort) полагаются на возможность доступа к элементу по его индексу. И вновь существует статический метод расширения (Enumerable.ElementAt), который использует оптимизированную ветвь кода (code path), если данный перечисляемый тип относится к IList<T>. Однако, если индексация используется в цикле, линейное сканирование может иметь катастрофические последствия для производительности, так как ваш изящный алгоритм O(n) превратится в устрашающий O(n2). Таким образом, если вам нужен произвольный доступ, то тут ничего не поделаешь — значит, он вам действительно нужен.
Исходя из этого, почему бы просто-напросто не использовать ICollection<T>/IList<T> вместо IEnumerable<T>? Дело в том, что тогда вы потеряете ковариантность и больше не сможете различать методы, только читающие наборы, и методы, читающие и записывающие наборы. А это становится особенно важным, когда вы используете асинхронное программирование или многопоточность в целом. Другими словами, вам нужно совместить несовместимое.
Рассмотрим IReadOnlyCollection<T> и IReadOnlyList<T>. Первый в основном является тем же, что и IEnumerable<T>, но с добавлением свойства Count. Это позволяет создавать алгоритмы, которые могут выражать потребность в материализованных наборах или в наборах с известным, конечным размером. IReadOnlyList<T> добавляет еще и индексатор. Оба эти интерфейса ковариантны, а значит, если метод принимает IReadOnlyList<Shape>, можно вызывать его с List<Circle>:
class Shape { /*...*/ }
class Circle : Shape { /*...*/ }
void LayoutShapes(IReadOnlyList<Shape> shapes) { /*...*/ }
void LayoutCircles()
{
List<Circle> circles = GetCircles();
LayoutShapes(circles);
}
К сожалению, наша система типов не позволяет сделать типы T ковариантными, если только в них нет методов, принимающих T как входной параметр. Поэтому мы не можем добавить метод IndexOf в IReadOnlyList<T>. Мы считаем это небольшой жертвой по сравнению с потерей поддержки ковариантности.
Все наши реализации встроенных наборов, например массивов, List<T>, Collection<T> и ReadOnlyCollection<T>, также реализуют интерфейсы наборов только для чтения. Поскольку любой набор можно рассматривать как набор только для чтения, в алгоритмах можно объявлять свои намерения более точно, не ограничивая уровень повторного использования; их можно применять для всех типов наборов. В предыдущем примере потребитель LayoutShapes мог бы передать List<Circle>, но LayoutShapes также принимал бы массив Circle или Collection<Circle>.
Другое преимущество этих типов наборов — удобство работы с Windows Runtime (WinRT). WinRT предоставляет собственные типы наборов, такие как Windows.Foundation.Collections.IIterable<T> и Windows.Foundation.Collections.IVector<T>. Уровень метаданных CLR проецирует их напрямую на соответствующие типы данных BCL. Например, при использовании WinRT из .NET Framework IIterable<T> становится IEnumerable<T>, а IVector<T> — IList<T>. По сути, разработчик, использующий Visual Studio и IntelliSense, даже не смог бы заметить, что в WinRT другие типы наборов. Поскольку WinRT также предоставляет версии только для чтения (скажем, IVectorView<T>), новые интерфейсы только для чтения делают картину законченной, и все типы наборов можно легко использовать как в .NET Framework, так и в WinRT.
Поддержка архивов .zip
Еще одно давнее пожелание — введение поддержки чтения и записи обычных архивов .zip. Версии .NET Framework 3.0 и более поздние поддерживают чтение и запись архивов в формате Open Packaging Convention (bit.ly/ddsfZ7). Однако System.IO.Packaging был рассчитан на поддержку только этой спецификации, и его нельзя было использовать для обработки обычных архивов .zip.
В этот выпуск добавлена полноценная поддержка таких архивов через System.IO.Compression.ZipArchive. Кроме того, мы исправили ряд застарелых проблем с производительностью и качеством сжатия в нашей реализации DeflateStream. Начиная с .NET Framework 4.5, класс DeflateStream использует популярную библиотеку zlib. В итоге это обеспечивает более эффективную реализацию алгоритма сжатия и в большинстве случаев позволяет получать сжатые файлы меньшего размера, чем в предыдущих версиях .NET Framework.
Для извлечения всего архива на диск достаточно написать одну строку кода:
ZipFile.ExtractToDirectory(@"P:\files.zip", @"P:\files");
Мы также позаботились о том, чтобы типичные операции не требовали чтения всего архива в память. Например, извлечение одного файла из большого архива можно выполнить так:
using (ZipArchive zipArchive = ZipFile.Open(
@"P:\files.zip", ZipArchiveMode.Read))
{
foreach (ZipArchiveEntry entry in zipArchive.Entries)
{
if (entry.Name == "file.txt")
{
using (Stream stream = entry.Open())
{
ProcessFile(stream);
}
}
}
}
В этом случае в память загружается только таблица содержания Zip-архива. Извлекаемый файл является полностью потоковым, т. е. он не требует размещения в памяти. Это обеспечивает возможность обработки Zip-архивов любого размера даже в условиях ограниченной памяти.
Создание Zip-архивов выполняется аналогично. Чтобы создать Zip-архив из какого-либо каталога, тоже достаточно одной строки:
ZipFile.CreateFromDirectory(@"P:\files", @"P:\files.zip");
Конечно, вы можете конструировать Zip-архив вручную, что дает вам полный контроль над его внутренней структурой. Следующий код показывает, как создать Zip-архив, в который добавляются только файлы исходного кода на C# (в этом архиве будет присутствовать и подкаталог SourceCode):
IEnumerable<string> files =
Directory.EnumerateFiles(@"P:\files", "*.cs");
using (ZipArchive zipArchive =
ZipFile.Open(@"P:\files.zip", ZipArchiveMode.Create))
{
foreach (string file in files)
{
var entryName = Path.Combine("SourceCode", Path.GetFileName(file));
zipArchive.CreateEntryFromFile(file, entryName);
}
}
Используя потоки данных, вы также можете конструировать Zip-архивы, на вход которых подается контент, не хранящийся в физическом файле. То же самое относится и к самому Zip-архиву. Например, вместо ZipFile.Open можно использовать конструктор ZipArchive, который принимает поток данных. На основе этого можно было бы, скажем, создать веб-сервер, который создает Zip-архивы «на лету» из контента, хранящегося в базе данных, и записывает результаты непосредственно в поток ответа, а не на диск.
Одну деталь этого API стоит рассмотреть внимательнее. Вероятно, вы заметили, что статические методы определены в классе ZipFile, тогда как реальный экземпляр имеет тип ZipArchive. Почему так?
Начиная с .NET Framework 4.5, мы проектировали API с учетом портируемости. Некоторые .NET-платформы не поддерживают файловые пути, например .NET для приложений в стиле Metro в Windows 8. На этой платформе доступ к файловой системе осуществляется опосредованно для поддержки модели на основе возможностей (capability-based model). Чтобы читать или записывать файлы, вы больше не можете использовать обычные Win32 API — вместо этого вы должны применять WinRT API.
Как я уже показывал, Zip-функциональность сама по себе вообще не требует файловых путей. Поэтому мы разделили ее на две части.
- System.IO.Compression.dll В этой сборке содержится универсальная Zip-функциональность. Она не поддерживает файловые пути. Главным классом в этой сборке является ZipArchive.
- System.IO.Compression.FileSystem.dll Эта сборка предоставляет статический класс ZipFile, в котором определены методы расширения и статические вспомогательные члены. Эти API дополняют Zip-функциональность необходимыми методами на .NET-платформах, поддерживающих пути в файловой системе.
Чтобы узнать больше о написании портируемого .NET-кода, загляните на страницу Portable Class Libraries в MSDN Library по ссылке bit.ly/z2r3eM.
Прочие усовершенствования
Конечно, всегда можно рассказать гораздо больше. После столь долгой работы над новым выпуском назвать самые важные средства иногда так же трудно, как и сказать, кто из ваших детей самый любимый. Далее я попытаюсь выделить несколько областей, заслуживающих рассмотрения, но на подробное освещение которых в этой статье просто не хватает места.
AssemblyMetadataAttribute Одна из вещей, которым мы научились в .NET Framework касательно атрибутов, состоит в том, что сколько бы их ни было, их всегда не хватает (кстати, в .NET Framework 4 уже более 300 атрибутов уровня сборок). AssemblyMetadataAttribute — универсальный атрибут уровня сборки, позволяющий сопоставить со сборкой строковую пары «ключ-значение». Это можно использовать для того, чтобы указать на главную страницу продукта на вашем сайте или метку системы управления версиями, соответствующую версии исходного кода, из которого была скомпилирована данная сборка.
WeakReference<T> В существующем необобщенном типе WeakReference есть две проблемы. Во-первых, он заставляет потребителей преобразовывать типы при обращении к любой мишени. А во-вторых, что важнее, в его архитектуре имеется промах, из-за которого его использование зачастую создает условия для гонок: он предоставляет один API для проверки того, существует ли объект (IsAlive), и отдельный API для обращения к реальному объекту (Target). WeakReference<T> устраняет эти проблемы, предоставляя единый API TryGetTarget, который выполняет обе операции атомарно.
Comparer<T>.Create(Comparison<T>) BCL позволяет реализовать компараторы (средства сравнения) наборов двумя способами. Один из способов — через интерфейс IComparer<T>, а другой — через делегат Comparison<T>. Преобразование IComparer<T> в Comparison<T> осуществляется просто. В большинстве языков предусмотрено неявное преобразование из метода в тип делегата, поэтому вы можете легко конструировать Comparison<T> из метода IComparer<T> Compare. Однако другое направление требует самостоятельной реализации IComparer<T>. В .NET Framework 4.5 в Comparer<T> добавлен статический метод Create, который на основе данного Comparison<T> дает вам реализацию для IComparer<T>.
ArraySegment<T> В .NET Framework 2.0 появилась struct-конструкция ArraySegment<T>, позволяющая представить подмножество указанного массива без копирования. К сожалению, в ArraySegment<T> не было реализовано никаких интерфейсов наборов, что не позволяло передавать ее в методы, работающие с интерфейсами наборов. В этом выпуске данный промах исправлен, и ArraySegment<T> теперь реализует IEnumerable, IEnumerable<T>, ICollection<T> и IList<T>, а также IReadOnlyCollection<T> и IReadOnlyList<T>.
Концепции наборов только для чтения
- Изменяемый (или не только для чтения) Самый распространенный тип набора в мире .NET. Это такие наборы, как List<T>, которые разрешают чтение, добавление, удаление и изменение элементов.
- Только для чтения Это наборы, которые нельзя модифицировать извне. Однако данная концепция наборов не гарантирует, что их содержимое никогда не изменится. Например, наборы ключей и значений в словаре нельзя обновлять напрямую, но добавление чего-либо в словарь косвенно влечет за собой обновление наборов ключей и значений.
- Неизменяемый Это наборы, которые после создания гарантированно никогда не изменяются. Это неплохое качество в многопоточной среде. Если некая сложная структура данных полностью неизменяема, ее всегда можно безопасно передавать фоновому рабочему потоку. Вам не придется волноваться о том, что в это время кто-то изменит данную структуру. На сегодняшний день такого типа набора в Microsoft .NET Framework BCL нет.
- Замораживаемый Эти наборы ведут себя как изменяемые, пока не будут заморожены (frozen). После этого они становятся эквивалентом неизменяемых наборов. Хотя в BCL эти наборы не определены, вы можете обнаружить их в Windows Presentation Foundation.
Более подробно о различных концепциях наборов можно прочитать в великолепной статье Эндрю Арнотта (Andrew Arnott), опубликованной в блоге по ссылке bit.ly/pDNNdM.
SemaphoreSlim.WaitAsync Это единственный синхронизирующий примитив, поддерживающий асинхронное ожидание захвата блокировки. Если вас интересуют подробности, прочитайте «What’s New for Parallelism in .NET 4.5 Beta» (bit.ly/AmAUIF).
ReadOnlyDictionary<TKey,TValue> Со времен .NET Framework 2.0 в BCL присутствует ReadOnlyCollection<T>, который служит оболочкой только для чтения вокруг указанного экземпляра набора. Это позволяет тем, кто реализует объектные модели, предоставлять наборы, которые потребители не могут изменять. В этом выпуске мы ввели ту же концепцию для словарей.
BinaryReader, BinaryWriter, StreamReader, StreamWriter Все высокоуровневые классы «читателей» и «писателей» принимают в своих конструкторах экземпляр потока данных (stream instance). В предыдущих выпусках это означало, что права владения на этот поток передаются экземпляру «читателя» или «писателя»; при освобождении «читателя» или «писателя» это влекло за собой освобождение и нижележащего потока данных. Это удобно, когда у вас только один «читатель»/«писатель», но создает трудности в тех случаях, где нужно составить несколько разных API, оперирующих потоками данных, и в то же время использовать «читатели»/«писатели» как часть их реализации. Максимум, что вы могли сделать в предыдущих выпусках, — не освобождать «читатель»/«писатель» и оставить комментарий в исходном коде, поясняющий проблему (этот подход также вынуждал вас вручную сбрасывать на диск данные из «писателя», чтобы избежать их потери). .NET Framework 4.5 позволяет выражать этот контракт, используя конструктор «читателя»/«писателя», принимающий параметр leaveOpen, в котором можно явно указывать, что данный «читатель»/«писатель» не должен освобождать нижележащий поток данных.
Console.IsInputRedirected, Console.IsOutputRedirected и Console.IsErrorRedirected Программы командной строки поддерживают перенаправление ввода и вывода. Для большинства приложений эта процедура осуществляется прозрачным образом. Однако в некоторых случаях требуется другое поведение, когда используется перенаправление. Например, цветной вывод в консоли бесполезен, а попытка задания позиции курсора не принесет успеха. Эти свойства позволяют запрашивать, перенаправляется ли какой-нибудь из стандартных потоков данных.
Console.OutputEncoding и Console.InputEncoding теперь можно устанавливать в Encoding.Unicode Установка Console.OutputEncoding в Encoding.Unicode позволяет программе записывать символы, которые не могут быть представлены кодовой страницей OEM, сопоставленной с консолью. Эта функциональность также упрощает отображение текста в нескольких скриптах в консоли. Более подробное описание вскоре будет доступно в переработанной документации на класс Console в MSDN Library.
ExceptionDispatchInfo Обработка ошибок — важный аспект в построении компонентов инфраструктуры. Иногда повторная генерация (re-throwing) исключений (в C# через «throw;») недостаточна, поскольку возможна только внутри обработчика исключений. Некоторые компоненты инфраструктуры, такие как Task, должны повторно генерировать исключение в более поздний момент (скажем, после обратного маршалинга в исходный поток). В прошлом это означало, что трассировка исходного стека и классификация Windows Error Reporting (WER) (также известная как корзины Watson) будет потеряна, так как повторная генерация того же объекта исключения просто приведет к перезаписи этой информации. ExceptionDispatchInfo позволяет захватывать существующий объект исключения и заново генерировать его без потери какой-либо ценной информации, записанной в объекте исключения.
Regex.Timeout Регулярные выражения — отличный способ проверки ввода. Однако мало кому известно, что некоторые регулярные выражения могут оказаться невероятно дорогостоящими в вычислении при применении к определенным видам текстового ввода. Это особенно проблематично в серверных средах, в которые истинные регулярные выражения определяются через конфигурацию. Поскольку очень трудно, если вообще невозможно, предсказать поведение данного регулярного выражения в период выполнения, самый консервативный подход в таких случаях заключается в применении ограничения на то, сколько времени механизм Regex будет пытаться сравнивать полученный ввод. По этой причине в Regex теперь несколько API, принимающих интервал ожидания: Regex.IsMatch, Regex.Match, Regex.Matches, Regex.Split и Regex.Replace.
Заключение
Средства, рассмотренные в этой статье, доступны в Visual Studio 11 Beta. Этот выпуск отвечает нашим высоким стандартам, предъявляемым к предварительным версиям программного обеспечения, поэтому мы поддерживаем его в производственных средах. Вы можете скачать бета-версию по ссылке bit.ly/9JWDT9. Так как это предварительная версия ПО, мы заинтересованы в обратной связи с вами независимо от того, обнаружите вы какие-то проблемы (connect.microsoft.com/visualstudio) или захотите внести предложения по новым средствам (visualstudio.uservoice.com). Я также предложил бы подписаться на блог моей группы (blogs.msdn.com/b/bclteam), чтобы быть в курсе любых предстоящих изменений или объявлений.