C# — є щось зайве?

Все буде швидко. Це виступ Анатолія Левенчука, останнім часом не дає мені спокою. Успіхи глибинного навчання в останній рік говорять про те, що все дуже швидко зміниться. Занадто довго кричали вовки-вовки говорили «штучний інтелект», а його все не було. І ось, коли він, нарешті, приходить до нас, багато людей цього просто не усвідомлюють, думаючи, що все закінчиться черговою перемогою комп'ютера в черговий інтелектуальної гри. Дуже багато людей, якщо не все людство опиниться за бортом прогресу. І цей процес вже запущений. Думаю, що в цей момент мене не дуже буде цікавити питання, яке винесено в заголовок статті. Але, поки цей момент ще не настав, спробую підняти цей потенційно спірне питання.

Програмуючи вже більше 25 років, застав досить багато різних концепцій, що зміг спробувати, ще більше не встиг. Зараз з цікавістю спостерігаю за мовою Go, який можна віднести до продовжувачам «лінійки мов Вірта» — Algol-Pascal-Modula-Oberon. Одним з чудових властивостей цього ланцюжка є те, що кожен наступний мова стає простіше попереднього, але не менш потужним і виразним.

Думаю, що всім зрозуміло, чим гарний проста мова. Але все ж наведу ці критерії, оскільки вони будуть спливати не пізніше:

  • Простий мову швидше вивчається, значить простіше отримати необхідних розробників.
  • Підтримка програми на більш простому мовою зазвичай простіше (так, це інтуїтивне відчуття, яке потрібно довести, але я прийму його зараз за аксіому).
  • У більш простої мови простіше розвивати навколишню інфраструктуру — переносити на різні платформи, створювати різні утиліти, генератори, парсери і т. п.


Чому ж тоді існують складні мови? Вся справа у виразності. Якщо якась конструкція дозволяє коротко описати необхідну дію, то це цілком може окупити негативні сторони ускладнення мови.

За відносно недовгий час свого існування, мова C# ввібрав в себе значну кількість різноманітних концепцій, що відобразилися в його конструкціях. Швидкість їх додавання іноді лякає. Мені, оскільки я з C# майже з самого початку — простіше. Але як новачкам, які тільки приступають до вивчення? Іноді заздрю Java-програмістів, де нововведення впроваджуються в мову набагато більш консервативно.

Те, що додано в мову — адже реально вже не вирубаєш і сокирою. Звичайно, якщо взяти мову, широко поширений у вузьких колах, можна дозволити собі несумісність між версіями. Деякі «витівки» зворотного несумісності може собі дозволити таку мову, як Python (при переході з 2-ї на 3-ю версію). Але не C#, за яким стоїть Microsoft. Мені здається, що якби розробники розуміли, що з кожною новою фичей мова стає не тільки зручніше (у певних випадках), але і трохи ближче до своєї смерті від «ожиріння», то коментарі були б трохи менше захопленими, ніж це має місце в першій гілці відгуків на нововведення C# 7.

Те, що описано далі — всього лише мої спекуляції на тему того, чи це дійсно корисна штука. Звичайно, це може бути справою смаку і не всі погодяться зі мною (дивіться спойлери). І в будь-якому випадку, це залишиться в C# вже навічно… Ну, до моменту сингулярності, принаймні.

Список доданих фіч мови за версіями можна знайти тут: C# Features added in versions. Не буду чіпати версію 2.0, почну з 3.0.

Ліричні спогадиПрактично у будь-якого явища, факту, фічі мови, є і позитивні і негативні сторони. Те, які оцінки ми цьому надаємо, у багато залежить від наших суб'єктивних особливостей і інша людина може те ж саме оцінити протилежним способом. Мало того, що це природно, швидше за все, ці оцінки в обох випадках будуть правильними. Просто для різних людей. У спойлерах далі спробую показати приклади таких відмінностей.


C# 3.0
Implicitly typed local variables
var i = 5;
var s = "Привіт";
var d = 1.0;
var numbers = new int[] {1, 2, 3};
var orders = new Dictionary<int,Order>();

