[C#, ООП, Программирование] Волшебные методы в C# (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет, Хабр!
Сегодня мы предлагаем вам перевод статьи, которую можно отнести к жанру «imho», и которая наверняка заинтересует ценителей C#. Автор рассказывает о самых интересных и полезных, с его точки зрения, методах C# в версии 3 и выше.
В языке C# существует ряд особых сигнатур методов, поддерживаемых на уровне самого языка. Методы с такими сигнатурами приспособлены к использованию особого синтаксиса, обладающего рядом достоинств. Например, эти методы помогают упрощать код, либо позволяют создавать предметно-ориентированные языки, чтобы гораздо четче выразить проблему, специфичную для нашей предметной области. Я в разных контекстах встречал такие методы, поэтому решил написать целую статью и резюмировать все, что мне удалось нарыть по этой теме.
Синтаксис инициализации коллекций
Инициализатор коллекций – довольно старая фича, существует в языке с версии 3 (выпущенной в конце 2007 года). Просто напомню, Collection initializer позволяет заранее заполнять списки, поскольку предоставляет элементы, которые будут находиться в операторе блока:
var list = new List<int> { 1, 2, 3};
Этот код преобразуется в следующий список операторов:
var list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
Collection initializer характерен не только для таких типов, как массивы и коллекции из BCL, а может использоваться с любым типом, удовлетворяющим следующим условиям:
- Реализует интерфейс IEnumerable
- Объявляет метод с сигнатурой void Add(T item)
public class CustomList<T>: IEnumerable
{
public IEnumerator GetEnumerator() => throw new NotImplementedException();
public void Add(T item) => throw new NotImplementedException();
}
Можно добавить поддержку Collection initializer к существующим типам, определив метод Add как метод расширения:
public static class ExistingTypeExtensions
{
public static void Add<T>(ExistingType @this, T item) => throw new NotImplementedException();
}
Этот синтаксис также может использоваться для вставки элементов в поле коллекции, когда в блоке инициализации нет доступного сеттера:
class CustomType
{
public List<string> CollectionField { get; private set; } = new List<string>();
}
class Program
{
static void Main(string[] args)
{
var obj = new CustomType
{
CollectionField =
{
"item1",
"item2"
}
};
}
}
Метод Add может иметь более одного параметра:
public class CustomList<T>: IEnumerable
{
public IEnumerator GetEnumerator() => throw new NotImplementedException();
public void Add(T item, string extraParam1, int extraParam1) => throw new NotImplementedException();
}
Для использования такой перегрузки внутри блока инициализации необходимо заключить все параметры в дополнительную пару фигурных скобок:
var obj = new CustomType
{
CollectionField =
{
{"item1", "extraParamVal1", 2 },
{"item2", "extraParamVal2", 3 }
}
};
Collection initializer довольно часто применяется для инициализации коллекции с хорошо известным количеством элементов, но его можно задействовать и для задания коллекции с динамическим количеством элементов. В обоих случаях синтаксис будет идентичен:
var obj = new CustomType
{
CollectionField =
{
existingItems
}
};
Это можно делать с типами, удовлетворяющими следующим условиям:
- Реализует интерфейс IEnumerable
- Объявляет метод с сигнатурой void Add(IEnumerable<T> items)
public class CustomList<T>: IEnumerable
{
public IEnumerator GetEnumerator() => throw new NotImplementedException();
public void Add(IEnumerable<T> items) => throw new NotImplementedException();
}
К сожалению, массив и коллекции из BCL не реализуют метод void Add(IEnumerable<T> items), но это можно легко изменить, определив метод расширения для уже существующих типов коллекций:
public static class ListExtensions
{
public static void Add<T>(this List<T> @this, IEnumerable<T> items) => @this.AddRange(items);
}
Благодаря такому методу расширения, теперь можно писать код, имеющий следующий вид:
var obj = new CustomType
{
CollectionField =
{
existingItems.Where(x => /*Filter items*/) .Select(x => /*Map items*/)
}
};
или даже собрать результирующую коллекцию, смешав отдельные элементы и результаты, полученные от множественных enumerables:
var obj = new CustomType
{
CollectionField =
{
individualElement1,
individualElement2,
list1.Where(x => /*Filter items*/) .Select(x => /*Map items*/),
list2.Where(x => /*Filter items*/) .Select(x => /*Map items*/)
}
};
Без такого синтаксиса было бы очень сложно достичь подобного результата внутри блока инициализации.
Я открыл эту языковую фичу случайно, работая с отображениями для типов с полями коллекций, сгенерированными из контрактов protobuf. Если вы не знакомы с protobuf, но вам доводилось использовать grpctools для генерации дотнетовских типов из файлов proto, то обращу внимание, что все типы коллекций генерируются следующим образом:
[DebuggerNonUserCode]
public RepeatableField<ItemType> SomeCollectionField
{
get
{
return this.someCollectionField_;
}
}
Как видите, поля коллекций в сгенерированном коде не имеют сеттера, но нет худа без добра: RepeatableField реализует void Add(IEnumerable items), что позволяет инициализировать их в блоке инициализации:
/// <summary>
/// Вносит все указанные значения в эту коллекцию. Данный метод нужен, чтобы
/// обеспечивать создание повторяющихся полей из запросов внутри инициализаторов коллекций.
/// В коде инициализатора, не относящемся к коллекциям, попробуйте использовать эквивалентный <see cref="AddRange"/>
/// метод для ясности.
/// </summary>
/// <param name="values">Значения, которую требуется добавить в эту коллекцию.</param>
public void Add(IEnumerable<T> values)
{
AddRange(values);
}
Синтаксис инициализации словарей
Среди крутых фич, появившихся в C# 6, были инициализаторы индекса, упростившие синтаксис инициализации словарей. Благодаря им, можно писать init-код словарей в гораздо более удобочитаемом виде:
var errorCodes = new Dictionary<int, string>
{
[404] = "Page not Found",
[302] = "Page moved, but left a forwarding address.",
[500] = "The web server can't come out to play today."
};
Этот код преобразуется в:
var errorCodes = new Dictionary<int, string>();
errorCodes[404] = "Page not Found";
errorCodes[302] = "Page moved, but left a forwarding address.";
errorCodes[500] = "The web server can't come out to play today.";
Не так много, но в результате определенно становится гораздо удобнее писать и читать код.
Самая классная черта Index initializer в том, что они применимы не только с классом Dictionary<>, но и с любым типом, определяющим indexer:
class HttpHeaders
{
public string this[string key]
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
}
class Program
{
static void Main(string[] args)
{
var headers = new HttpHeaders
{
["access-control-allow-origin"] = "*",
["cache-control"] = "max-age=315360000, public, immutable"
};
}
}
Деконструкторы
В C# 7.0 наряду с кортежами был введен механизм деконструкции. Деконструкторы позволяют «декомпозировать» кортеж в набор отдельных переменных, вот так:
var point = (5, 7);
// декомпозиция кортежа в отдельные переменные
var (x, y) = point;
что эквивалентно:
ValueTuple<int, int> point = new ValueTuple<int, int>(1, 4);
int x = point.Item1;
int y = point.Item2;
Такой синтаксис также позволяет попеременно использовать значения двух переменных без необходимости явно объявлять третью переменную:
int x = 5, y = 7;
//переключение
(x, y) = (y,x);
… или более лаконично инициализировать член:
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
}
Деконструкторы могут использоваться не только с кортежами, но и с собственными типами. Чтобы была возможна деконструкция собственного типа, он должен реализовывать метод, подчиняющийся следующим правилам:
- Называется Deconstruct
- Возвращает void
- Каждый из его параметров должен определяться с модификатором out
Для нашего типа Point мы можем определить деконструктор следующим образом:
class Point
{
public int X {get;}
public int Y {get;}
public Point(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}
а пример его использования может выглядеть так:
var point = new Point(2,4);
var (x, y)= point;
что под капотом выглядит так:
int x;
int y;
new Point(2, 4).Deconstruct(out x, out y);
Деконструкторы можно добавлять к типам, объявленным вне исходного кода, определяя их как метод расширения:
public static class PointExtensions
{
public static void Deconstruct(this Point @this, out int x, out int y) => (x, y) = (@this.X, @this.Y);
}
Среди наиболее полезных деконструкторов отметим предназначенный для KeyValuePair<TKey,TValue>, обеспечивающий легкий доступ к ключу и значению при переборе словаря:
foreach(var (key, value) in new Dictionary<int, string> { [1] = "val1", [2] = "val2" })
{
// делаем что-нибудь
}
KeyValuePair<TKey,TValue>.Deconstruct(TKey, TValue) доступен лишь начиная с netstandard2.1. В предыдущих версиях netstandard его было необходимо добавлять вручную, это делалось при помощи метода расширения.
Собственные ожидаемые типы
Версия C# 5 (выпущенная вместе с Visual Studio 2012) ввела в дело механизм async/await, поистине переломивший ситуацию в области асинхронного программирования. Ранее обработка вызова асинхронных методов весьма часто требовала писать весьма путаный код, особенно в случаях, когда асинхронный вызов был не один:
void DoSomething()
{
DoSomethingAsync().ContinueWith((task1) => {
if (task1.IsCompletedSuccessfully)
{
DoSomethingElse1Async(task1.Result).ContinueWith((task2) => {
if (task2.IsCompletedSuccessfully)
{
DoSomethingElse2Async(task2.Result).ContinueWith((task3) => {
//TODO: Do something
});
}
});
}
});
}
private Task<int> DoSomethingAsync() => throw new NotImplementedException();
private Task<int> DoSomethingElse1Async(int i) => throw new NotImplementedException();
private Task<int> DoSomethingElse2Async(int i) => throw new NotImplementedException();
Синтаксис async/await позволяет записать то же самое гораздо чище:
async Task DoSomething()
{
var res1 = await DoSomethingAsync();
var res2 = await DoSomethingElse1Async(res1);
await DoSomethingElse2Async(res2);
}
Возможно, вы удивитесь, но ключевое слово await не зарезервировано для работы только лишь с типом Task. Оно может использоваться с любым типом, который содержит метод GetAwaiter и возвращает тип, удовлетворяющий следующему требованию:
- Реализует интерфейс System.Runtime.CompilerServices.INotifyCompletion с методом void OnCompleted(Action continuation).
- Содержит булево свойство IsCompleted.
- Содержит метод GetResult, не имеющий параметров
Чтобы добавить поддержку ключевого слова await к собственному типу, необходимо определить метод GetAwaiter, возвращающий экземпляр TaskAwaiter<TResult> или собственный тип, удовлетворяющий вышеуказанным условиям.
class CustomAwaitable
{
public CustomAwaiter GetAwaiter() => throw new NotImplementedException();
}
class CustomAwaiter: INotifyCompletion
{
public void OnCompleted(Action continuation) => throw new NotImplementedException();
public bool IsCompleted => => throw new NotImplementedException();
public void GetResult() => throw new NotImplementedException();
}
Возможно, вам интересно, существует ли сценарий использования синтаксиса await с собственным ожидаемым типом. Если я угадал, то настоятельно рекомендую вам статью Стивена Тауба под названием “Await Anything”, в которой вас ждет масса интересных примеров.
Паттерн выражений запросов
Наилучшим изобретением в рамках C# 3.0 определенно был Language-Integrated Query, также известный как LINQ, обеспечивающий операции над коллекциями при помощи SQL-подобного синтаксиса. Существует две вариации LINQ: SQL-подобный синтаксис и синтаксис методов расширений. Я предпочитаю второй вариант, так как он кажется мне более удобочитаемым; наверное, я просто привык к нему. Интересный факт о SQL-подобном синтаксисе: оказывается, он преобразуется в синтаксис методов расширений при компиляции, так как это фича C#, а не CLR. LINQ был изобретен, прежде всего, для работы с типами IEnumerable, IEnumerable<T> и IQueryable<T>, но применим не только с ними, а с любым типом, удовлетворяющим требованиям паттерна выражений запросов. Вот полный список сигнатур методов, используемых LINQ:
class C
{
public C<T> Cast<T>();
}
class C<T> : C
{
public C<T> Where(Func<T,bool> predicate);
public C<U> Select<U>(Func<T,U> selector);
public C<V> SelectMany<U,V>(Func<T,C<U>> selector, Func<T,U,V> resultSelector);
public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector, Func<U,K> innerKeySelector, Func<T,U,V> resultSelector);
public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector, Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector);
public O<T> OrderBy<K>(Func<T,K> keySelector);
public O<T> OrderByDescending<K>(Func<T,K> keySelector);
public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector);
public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector, Func<T,E> elementSelector);
}
class O<T> : C<T>
{
public O<T> ThenBy<K>(Func<T,K> keySelector);
public O<T> ThenByDescending<K>(Func<T,K> keySelector);
}
class G<K,T> : C<T>
{
public K Key { get; }
}
Естественно, не требуется использовать всех этих методов, чтобы использовать синтаксис LINQ с нашим собственным типом. Отличное объяснение того, как работать с этим синтаксисом, приведено в статье Understand monads with LINQ.
Итоги
В этой статье я не пытался склонить вас к злоупотреблению перечисленными здесь синтаксическими трюками, а, скорее, хотел разъяснить их. С другой стороны, не следует полностью их избегать. Они были изобретены, так как в них имелась нужда, и иногда они позволяют сделать код гораздо чище. Если вы опасаетесь, что получится код, устройство которого покажется неочевидным вашим коллегам, просто посоветуйте им почитать эту статью;).
===========
Источник:
habr.com
===========
===========
Автор оригинала: CEZARY PIĄTEK
===========Похожие новости:
- [Занимательные задачки, Программирование, C++, Алгоритмы] Обобщение классической задачи на множества с собеседований
- [Программирование] Как я начал сходить с ума от программирования
- [IT-компании, Карьера в IT-индустрии, Промышленное программирование, Учебный процесс в IT] Всесезонная стажировка в Яндексе — новый набор и подробности
- [Высокая производительность, Поисковые технологии, Программирование, Алгоритмы, Go] Текстовый индекс по котировкам в памяти на Go (перевод)
- [C#, Unity] Простая имитация разрушений с использованием Unity и Blender
- [Ненормальное программирование] Язык J становится ближе к людям
- [JavaScript, Программирование, Разработка веб-сайтов] JavaScript: полное руководство по классам (перевод)
- [Assembler, Игры и игровые приставки, Ненормальное программирование, Разработка игр] Эмуляция NES/Famicom/Денди на веб-технологиях. Доклад Яндекса
- [.NET, ASP, C#, Программирование, Тестирование IT-систем] Я 20 лет наслаждаюсь разнообразием архитектур и хочу поделиться мыслями
- [Программирование, Java, .NET] Мне надоело, что индустрия зависит от прихоти создателей языков программирования. Сообществу нужно больше власти
Теги для поиска: #_c#, #_oop (ООП), #_programmirovanie (Программирование), #_c#, #_.net, #_oop (ООП), #_programmirovanie (программирование), [url=https://torrents-local.xyz/search.php?nm=%23_blog_kompanii_izdatelskij_dom_«piter»&to=0&allw=0&o=1&s=0&f%5B%5D=820&f%5B%5D=959&f%5B%5D=958&f%5B%5D=872&f%5B%5D=967&f%5B%5D=954&f%5B%5D=885&f%5B%5D=882&f%5B%5D=863&f%5B%5D=881&f%5B%5D=860&f%5B%5D=884&f%5B%5D=865&f%5B%5D=873&f%5B%5D=861&f%5B%5D=864&f%5B%5D=883&f%5B%5D=957&f%5B%5D=859&f%5B%5D=966&f%5B%5D=956&f%5B%5D=955]#_blog_kompanii_izdatelskij_dom_«piter» (
Блог компании Издательский дом «Питер»
)[/url], #_c#, #_oop (
ООП
), #_programmirovanie (
Программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:58
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет, Хабр! Сегодня мы предлагаем вам перевод статьи, которую можно отнести к жанру «imho», и которая наверняка заинтересует ценителей C#. Автор рассказывает о самых интересных и полезных, с его точки зрения, методах C# в версии 3 и выше. В языке C# существует ряд особых сигнатур методов, поддерживаемых на уровне самого языка. Методы с такими сигнатурами приспособлены к использованию особого синтаксиса, обладающего рядом достоинств. Например, эти методы помогают упрощать код, либо позволяют создавать предметно-ориентированные языки, чтобы гораздо четче выразить проблему, специфичную для нашей предметной области. Я в разных контекстах встречал такие методы, поэтому решил написать целую статью и резюмировать все, что мне удалось нарыть по этой теме. Синтаксис инициализации коллекций Инициализатор коллекций – довольно старая фича, существует в языке с версии 3 (выпущенной в конце 2007 года). Просто напомню, Collection initializer позволяет заранее заполнять списки, поскольку предоставляет элементы, которые будут находиться в операторе блока: var list = new List<int> { 1, 2, 3};
Этот код преобразуется в следующий список операторов: var list = new List<int>();
list.Add(1); list.Add(2); list.Add(3); Collection initializer характерен не только для таких типов, как массивы и коллекции из BCL, а может использоваться с любым типом, удовлетворяющим следующим условиям:
public class CustomList<T>: IEnumerable
{ public IEnumerator GetEnumerator() => throw new NotImplementedException(); public void Add(T item) => throw new NotImplementedException(); } Можно добавить поддержку Collection initializer к существующим типам, определив метод Add как метод расширения: public static class ExistingTypeExtensions
{ public static void Add<T>(ExistingType @this, T item) => throw new NotImplementedException(); } Этот синтаксис также может использоваться для вставки элементов в поле коллекции, когда в блоке инициализации нет доступного сеттера: class CustomType
{ public List<string> CollectionField { get; private set; } = new List<string>(); } class Program { static void Main(string[] args) { var obj = new CustomType { CollectionField = { "item1", "item2" } }; } } Метод Add может иметь более одного параметра: public class CustomList<T>: IEnumerable
{ public IEnumerator GetEnumerator() => throw new NotImplementedException(); public void Add(T item, string extraParam1, int extraParam1) => throw new NotImplementedException(); } Для использования такой перегрузки внутри блока инициализации необходимо заключить все параметры в дополнительную пару фигурных скобок: var obj = new CustomType
{ CollectionField = { {"item1", "extraParamVal1", 2 }, {"item2", "extraParamVal2", 3 } } }; Collection initializer довольно часто применяется для инициализации коллекции с хорошо известным количеством элементов, но его можно задействовать и для задания коллекции с динамическим количеством элементов. В обоих случаях синтаксис будет идентичен: var obj = new CustomType
{ CollectionField = { existingItems } }; Это можно делать с типами, удовлетворяющими следующим условиям:
public class CustomList<T>: IEnumerable
{ public IEnumerator GetEnumerator() => throw new NotImplementedException(); public void Add(IEnumerable<T> items) => throw new NotImplementedException(); } К сожалению, массив и коллекции из BCL не реализуют метод void Add(IEnumerable<T> items), но это можно легко изменить, определив метод расширения для уже существующих типов коллекций: public static class ListExtensions
{ public static void Add<T>(this List<T> @this, IEnumerable<T> items) => @this.AddRange(items); } Благодаря такому методу расширения, теперь можно писать код, имеющий следующий вид: var obj = new CustomType
{ CollectionField = { existingItems.Where(x => /*Filter items*/) .Select(x => /*Map items*/) } }; или даже собрать результирующую коллекцию, смешав отдельные элементы и результаты, полученные от множественных enumerables: var obj = new CustomType
{ CollectionField = { individualElement1, individualElement2, list1.Where(x => /*Filter items*/) .Select(x => /*Map items*/), list2.Where(x => /*Filter items*/) .Select(x => /*Map items*/) } }; Без такого синтаксиса было бы очень сложно достичь подобного результата внутри блока инициализации. Я открыл эту языковую фичу случайно, работая с отображениями для типов с полями коллекций, сгенерированными из контрактов protobuf. Если вы не знакомы с protobuf, но вам доводилось использовать grpctools для генерации дотнетовских типов из файлов proto, то обращу внимание, что все типы коллекций генерируются следующим образом: [DebuggerNonUserCode]
public RepeatableField<ItemType> SomeCollectionField { get { return this.someCollectionField_; } } Как видите, поля коллекций в сгенерированном коде не имеют сеттера, но нет худа без добра: RepeatableField реализует void Add(IEnumerable items), что позволяет инициализировать их в блоке инициализации: /// <summary>
/// Вносит все указанные значения в эту коллекцию. Данный метод нужен, чтобы /// обеспечивать создание повторяющихся полей из запросов внутри инициализаторов коллекций. /// В коде инициализатора, не относящемся к коллекциям, попробуйте использовать эквивалентный <see cref="AddRange"/> /// метод для ясности. /// </summary> /// <param name="values">Значения, которую требуется добавить в эту коллекцию.</param> public void Add(IEnumerable<T> values) { AddRange(values); } Синтаксис инициализации словарей Среди крутых фич, появившихся в C# 6, были инициализаторы индекса, упростившие синтаксис инициализации словарей. Благодаря им, можно писать init-код словарей в гораздо более удобочитаемом виде: var errorCodes = new Dictionary<int, string>
{ [404] = "Page not Found", [302] = "Page moved, but left a forwarding address.", [500] = "The web server can't come out to play today." }; Этот код преобразуется в: var errorCodes = new Dictionary<int, string>();
errorCodes[404] = "Page not Found"; errorCodes[302] = "Page moved, but left a forwarding address."; errorCodes[500] = "The web server can't come out to play today."; Не так много, но в результате определенно становится гораздо удобнее писать и читать код. Самая классная черта Index initializer в том, что они применимы не только с классом Dictionary<>, но и с любым типом, определяющим indexer: class HttpHeaders
{ public string this[string key] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } } class Program { static void Main(string[] args) { var headers = new HttpHeaders { ["access-control-allow-origin"] = "*", ["cache-control"] = "max-age=315360000, public, immutable" }; } } Деконструкторы В C# 7.0 наряду с кортежами был введен механизм деконструкции. Деконструкторы позволяют «декомпозировать» кортеж в набор отдельных переменных, вот так: var point = (5, 7);
// декомпозиция кортежа в отдельные переменные var (x, y) = point; что эквивалентно: ValueTuple<int, int> point = new ValueTuple<int, int>(1, 4); int x = point.Item1; int y = point.Item2; Такой синтаксис также позволяет попеременно использовать значения двух переменных без необходимости явно объявлять третью переменную: int x = 5, y = 7;
//переключение (x, y) = (y,x); … или более лаконично инициализировать член: class Point
{ public int X { get; } public int Y { get; } public Point(int x, int y) => (X, Y) = (x, y); } Деконструкторы могут использоваться не только с кортежами, но и с собственными типами. Чтобы была возможна деконструкция собственного типа, он должен реализовывать метод, подчиняющийся следующим правилам:
Для нашего типа Point мы можем определить деконструктор следующим образом: class Point
{ public int X {get;} public int Y {get;} public Point(int x, int y) => (X, Y) = (x, y); public void Deconstruct(out int x, out int y) => (x, y) = (X, Y); } а пример его использования может выглядеть так: var point = new Point(2,4);
var (x, y)= point; что под капотом выглядит так: int x;
int y; new Point(2, 4).Deconstruct(out x, out y); Деконструкторы можно добавлять к типам, объявленным вне исходного кода, определяя их как метод расширения: public static class PointExtensions
{ public static void Deconstruct(this Point @this, out int x, out int y) => (x, y) = (@this.X, @this.Y); } Среди наиболее полезных деконструкторов отметим предназначенный для KeyValuePair<TKey,TValue>, обеспечивающий легкий доступ к ключу и значению при переборе словаря: foreach(var (key, value) in new Dictionary<int, string> { [1] = "val1", [2] = "val2" })
{ // делаем что-нибудь } KeyValuePair<TKey,TValue>.Deconstruct(TKey, TValue) доступен лишь начиная с netstandard2.1. В предыдущих версиях netstandard его было необходимо добавлять вручную, это делалось при помощи метода расширения. Собственные ожидаемые типы Версия C# 5 (выпущенная вместе с Visual Studio 2012) ввела в дело механизм async/await, поистине переломивший ситуацию в области асинхронного программирования. Ранее обработка вызова асинхронных методов весьма часто требовала писать весьма путаный код, особенно в случаях, когда асинхронный вызов был не один: void DoSomething()
{ DoSomethingAsync().ContinueWith((task1) => { if (task1.IsCompletedSuccessfully) { DoSomethingElse1Async(task1.Result).ContinueWith((task2) => { if (task2.IsCompletedSuccessfully) { DoSomethingElse2Async(task2.Result).ContinueWith((task3) => { //TODO: Do something }); } }); } }); } private Task<int> DoSomethingAsync() => throw new NotImplementedException(); private Task<int> DoSomethingElse1Async(int i) => throw new NotImplementedException(); private Task<int> DoSomethingElse2Async(int i) => throw new NotImplementedException(); Синтаксис async/await позволяет записать то же самое гораздо чище: async Task DoSomething()
{ var res1 = await DoSomethingAsync(); var res2 = await DoSomethingElse1Async(res1); await DoSomethingElse2Async(res2); } Возможно, вы удивитесь, но ключевое слово await не зарезервировано для работы только лишь с типом Task. Оно может использоваться с любым типом, который содержит метод GetAwaiter и возвращает тип, удовлетворяющий следующему требованию:
Чтобы добавить поддержку ключевого слова await к собственному типу, необходимо определить метод GetAwaiter, возвращающий экземпляр TaskAwaiter<TResult> или собственный тип, удовлетворяющий вышеуказанным условиям. class CustomAwaitable
{ public CustomAwaiter GetAwaiter() => throw new NotImplementedException(); } class CustomAwaiter: INotifyCompletion { public void OnCompleted(Action continuation) => throw new NotImplementedException(); public bool IsCompleted => => throw new NotImplementedException(); public void GetResult() => throw new NotImplementedException(); } Возможно, вам интересно, существует ли сценарий использования синтаксиса await с собственным ожидаемым типом. Если я угадал, то настоятельно рекомендую вам статью Стивена Тауба под названием “Await Anything”, в которой вас ждет масса интересных примеров. Паттерн выражений запросов Наилучшим изобретением в рамках C# 3.0 определенно был Language-Integrated Query, также известный как LINQ, обеспечивающий операции над коллекциями при помощи SQL-подобного синтаксиса. Существует две вариации LINQ: SQL-подобный синтаксис и синтаксис методов расширений. Я предпочитаю второй вариант, так как он кажется мне более удобочитаемым; наверное, я просто привык к нему. Интересный факт о SQL-подобном синтаксисе: оказывается, он преобразуется в синтаксис методов расширений при компиляции, так как это фича C#, а не CLR. LINQ был изобретен, прежде всего, для работы с типами IEnumerable, IEnumerable<T> и IQueryable<T>, но применим не только с ними, а с любым типом, удовлетворяющим требованиям паттерна выражений запросов. Вот полный список сигнатур методов, используемых LINQ: class C
{ public C<T> Cast<T>(); } class C<T> : C { public C<T> Where(Func<T,bool> predicate); public C<U> Select<U>(Func<T,U> selector); public C<V> SelectMany<U,V>(Func<T,C<U>> selector, Func<T,U,V> resultSelector); public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector, Func<U,K> innerKeySelector, Func<T,U,V> resultSelector); public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector, Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector); public O<T> OrderBy<K>(Func<T,K> keySelector); public O<T> OrderByDescending<K>(Func<T,K> keySelector); public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector); public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector, Func<T,E> elementSelector); } class O<T> : C<T> { public O<T> ThenBy<K>(Func<T,K> keySelector); public O<T> ThenByDescending<K>(Func<T,K> keySelector); } class G<K,T> : C<T> { public K Key { get; } } Естественно, не требуется использовать всех этих методов, чтобы использовать синтаксис LINQ с нашим собственным типом. Отличное объяснение того, как работать с этим синтаксисом, приведено в статье Understand monads with LINQ. Итоги В этой статье я не пытался склонить вас к злоупотреблению перечисленными здесь синтаксическими трюками, а, скорее, хотел разъяснить их. С другой стороны, не следует полностью их избегать. Они были изобретены, так как в них имелась нужда, и иногда они позволяют сделать код гораздо чище. Если вы опасаетесь, что получится код, устройство которого покажется неочевидным вашим коллегам, просто посоветуйте им почитать эту статью;). =========== Источник: habr.com =========== =========== Автор оригинала: CEZARY PIĄTEK ===========Похожие новости:
Блог компании Издательский дом «Питер» )[/url], #_c#, #_oop ( ООП ), #_programmirovanie ( Программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:58
Часовой пояс: UTC + 5