- C # 7 - Основний напрямок поліпшень
- C # 7.0 - Робота з даними
- Кортежі / Tuples
- Покращена продуктивність в C # 7.0
- локальні функції
- Локальні змінні за посиланням
- Продуктивність для асинхронних методів
- Спрощення в написанні коду в C # 7.0
Наша взаимовыгодная связь https://banwar.org/
Зовсім недавно була випущена нова версія мови програмування C #. У цій статті я розповім про основні нововведення в новій версії C # 7.
C # 7 - Основний напрямок поліпшень
У всіх попередніх випусках C # (можливо за винятком C # 6), все поліпшення були зосереджені навколо деякої основної теми:
C # 2.0 - вводяться Generic типи.
C # 3.0 - додається LINQ за допомогою впровадження методів розширень (extension methods), додаються лямбда виразу, анонімні типи і інші поліпшення, необхідні для зручної роботи з LINQ.
C # 4.0 - все поліпшення зосереджені на поліпшенні сумісності (interoperability) за допомогою динамічних не строго типізованих змінних (dynamic),
C # 5.0 - спрощується асинхронне програмування за допомогою додавання ключових слів async / await
C # 6.0 - повністю переписується компілятор, додаються кілька поліпшень і нових фіч.
І нова версія мови C # 7.0 не виняток. Розробники компілятора зосередилися на трьох основних напрямках:
Робота з даними - популярність веб-служб призводить до зміни способу моделювання даних. Замість того, щоб розробляти моделі даних як частина програми, їх визначення стає частиною контрактів веб-сервісів. Хоча це дуже зручно в функціональних мовах, це може принести додаткову складність в об'єктно-орієнтовану розробку додатків. Кілька нових функцій C # 7.0 націлені на спрощення роботи з контрактами зовнішніх даних.
Покращена продуктивність - зростання частки додатків для мобільних пристроїв робить свій вплив, і тепер компілятор повинен генерувати високопродуктивний код, який може швидко і надійно виконуватися на мобільних платформах. В C # 7.0 представлені функції, які дозволяють оптимізувати продуктивність, які раніше були недоступні для платформи .NET.
Спрощення коду - новий компілятор вносить кілька додаткових невеликих змін, заснованих на розробках для C # 6.0, які дозволяють ще більше спростити написання якісного і красивого код.
C # 7.0 не підтримуються в Visual Studio 2015 (і відповідно більш ранніми версіями).
Щоб почати використовувати нові функції, вам необхідно завантажити та встановити Visual Studio 2017.
А тепер давайте розглянемо докладніше всі ці нові функції.
C # 7.0 - Робота з даними
Об'єктно-орієнтовані мови програмування, такі як C #, відмінно підходять для випадків, коли потрібна робота з розширюваним набором типів даних за допомогою визначеного набору операцій. Зазвичай вони моделюються за допомогою інтерфейсу (або базового класу), що визначає доступні операції і потенційно зростаючий набір класів, що представляють типи даних. Реалізуючи інтерфейс, класи містять реалізацію всіх очікуваних операцій, які можуть бути зроблені над об'єктом.
Наприклад, в комп'ютерній грі класами можуть бути представлені різні види зброї (наприклад, меч або цибуля), а операції над об'єктами зброї можуть бути різні дії (такі як атака або ремонт). В цьому випадку додавання нового типу зброї (наприклад, світловий меч) дуже проста операція: просто створіть новий клас, який реалізує інтерфейс зброї. З іншого боку, додавання нового дії (наприклад, чистка зброї) зажадає розширення інтерфейсу і зміни всіх існуючих реалізацій зброї.
interface IEnemy {int Health {get; set; }} Interface IWeapon {int Damage {get; set; } Void Attack (IEnemy enemy); void Repair (); } Class Sword: IWeapon {public int Damage {get; set; } Public int Durability {get; set; } Public void Attack (IEnemy enemy) {if (Durability & gt; 0) {enemy.Health - = Damage; Durability--; }} Public void Repair () {Durability + = 100; }} Class Bow: IWeapon {public int Damage {get; set; } Public int Arrows {get; set; } Public void Attack (IEnemy enemy) {if (Arrows & gt; 0) {enemy.Health - = Damage; Arrows--; }} Public void Repair () {}}
У функціональному програмуванні типи даних не містять операції. Замість цього кожна функція реалізує єдину операцію для всіх типів даних. Це значно спрощує додавання нової операції (просто визначити і впровадити нову функцію), але ускладнює додавання нового типу даних (необхідно модифікувати всі існуючі функції). Програмування в такому стилі вже було можливо в C # 6, хоча код виходить набагато більш громіздким, ніж могло б бути.
interface IEnemy {int Health {get; set; }} Interface IWeapon {int Damage {get; set; }} Class Sword: IWeapon {public int Damage {get; set; } Public int Durability {get; set; }} Class Bow: IWeapon {public int Damage {get; set; } Public int Arrows {get; set; }} Static class WeaponOperations {static void Attack (this IWeapon weapon, IEnemy enemy) {if (weapon is Sword) {var sword = weapon as Sword; if (sword.Durability> 0) {enemy.Health - = sword.Damage; sword.Durability--; }} Else if (weapon is Bow) {var bow = weapon as Bow; if (bow.Arrows> 0) {enemy.Health - = bow.Damage; bow.Arrows--; }}} Static void Repair (this IWeapon weapon) {if (weapon is Sword) {var sword = weapon as Sword; sword.Durability + = 100; }}}
Pattern matching - це функція, яка повинна допомогти спростити наведений вище код. Нижче приклад, як це може виглядати якщо застосувати його до методу Attack ():
static void Attack (this IWeapon weapon, IEnemy enemy) {if (weapon is Sword sword) {if (sword.Durability> 0) {enemy.Health - = sword.Damage; sword.Durability--; }} Else if (weapon is Bow bow) {if (bow.Arrows> 0) {enemy.Health - = bow.Damage; bow.Arrows--; }}}
Замість того, щоб перевіряти тип зброї і призначати його правильної типизированной змінної в двох окремих операціях, тепер оператор is також дозволяє нам оголосити нову змінну і привласнити їй значення типу.
З аналогічним результатом можна використовувати оператор case switch замість if. Це може зробити код ще більш зрозумілим, особливо коли є багато різних гілок:
switch (weapon) {case Sword sword when sword.Durability> 0: enemy.Health - = sword.Damage; sword.Durability--; break; case Bow bow when bow.Arrows> 0: enemy.Health - = bow.Damage; bow.Arrows--; break; }
Зверніть увагу, як оператор case виконує як привласнення типу, так і додаткову умовну перевірку, роблячи код набагато більш компактним і зрозумілим.
Кортежі / Tuples
Існує ще одна нова фіча в C # 7.0, яка повинна сприяти створенню більш компактного коду: кортежі (tuples). Це нова, більш легка альтернатива анонімним класах. Їх основне використання, ймовірно, буде в функціях, які повертають кілька значень, в якості альтернативи аргументів з модифікатором out. приклад:
public static (int weight, int count) Stocktake (IEnumerable <IWeapon> weapons) {var w = 0; var c = 0; foreach (var weapon in weapons) {w + = weapon.Weight; c ++; } Return (w, c); }
ВАЖЛИВО: Щоб використовувати кортежі, вам необхідно встановити NuGet пакет System.ValueTuple, який додає необхідний тип System.ValueType <> узагальненого типу.
Установка NuGet пакет для підтримки System.ValueTuple
Метод вище повертає два окремих значення без використання аргументів з модифікатором out і без необхідності оголошувати новий спеціальний тип або використовувати анонімну змінну. Синтаксис виклику методу залишається тим самим:
static void Main (string [] args) {List <IWeapon> inventory = new List <IWeapon> (); Sword sword = new Sword (); sword.Damage = 100; sword.Durability = 200; sword.Weight = 50; inventory.Add (sword); var inventoryInfo = Tuples.Stocktake ((IEnumerable <IWeapon>) inventory); Console.WriteLine ($ "Weapon count: {inventoryInfo.count}"); Console.WriteLine ($ "Total weight: {inventoryInfo.weight}"); }
Значення, що повертається схоже на узагальнений клас Tuple <> з .NET Framework. Однак є дві важливі відмінності:
1. Його члени можуть мати значущі імена, певні в викликається метод, і не обмежуються описовими Item1, Item2 і т. Д. Це робить код краще читаним і зрозумілішим.
2. Це змінна типу значення, а не змінна посилального типу. Тобто немає витрат на виділення пам'яті.
А ще, в якості альтернативи, при виклику методу можна використовувати нову функцію мови деконструкцію (deconstruction) для повернення значень в локальні змінні:
(Var inventoryWeight, var inventoryCount) = Tuples.Stocktake ((IEnumerable) inventory); Console.WriteLine ($ "Weapon count: {inventoryCount}"); Console.WriteLine ($ "Total weight: {inventoryWeight}");
В цьому випадку ви визначаєте імена для окремих членів, що повертається. Цей синтаксис дозволить ігнорувати повернені значення, які вас не цікавлять, використовуючи відкидання (зверніть увагу на використання нижнього підкреслення _ замість оголошень змінних в наведеному нижче коді):
(Var inventoryWeight2, _) = Tuples.Stocktake ((IEnumerable) inventory); Console.WriteLine ($ "Total weight: {inventoryWeight2}");
Деконструкція (deconstruction) може застосовуватися не тільки при роботі з кортежами. Будь-який тип може використовувати деконструкцію, якщо є оголошений метод деконструкції для цього типу. приклад:
public static void Deconstruct (this Sword sword, out int damage, out int durability) {damage = sword.Damage; durability = sword.Durability; }
Такий метод розширення робить наступний код цілком дійсним:
(Var damage, var durability) = sword; Console.WriteLine ($ "Sword damage rating: {damage}"); Console.WriteLine ($ "Sword durability: {durability}");
Головна умова - тип повинен містити метод з ім'ям Deconstruct, або повинен бути оголошений метод розширення з ім'ям Deconstruct.
Покращена продуктивність в C # 7.0
Покращення продуктивності в C # 7.0 зосереджені на скороченні копіювання даних між осередками пам'яті.
локальні функції
Локальні функції (Local functions) дозволяють оголошувати допоміжні функції, вкладені в інші функції. Це не тільки зменшує їх обсяг, але також дозволяє використовувати змінні, оголошені в їх охоплює області, без виділення додаткової пам'яті в купі або стеку:
static void ReduceMagicalEffects (this IWeapon weapon, int timePassed) {double CalculateDecayRate () => 0.1; double decayRate = CalculateDecayRate (); double GetRemainingEffect (double currentEffect) => currentEffect * Math.Pow (decayRate, timePassed); weapon.FireEffect = GetRemainingEffect (weapon.FireEffect); weapon.IceEffect = GetRemainingEffect (weapon.IceEffect); weapon.LightningEffect = GetRemainingEffect (weapon.LightningEffect); }
Локальні змінні за посиланням
Значення, що повертаються і локальні змінні за посиланням також можуть використовуватися для запобігання непотрібного копіювання даних. Але в той же час вони змінюють поведінку. Оскільки змінна вказує на вихідну комірку пам'яті, будь-які зміни в цьому значенні, звичайно ж, також впливають на значення локальної змінної:
public void LocalVariableByReference () {var terrain = Terrain.Get (); ref TerrainType terrainType = ref terrain.GetAt (4, 2); Assert.AreEqual (TerrainType.Grass, terrainType); // Змінити значення enum в вихідному місцезнаходження terrain.BurnAt (4, 2); // локальна змінна також була порушена і змінила своє значення Assert.AreEqual (TerrainType.Dirt, terrainType); }
У наведеному вище прикладі terrainType є локальної змінної по посиланню, а GetAt - функцією, що повертає значення за посиланням:
public ref TerrainType GetAt (int x, int y) = & gt; ref terrain [x, y];
Продуктивність для асинхронних методів
Крім того, в C # 7.0 була покращена продуктивність і для асинхронних методів.
В даний час все асинхронні методи повинні повертати Task, який є посилальним типом. Тепер доступний тип значення: ValueTask.
Якщо асинхронний метод вже має результат, доступний при виклику, це може привести до значних поліпшень продуктивності через зменшення кількості розподілів в купі.
Крім того, мова тепер дозволяє використовувати інші призначені для користувача типи повертаються даних для асинхронних методів, що може мати сенс для бібліотек з особливими вимогами.
Спрощення в написанні коду в C # 7.0
Тепер трохи поговоримо про синтаксичному цукрі.
В C # 6.0 з'явилася підтримка методів - виразів і властивостей тільки для читання. Тепер якості читання / запису, конструктори і фіналізатор також підтримують ці фічі:
class Grunt: IEnemy {// Використовуємо запис тіла конструктора у вигляді виразу public Grunt (int health) => _health = health> = 0? health: throw new ArgumentOutOfRangeException (); // Теж саме для фіналізатор - тіло методу записано у вигляді виразу ~ Grunt () => Debug.WriteLine ( "Finalizer called"); // Тіло для методів властивості також можна записувати у вигляді виразу private int _health; public int Health {get => _health = 0; set => _health = value> = 0? value: 0; }}
В цьому класі використовується ще одна нова цікава функція: throw expression. Конструктор видає виключення для від'ємних значень здоров'я. Раніше це було можливо тільки з утвердження (statement), тепер вираження також підтримують його.
Щоб передати змінну в вихідний аргумент функції до C # 7.0, вам потрібно було оголосити її заздалегідь:
ISpell spell; if (learnedSpells.TryGetValue (spellName, out spell)) {spell.Cast (enemy); }
Тепер ви можете оголосити змінну out безпосередньо в списку аргументів функції:
if (learnedSpells.TryGetValue (spellName, out var spell)) {spell.Cast (enemy); }
І останнє поліпшення, про який слід згадати, - це поліпшення числових літералів:
const int MAX_PLAYER_LEVEL = 0b0010_1010;
В C # 7.0 додана підтримка бінарних литералов. Щоб зробити їх більш чіткими, можна використовувати як роздільник цифр можна використовувати нижнє підкреслювання. А ще підкреслення можна використовувати в десяткових і шістнадцяткових літералах.
Ось такі нововведення принесла нам нова сьома версія мови C #.