Горезвісне var. Про введення якого зараз сперечаються в світі Java («Var і val в Java?», Ключове слово var» в Java: будь ласка, тільки не це»

Код пишеться один раз, читають багато (банальна істина). Автоматичний висновок типу у багатьох випадках змушує робити додаткові дії для того, щоб зрозуміти, якого типу змінна. А значить, це погано. Так, це звично, наприклад, для JavaScript-програмістів, але там зовсім інша парадигма типізації.

Роздратування від явного і повного прописування типів викликають такі ось шматки коду:

List<Pair<String, Double>> scores = seeker.getScores(documentAsCentroid);
...
foreach(Pair<String, Double> score in scores)


І це (Pair<String, Double>) далеко не самий довге визначення типу, яке доводиться повторювати. А будь-які повтори – це дійсно погано (крім того, що просто незграбно). Але є спосіб значно краще і виразніше. Ось чого мені після Паскаля не вистачало в Java, а потім в C#, так це конструкції типу Type (typedef C). В C# під цю справу намагався пристосувати using, який дозволяє на початку файлу написати щось типу:

using StopWordsTables = System.Collections.Generic.List<System.Collections.Generic.Словник<string, string>>;


Ця конструкція дозволяла замість тієї громіздкою писанини, що стоїть праворуч, використовувати ідентифікатор StopWordsTables. На жаль, він дійсний тільки в межах одного вихідного файлу…

От якби ввели typedef, це б вирішило проблему громіздких типів без шкоди для читання коду.

Заперечення з приводу того, що можна було б домовитися використовувати var тільки там, де тип легко вивести очима (тобто, він явно видно в инициализаторе) не знайдуть у мене підтримки з однієї простої причини. Аналогічне правило вже ввела в своєму Code Agreement Майкрософт (про свою компанію я вже мовчу). Тільки ось практично ніхто цього не дотримується. Var перемогло. Люди ледачі.

Є ще момент — var дуже обмежений. Його можна використовувати тільки в локальних ідентифікаторах. У властивостях, полях, методи, доводиться раз за разом писати ці дратівливо довгі ідентифікатори колекцій, а в разі зміни типів повторювати редагування у всіх місцях. З Type/typedef цього все пішло у минуле.

У розвиток теми – якщо вже ввели var, чому б не довести ідею до логічного завершення, як це зроблено в Go? У инициализаторе замість "=" писати ":=", що означає, що тип виводиться автоматично. І тоді взагалі не потрібно ніякого слова писати на місці типу. Ще коротше… до Речі, type в Go теж є, що дуже зручно.

Мій висновок — var C# був помилкою. Потрібен був лише typedef.

Далі в прикладах я буду використовувати var лише тому, що він дозволяє скоротити розмір прикладу, а в парі-трійці рядків не встигають проявитися його негативні моменти.

Object and collection initializers
var r = new Rectangle {
P1 = new Point { X = 0, Y = 1 },
P2 = new Point { X = 2, Y = 3 }
};
List<int> digits = new List < int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

Штука корисна, скорочує код не на шкоду читання. Схвалюю.

Auto-Implemented properties
Тепер, замість
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; } }
}

Стало можливо писати так:
public Class Point {
public int X { get; set; }
public int Y { get; set; }
}

Щодо властивостей в глибині душі так і не зрозумів, а чи потрібно їх було вводити? Он, у тій же Java і без них цілком нормально жити, використовуючи певні угоди імен в методах. Але якщо вже ввели, то таке спрощення їх опису цілком зручно (без погіршення читабельності) у багатьох випадках.

Anonymous types
var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;

Мені дана опція мови так жодного разу і не знадобилася. Хоча ні, 1 раз таки потрібно було, згадав. Я не вводив. Хоча ті приклади, що бачив у підручнику, начебто і логічні. Загалом, можливо і корисна штука, просто не в моїх сценаріях (віддаю перевагу возитися з алгоритмами, а не з базами і JSON, хоча, різне буває).

Extension methods
namespace Acme.Utilities
{
public static class Extensions
{
public static int ToInt32(this string s) {
return Int32.Parse(s);
}
public static T[] Slice<T>(this T[] source, int index, int count) {
if (index < 0 || count < 0 || source.Length – index < count)
throw new ArgumentException();
T[] result = new T[count];
Array.Copy(source, index, result, 0, count);
return result;
}
}
}


using Acme.Utilities;
...
string s = "1234";
int i = s.ToInt32(); // Same as Extensions.ToInt32(s)
int[] digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] a = digits.Slice(4, 3); // Same as Extensions.Slice(digits, 4, 3)

Дуже зручна штука. Часом теоретики ООП її лають, але без неї було б незручно (громіздко) робити багато речей.

Query expressions
Він же LINQ. Цей пункт викликає настільки змішані почуття! Ну, приблизно, як ложка дьогтю в бочці чогось хорошого. Поза всяким сумнівом, LINQ стала однією з по-справжньому класних можливостей мови. Але навіщо потрібно було реалізовувати це двома способами? Я про так званий людино-зрозумілий синтаксис (NPV), який імітував SQL-запити, наскільки я розумію.

string[] people = new [] { "Tom", "Dick", "Harry" };
// NPV або ж синтаксис запитів
var filteredPeople = from p in people where p.Length > 3 select p; 
// функціональний стиль або лямбда синтаксис
var filteredPeople = people.Where (p => p.Length > 3); 


В результаті маємо:

  • NPV не відповідає SQL безпосередньо, так що знання SQL недостатньо для того, щоб написати відповідний запит. Є свої особливості.
  • Одне і те ж (з невеликими і рідкісними винятками, функціональний і NPV-стилі еквівалентні) можна написати двома способами.
  • Програмісту слід вчити обидва варіанти, оскільки вони обидва можуть з'явитися в коді.
  • NPV стиль різко контрастує з іншим кодом, виглядаючи чимось чужорідним.


Схожі почуття в плані чужорідності стилю у мене викликають байндинги WPF. Вони являють собою микроскриптовые конструкції, написані на своїй мові всередині XML. В результаті все виглядає громіздко і негарно. Не знаю, як можна було б зробити гарніше — може створити спеціалізований мова розмітки, а не городити все в XML? Але я відволікся. Загалом, зізнаюся — за останні кілька років не написав ні одного виразу NPV, при цьому абсолютно не маючи в цьому потреби. Тільки трошки редагував чужі.
Загалом, LINQ — дуже і дуже потрібна штука, до якої дуже даремно привісили гирю NPV.

Lambda expressions
x => x + 1 // Implicitly typed, expression body
x => { return x + 1; } // Implicitly typed, statement body
(int x) => x + 1 // Explicitly typed, expression body
(int x) => { return x + 1; } // Explicitly typed, statement body
(x, y) => x * y // Multiple parameters
() => Console.WriteLine() // No parameters

Це було прекрасне придбання, що привнесло в C# елементи функціонального стилю і істотно поліпшеного виразність коротких фрагментів коду, що передаються як аргументи. Ось тільки коли лямбды починають займати з десяток і більше рядків, читати код стає дуже складно. Важливо вчасно зупинитися і в цьому випадку перейти знову на методи.

Expression trees
Навряд чи варто розглядати цю фічу окремо від LINQ та Lambda.

Partial methods
Непоганий спосіб розділити автоматично генерується і ручної код. Я — за.

Веня, він же БенЯкщо не вважати програмованого калькулятора МК-61, то перша зустріч з комп'ютерами в мене була в КПК, на старших класах школи. Більша частина учнів отсиживала заняття, старанно записуючи в зошит те, що читали викладачі, деякий час витрачали на реалізацію алгоритмів начебто бульбашкового сортування або бінарного пошуку на Фокале. Але 4 людини дуже швидко відійшли від основної програми і ми сиділи за побутовими телевізорами (які служили нам моніторами) після занять (а трохи пізніше і під час занять) до упору, реалізуючи свої ідеї, поки викладачі не починали благати «відпустити» їх додому.

Фокал — це був тихий жах, після якого Бейсік здавався зразковою мовою, але іншої мови «високого рівня» наБК 0010 зашито не було. З іншого боку, система команд процесора К1801ВМ1 відрізнялася зручною структурою, що дозволяла відносно легко програмувати прямо в кодах, вводячи команди в вісімковій системі (16 біт). Чому вісімкова? 8 регістрів, 8 способів адресації. Саме тому було зручно використовувати саме такий метод асемблер/дізассемблер для невеликих програм був не потрібен. Трошки незручно було тільки обчислювати зміщення, коли програма попередньо писалася в зошиті.

Потім був університет, МС 1502. Хоч це вже і був IBM-PC сумісний комп'ютер, але спочатку тут не було дисководів, MS DOS, асемблерів. Працювали прямо з інтерпретатора бейсика, який був зашитий у біосі.

І ось тут вже мені стало очевидно, що всі люди різні. Був у цій банді нашій групі Веніамін. Мене він завжди вражав тим, що в його програмах я жодного разу не помічав помилок. Все, що він писав — працювало з першого разу. На МС 1502 система команд (x86) була набагато менш логічна (це моя особиста думка), ніж на БК 0010 (PDP-11). Тому тут я перестав писати невеликі вставки прямо в кодах. Чесно кажучи, взагалі відійшов від асемблера. А ось Веніамін — продовжував компілювати прямо в голові, писати код в операторі DATA, які потім виконувалися прямо з програм на бейсіку. І все продовжувало працювати з першого разу! Він часто навіть по кілька годин не скидав на касету написану програму — адже тут був великий ризик зависання у випадку помилки в коді. У цьому випадку допомагала тільки перезавантаження.

І ось, настав день, коли у Віника (як ми між собою його іноді називали), програма не запрацювала. Він був вражений, здивований, збентежений і ще багато чого (аналогії з анекдотом про вчительку руського тут немає — Веня обходився без особливо живої частини великоросійської мови). Хоча я, наприклад, потай трохи зловтішався — адже не можна ж ніколи не помилятися. Але, я був осоромлений! Після довгих з'ясувань, виявилося, що проблема полягала в помилку при описі роботи однієї з команд процесора — якийсь там прапор виставлявся, написано було, що повинен був скидатися (вже не пам'ятаю точно — 25 років минуло).

Так що якщо хтось скаже, що йому не потрібна типізація, юніт-тести, він завжди пише безпомилкові програми — я повірю. Я бачив такої людини (його сліди загубилися після переїзду в США і початку роботи в Microsoft). Але я і ще багато інших людей — не такі.


C# 4.0
Dynamic binding
Потенційнокорисний приклад замість
var doc = new XmlDocument("/path/to/file.xml");
var baz = doc.GetElement("foo").GetElement("qux");

можна написати
dynamic doc = new XmlDocument("/path/to/file.xml");
var baz = doc.foo.qux;

Незважаючи на те, що виглядає добре, я б не рекомендував таке використання. Тип dynamic — дуже небезпечна штука, оскільки втрачається весь контроль типів. З більш дрібних капостей — перестають працювати підказки в редакторі. Тим не менш, в певних сценаріях, він корисний. Наприклад, з його допомогою я у себе робив підвантаження плагінів (точніше, використання коду з них). За рахунок того, що виклики методів тут кешируются, то виходить продуктивно і не потрібно городити це самостійно. А щодо безпеки — інакше мені б все-одно довелося б працювати через рефлексію, так що в цьому випадку безпека не була би більшою. А ось код був би більш складним і заплутаним. Так що обережне використання динаміків в обмеженій кількості сценаріїв схвалюю. Звичайно, вводилися вони більше з прицілом на скриптові мови. Ну треба, так треба.

Named and optional arguments
class Car {
public void Accelerate(
double speed, int? gear = null, 
bool inReverse = false) { 
/* ... */ 
}
}

Car myCar = new Car();
myCar.Accelerate(55);

Зменшується кількість перевантажених методів — код стає простіше і надійніше (менше можливостей зробити помилку копіпаста, менше роботи при рефакторинге). Схвалюю.

Generic co — and contravariance
Цілком логічне уточнення поведінки мови. Особливої складності в вивчення і синтаксис не вносить і не може бути розглянуто новачками пізніше. Схвалюю.

Embedded interop types («NoPIA»)
Це одна з особливостей, про які мені особливо нічого сказати, виходячи зі своєї практики — просто читав, що таке є. Мені вона не потрібна була, але COM мабуть, ще довго не помре і тим, хто (наприклад) працює з MS Office, він ще довго буде потрібен.

C# 5.0
Asynchronous methods
public async Task ReadFirstBytesAsync(string filePath1, string filePath2)
{
using (FileStream fs1 = new FileStream(filePath1, FileMode.Open))
using (FileStream fs2 = new FileStream(filePath2, FileMode.Open))
{
await fs1.ReadAsync(new byte[1], 0, 1); // 1
await fs2.ReadAsync(new byte[1], 0, 1); // 2
}
}

Дуже зручна конструкція. На жаль, при практичній реалізації виникли деякі обмеження — деталі реалізації протікали у вигляді обмежень (Async/await в C#: підводні камені). Частина їх була знята в наступних версіях (Await in catch/finally blocks) мови, або бібліотеки akka.net спочатку не дозволяла змішувати свою модель асинхронного виконання з розглянутої фичей, але потім це виправили). Може бути мало б сенс розглянути і якісь інші патерни паралельного взаємодії — типу горутин. Але тут вже вибір за архітекторами мови. Загалом, схвалюю.

Caller info attributes
public void DoProcessing()
{
TraceMessage("Something happened.");
}

public void TraceMessage(string message,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
System.Diagnostics.Trace.WriteLine("message: " + message);
System.Diagnostics.Trace.WriteLine("member name: " + memberName);
System.Diagnostics.Trace.WriteLine("source file path: " + sourceFilePath);
System.Diagnostics.Trace.WriteLine("source line number: " + sourceLineNumber);
}

// Sample Output:
// message: Something happened.
// member name: DoProcessing
// source file path: c:\Users\username\Documents\Visual Studio 2012\Projects\CallerInfoCS\CallerInfoCS\Form1.cs
// source line number: 31

Невеликий синтаксичний цукор, який не обтяжує мова, але дозволяє в певних сценаріях зменшити кількість копіпаста і коду. Схвалюю.

C# 6.0
Compiler-as-a-service (Roslyn)
Цей пункт (незважаючи на загальну важливість) пропущу. Я б відніс його швидше до інфраструктури, а не безпосередньо до мови.

Import of static type members into namespace
using static System.Console;
using static System.Math;
using static System.DayOfWeek;
class Program
{
static void Main()
{
WriteLine(Sqrt(3*3 + 4*4)); 
WriteLine(Friday - Monday); 
}
}

Спочатку мені ця фіча здалася корисною. Але спробувавши її на практиці, змушений визнати, що помилився. Читаність коду погіршується — методи та члени статичного класу починають мішатися з методами поточного класу. І навіть введення став повільніше, хоча начебто кількість ідентифікаторів зменшилася на одиницю. Але за рахунок того, що тепер у підказці від Intellisense більше варіантів, натискань потрібно зробити більше. Загалом, ця фіча, з моєї точки зору — помилка.

Exception filters
try { ... }
catch (MyException e) when (myfilter(e))
{
...
}

Ще не спробував. Тому є спокуса назвати фічу марною, але може просто мої сценарії до неї не сильно підходять? Може, хто розкаже, в яких випадках і наскільки часто вона реально хороша?

Await in catch/finally blocks
Не вважаю це самостійної фичей — швидше виправлення попередніх проблем.

Auto property initializers
public class Customer
{
public string First { get; set; } = "Jane";
public string Last { get; set; } = "Doe";
}

Логічне і зручне доповнення до автосвойствам. Код стає чистішим, а значить схвалюю.

Default values for getter-only properties
public class Customer
{
public string First { get; } = "Jane";
public string Last { get; } = "Doe";
}

Аналогічно попередньому пункту.

Expression-bodied members
public string Name => First + " " + Last;
public Customer this[long id] => store.LookupCustomer(id); 

Великий практики застосування поки немає, але виглядає непогано. Треба буде ще пройтися за своїм кодом і подивитися, де можна б застосувати. Головне тут як з лямбдами — не перестаратися і не робити виразів на половину екрану.

Null propagator (succinct null checking)
public static string Truncate(string value, int length)
{ 
return value?.Substring(0, Math.Min(value.Length, length));
}

Давно напрашивавшаяся штука. Схвалюю. Хоча, на практиці виявилося, що застосовується не так часто, як очікувалося до того.

String Interpolation
Про! Ось це те, чого чекав давним-давно, і що миттєво прижилося в моєму коді. Завжди намагався писати ідентифікатори в контексті рядка приблизно так:

"Total lines:" + totalLines + ", total words: "+ totalWords + ".";


Іноді мене запитували, а чи я знаю про форматування рядків? Так, знаю, але там є 2 великі проблеми:

  • Вираз віднесено далеко від місця, де воно вставляється. Це ускладнює читання коду.
  • Рядок з текстовими значеннями форматування, фактично є микроскриптом, який виконується в run-time. Відповідно, вся система типізації, перевірки відповідності параметрів C# летить в тартарари.
Також це призводить до того, що в методах Format(...) допускається велика кількість помилок при рефакторинге.
Тому і використав таке ось трохи громіздке написання. Але, нарешті, дочекався від C# такого подарунка :) Схвалюю однозначно і всіма кінцівками!

nameof operator
if (x == null) throw new ArgumentNullException(nameof(x));
WriteLine(nameof(person.Address.ZipCode)); // prints "ZipCode"

Аналогічно «Caller info attributes». Схвалюю.

Dictionary initializer
var numbers = new Dictionary<int, string> {
[7] = "seven",
[9] = "nine",
[13] = "thirteen"
};

Ще одне нововведення, яке дозволяє спростити роботу з кодом, який нерідко набирається за допомогою копіпаста і схильний до помилок від неуважності. Будь-яка можливість зробити його чистішим і зручніше для читання буде приводити до плюсів у карму архітектора мови. Плюсик.

Анти-БенУ попередньому спойлері я описав ідеального програміста, якого зустрічав в реальності. Сам же я у багатьох випадках є повною протилежністю. Я схильний до опечаткам, у мене дуже мала сверхоперативная пам'ять. Саме тому, запуск нового коду завжди для мене починався з налагодження. Поступово склалися певні прийоми і прийомчики, які дозволяли зменшити кількість помилок. Код дробився до маленьких фрагментів-функцій, які збиралися в великі. Це робилося навіть у тому випадку, якщо звернення до вкладеної функції робилося тільки один раз. До речі, угорська нотація для елементарних типів також зменшила кількість логічних помилок — вони стають видніше візуально. Також, активно використовую саджесты в IDE — вони дозволяють знизити кількість елементарних описок практично до нуля.

І ось вийшла Java. (Це було ще минуле тисячоліття)

Прочитав книжку, надихнувся ідеєю реалізації простого http-сервера (просто віддавав статичні файли) і за образом і подобою приклад з книги написав свій код з невеликими варіаціями. Код саме писав, а не копіював фрагменти. Відносно довго виловлював всякі неточності, пов'язані з тим, що мова новий, просто свої звичайні помилки. Нарешті, код откомпилировался. Я його запустив, приготувавшись до етапу вилову таких багів. Але програма запрацювала. І заробила так, як я хотів.

Сказати, що я був здивований — нічого не сказати. Але саме після цього випадку я зрозумів, що для мене строгість мови (та ще автоматична збірка сміття — це був мій перший мову з цієї фичей) обертається можливістю продуктивно працювати над серйозними проектами. Я вірю, що багатьом не подобається статична типізація, необхідність явно описувати типи і ще багато чого, що відносять до суворим мов. І я вірю, що їм це дійсно заважає. Але їхня аудиторія — це Бен, але не я.


C# 7.0 proposals
Даними функціями я ще не користувався — зазвичай сиджу на релизной версії шарпа, іноді доводиться спускатися нижче. Тому тут наведу чисто умоглядні аргументи. Список наведу по статті "Нововведення C# 7", а не за даними вікіпедії.

Binary literals
int x = 0b1010000; 
int SeventyFive = 0B10_01011;

Нововведення виглядає невинно, не сильно ускладнюючи мову, а для тих, кому потрібно працювати з бітами — зручно. Трохи не зрозумів фразу «Можна відокремлювати нулі довільною кількістю підкреслень» — чому тільки нулі?

Local functions
public void Foo(int z)
{
void Init()
{
Boo = z;
}
Init();
}

Коли тільки перейшов на C# з Об'єктного Паскаля (Delphi), мені дуже не вистачало локальних функцій, як способу структурувати свій код. Просте винесення шматків коду в приватні методи призводило до появи класів з великою кількістю методів на одному рівні. Так відбувалося, поки я не зрозумів, що в C# для цього потрібно застосовувати інший метод — об'єктну декомпозицію. Після цього я часто почав виносити щодо громіздкий код у внутрішній клас зі своїми методами. По досягненні певного рівня складності, цей клас міг бути розділений на декілька пов'язаних класів, які виносилися в окрему папку і нэймспейс. Це дозволило внести ту ієрархію код, яку в стародавні часи забезпечували локальні функції і процедури Паскаля.

Таким чином, моя думка зараз змінилося — не варто давати ще одного способу структурування коду. Це ускладнення мови, ускладнення читання (зовнішній метод стає великим, тому складно охопити його поглядом від початку і до кінця), але немає принципових переваг.

Tuples
Поки не зрозумів необхідності цієї фічі, в яких ситуаціях вона буде корисніше, ніж повернути клас/структуру або ж використовувати out-аргументи. Тому для мене це скоріше негативний внесок у мову.

Pattern matching, conditions in switch
// pattern type
public void Foo(object item)
{
if (item is string s)
{
WriteLine(s.Length);
}
}
// Var Pattern
public void Foo(object item)
{
if(item is var x)
{
WriteLine(item == x); // prints true
}
}
// Wildcard Pattern
public void Foo(object item)
{
if(item is *)
{
WriteLine("Hi there"); //will be executed
}
}

Більш повний приклад можна переглянути за посиланням.
Виглядає непогано, але треба подивитися, наскільки це виявиться корисним на практиці. Є інтуїтивне підозра, що ускладнення мови не окупиться тим кількістю кейсів, де ця можливість буде корисна. Так що поки я насторожений.

Ref locals and ref returns
static void Main()
{
var arr = new[] { 1, 2, 3, 4 };
ref int Get(int[] array, int index)=> ref array[index]; 
ref int item = ref Get(arr, 1);
WriteLine(item);
item = 10;
WriteLine(arr[1]);
ReadLine();
}
// Will print
// 2
// 10

Просте і інтуїтивно зрозуміле розширення для роботи з посиланнями. Але реальна потреба в ньому поки що незрозуміла.

Описаних далі в статті на Хабре пунктів «Записи» і «Створення незмінних об'єктів» не бачу зараз в поточних пропозиції на 7-ю версію C#, тому оцінювати їх не буду.

Отже, що в підсумку?
З моєї точки зору, C# нажив (7-ю версію поки не чіпаю, оплакувати буду за фактом) собі такі зайві фічі:

  1. Людино-зрозумілий синтаксис LINQ. Достатньо було б зупинитися на fluent-стилі.
  2. Анонімні типи.
  3. Var. Ця обмежена локальними змінними фіча не дала впровадити нормального визначення типів, в той же час істотно погіршивши читабельність коду.
  4. Імпорт статиків — погіршує читаність коду.


Що було особливо корисно:

  1. LINQ (без NPV).
  2. Лямбды.
  3. Поступове спрощення ініціалізації і опису об'єктів, структур даних, властивостей.
  4. Іменовані і за замовчуванням параметри.
  5. Async/await.
  6. Інтерполяція рядків.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.