Як перенести моє мислення з C ++ на C #


12

Я досвідчений розробник C ++, знаю мову докладно і інтенсивно використовую деякі її особливості. Також я знаю принципи OOD та схеми дизайну. Зараз я вивчаю C #, але не можу зупинити відчуття, що не зможу позбутися розуму C ++. Я так сильно прив’язав себе до сильних сторін C ++, що не можу жити без деяких особливостей. І я не можу знайти жодного хорошого обходу або заміни для них у C #.

Які гарні практики , схеми дизайну , ідіоми , які відрізняються в C # від точки зору C ++, ви можете запропонувати? Як отримати ідеальний дизайн C ++, не виглядаючи німим на C #?

Зокрема, я не можу знайти хороший спосіб вирішення проблеми C # (останні приклади):

  • Контроль за часом експлуатації ресурсів, які потребують детермінованого очищення (наприклад, файлів). Це легко мати usingпід рукою, але як правильно ним користуватися, коли право власності на ресурс передається [... між потоками]? У C ++ я просто використовував би загальні покажчики і дозволяв би дбати про «збирання сміття» саме в потрібний час.
  • Постійна боротьба з переважаючими функціями для конкретних дженериків (я люблю такі речі, як часткова спеціалізація шаблонів на C ++). Чи варто просто відмовитися від будь-яких спроб зробити будь-яке загальне програмування на C #? Може бути, дженерики обмежені за призначенням, і використовувати їх не можна за винятком конкретної сфери проблем?
  • Макроподобна функціональність. Хоча в цілому це погана ідея, для деяких областей проблем немає іншого вирішення (наприклад, умовна оцінка твердження, як, наприклад, з журналами, які повинні переходити лише до версій налагодження). Якщо їх немає, це означає, що мені потрібно поставити більше if (condition) {...}котлових плит, і це все ще не рівне за рівнем викликання побічних ефектів.

№1 для мене важкий, я хотів би дізнатися відповідь сам. # 2 - ви можете навести простий приклад коду C ++ та пояснити, для чого це потрібно? Для №3 - використовуйте C#для створення іншого коду. Ви можете прочитати a CSVчи an XMLабо те, що ви записали як вхід, та створити файл C#чи SQLфайл. Це може бути більш потужним, ніж використання функціональних макросів.
Робота

Що стосується макро-функціональності, які конкретні речі ви намагаєтесь зробити? Я знайшов, що зазвичай є мовна конструкція C # для обробки того, що я зробив би з макросом у C ++. Також перегляньте директиви препроцесора C #: msdn.microsoft.com/en-us/library/4y6tbswk.aspx
ravibhagw

Багато речей, зроблених з макросами в C ++, можна зробити з відображенням у C #.
Себастьян Редл

Це один із моїх вихованців, коли хтось каже: "Правильний інструмент для правильної роботи - вибирайте мову, виходячи з того, що вам потрібно". Для великих програм загальномовними мовами це марне твердження. Однак це означає, що C ++ краще, ніж майже будь-яка інша мова, - це детерміноване управління ресурсами. Чи детерміноване управління ресурсами є основною ознакою вашої програми, або щось, до чого ви звикли? Якщо це не важлива функція користувача, ви можете бути надто зосереджені на цьому.
Бен

Відповіді:


16

На мій досвід, не існує книги, яка б це зробила. Форуми допомагають ідіомам та найкращим практикам, але врешті-решт вам доведеться вкласти час. Практикуйте, розв’язуючи ряд різних проблем у C #. Подивіться на те, що було важко / незручно, і спробуйте зробити їх менш жорсткими та менш незручними наступного разу. Для мене цей процес зайняв близько місяця, перш ніж я його «отримав».

Найбільше, на що слід звернути увагу, - це відсутність управління пам’яттю. У C ++ це лорди над кожним прийнятим вами кожним дизайнерським рішенням, але в C # це протилежно. У C # ви часто хочете ігнорувати загибель або інші переходи стану об'єктів. Така асоціація додає непотрібної зв'язку.

Переважно, зосередьтеся на тому, щоб використовувати нові інструменти найефективніше, а не побиватися над прив’язаністю до старих.

Як отримати ідеальний дизайн C ++, не виглядаючи німим на C #?

Усвідомлюючи, що різні ідіоми підштовхують до різних дизайнерських підходів. Ви не можете просто перенести щось 1: 1 між (більшістю) мовами і очікувати чогось прекрасного та ідіоматичного з іншого боку.

Але у відповідь на конкретні сценарії:

Спільні некеровані ресурси - Це була погана ідея в C ++ і настільки ж погана ідея в C #. Якщо у вас є файл, тоді відкрийте його, використовуйте його і закрийте. Поділитися між потоками - це просто запит на проблему, і якщо лише одна нитка справді використовує її, то відкрийте / власніть / закрийте її. Якщо у вас з певних причин справді є довгий файл, тоді створіть клас для його представлення і дозвольте програмі керувати цим у міру необхідності (закрийте перед виходом, прив’яжіть близько до Події закриття програми, подібних подій тощо)

Часткова спеціалізація шаблонів - Вам тут не пощастило, хоча чим більше я використовував C #, тим більше я знаходжу використання шаблонів у C ++, щоб потрапити в YAGNI. Навіщо робити щось загальне, коли T завжди є цілим? Інші сценарії найкраще вирішувати в C # ліберальним застосуванням інтерфейсів. Вам не потрібні дженерики, коли тип інтерфейсу або делегата може сильно вводити те, що ви хочете змінити.

Умови попереднього переробки - C # має #if, маємо гайки. Що ще краще, він має умовні атрибути, які просто скинуть цю функцію з коду, якщо символ компілятора не визначений.


Що стосується управління ресурсами, в C # досить часто існують класи менеджерів (які можуть бути статичними або динамічними).
rwong

Щодо спільних ресурсів: керовані в C # легко (якщо умови перегонів не мають значення), некеровані - набагато неприємніші.
Дедуплікатор

@rwong - поширене, але класи менеджерів все ще є анти-шаблоном, якого слід уникати.
Теластин

Що стосується управління пам'яттю, я виявив, що перехід на C # ускладнює це, особливо на початку. Я думаю, ви повинні бути більш обізнаними, ніж у C ++. В C ++ ви точно знаєте, коли і де виділяється, розміщується та посилається кожен об'єкт. У C # це все приховано від вас. Багато разів в C # ви навіть не усвідомлюєте, що посилання на об'єкт отримано і пам'ять починає просочуватися. Удачі відслідковуйте витоки пам’яті в C #, які, як правило, банальні для з'ясування в C ++. Звичайно, з досвідом ви дізнаєтесь ці нюанси C #, тому вони, як правило, менше стають проблемою.
Данк

4

Наскільки я можу бачити, єдине, що дозволяє детерміновано управління життям IDisposable, це руйнування (як у: відсутність підтримки мови чи типу), коли, як ви помітили, вам потрібно передати право власності на такий ресурс.

Перебуваючи в тому ж човні, що і ви - розробник C ++, який робить C # atm. - відсутність підтримки моделювання передачі власності (файлів, db-з'єднань, ...) у типах / підписах C # мене дуже дратувало, але я мушу визнати, що це працює цілком нормально, щоб "просто" розраховувати про документи convention та API, щоб отримати це право.

  • Використовуйте, IDisposableщоб зробити детерміновану очищення iff необхідно.
  • Коли вам потрібно передати право власності на IDisposable, просто переконайтеся, що задіяний API чіткий для цього, і не використовуйте usingв цьому випадку, оскільки usingйого не можна "скасувати", так би мовити.

Так, це не так елегантно, як те, що ви можете виразити в системі типів за допомогою сучасної C ++ (переміщення сематики тощо), але це виконує роботу, і (що дивно мені здається) спільнота C # в цілому не здається турбота надмірно, AFAICT.


2

Ви згадали дві конкретні функції C ++. Я обговорюю RAII:

Зазвичай це не застосовується, оскільки C # збирається сміттям. Найближча конструкція C # - це, мабуть, IDisposableінтерфейс, який використовується наступним чином:

using (FileStream fs = File.OpenRead(@"c:\path\filename.txt"))
{
   //Do stuff with the file.
} //File will be closed here.

Не слід розраховувати на те, що IDisposableчасто реалізовуватимуться (і це трохи вдосконалено), оскільки це не потрібно для керованих ресурсів. Однак вам слід використовувати usingконструкцію (або зателефонувати Disposeсобі, якщо usingце неможливо) для будь-якої реалізації IDisposable. Навіть якщо ви зіпсуєте це, добре розроблені класи C # спробують впоратися із цим (використовуючи фіналізатори). Однак у мові, зібраній зі сміттям, модель RAII не працює повністю (оскільки збирання не детерміновано), тож очищення ресурсів буде затримано (іноді катастрофічно).


РАЙ - це справді моя найбільша проблема. Скажіть, у мене є ресурс (скажімо файл), який створюється одним потоком і передається іншому. Як я можу переконатися, що він розміщений у певний час, коли ця нитка вже не потрібна? Звичайно, я міг би зателефонувати у розпорядження, але тоді це виглядає набагато гірше, ніж загальні покажчики в C ++. У ній нічого RAII.
Андрій

@Andrew Те, що ви описуєте, начебто передбачає передачу права власності, тому тепер другий потік відповідає за Disposing()файл і, ймовірно, повинен використовувати using.
svick

@Andrew - Загалом, не варто цього робити. Натомість слід розповісти нитці, як отримати файл і дозволити йому мати право власності на все життя.
Теластин

1
@Telastyn Чи означає це, що віддача права власності на керований ресурс є більшою проблемою в C #, ніж у C ++? Досить дивно.
Андрій

6
@Andrew: Дозвольте підсумувати: в C ++ ви отримуєте рівномірне, напівавтоматичне управління всіма ресурсами. У C # ви отримуєте повністю автоматичне управління пам'яттю - і повністю ручне управління всім іншим. Немає реальної зміни, тому єдине ваше справжнє запитання - це не "як я це виправити?", А лише "як я звикаю до цього?"
Jerry Coffin

2

Це дуже гарне запитання, оскільки я бачив, як ряд розробників C ++ писали жахливий код C #.

Краще не думати про C # як "C ++ з приємнішим синтаксисом". Це різні мови, які вимагають різних підходів у вашому мисленні. C ++ змушує вас постійно думати про те, що будуть робити процесор і пам'ять. C # не такий. C # спеціально розроблений так, що ви не замислюєтесь про процесор і пам'ять, а натомість думаєте про бізнес-домен, для якого ви пишете .

Одним із прикладів цього, що я бачив, є те, що багато розробників C ++ хотіли б використовувати для циклів, оскільки вони швидші, ніж foreach. Зазвичай це погана ідея в C #, оскільки вона обмежує можливі типи ітерації колекції (а отже, повторну використання та гнучкість коду).

Я думаю, що найкращий спосіб перестроїти з C ++ на C # - це спробувати підходити до кодування з іншої точки зору. Спочатку це буде важко, тому що з роками ви будете повністю звикати використовувати мозку "що це для процесора та пам'яті" у вашому мозку для фільтрування написаного вами коду. Але в C # вам слід замість цього думати про взаємозв'язки між об'єктами в бізнесі. "Що я хочу зробити" на відміну від "що робить комп'ютер".

Якщо ви хочете зробити щось у всьому списку об’єктів, замість того, щоб писати цикл для списку, створіть метод, який займає цикл IEnumerable<MyObject>та використовує foreach.

Ваші конкретні приклади:

Контроль за часом експлуатації ресурсів, які потребують детермінованого очищення (наприклад, файлів). Це легко мати під рукою, але як правильно ним користуватися, коли право власності на ресурс передається [... між потоками]? У C ++ я просто використовував би загальні покажчики і дозволяв би дбати про «збирання сміття» саме в потрібний час.

Ніколи не слід робити цього в C # (особливо між потоками). Якщо вам потрібно щось зробити у файл, зробіть це в одному місці за один раз. Це нормально (і справді хороша практика) писати клас обгортки, який керує некерованим ресурсом, який передається між вашими класами, але не намагайтеся передавати Файл між потоками і мати окремі класи записувати / читати / закривати / відкривати його. Не діліться правом власності на некеровані ресурси. Використовуйте Disposeсхему, щоб розібратися з очищенням.

Постійна боротьба з переважаючими функціями для конкретних дженериків (я люблю такі речі, як часткова спеціалізація шаблонів на C ++). Чи варто просто відмовитися від будь-яких спроб зробити будь-яке загальне програмування на C #? Може бути, дженерики обмежені за призначенням, і використовувати їх не можна за винятком конкретної сфери проблем?

Родові ресурси в C # розроблені так, щоб вони були загальними. Спеціалізація дженерики повинна оброблятися похідними класами. Чому слід List<T>поводитись інакше, якщо це List<int>чи є List<string>? Усі операції на них List<T>є загальними, щоб застосовуватись до будь-яких List<T> . Якщо ви хочете змінити поведінку .Addметоду на a List<string>, тоді створіть похідний клас MySpecializedStringCollection : List<string>або складений клас, MySpecializedStringCollection : IList<string>який використовує загальне внутрішнє, але робить все по-іншому. Це допоможе вам не порушувати Принцип заміни Ліскова та по-царському гнати інших, хто користується вашим класом.

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

Як уже говорили інші, для цього можна використовувати команди препроцесора. Атрибути ще кращі. Взагалі, атрибути - це найкращий спосіб поводження з речами, які не є "основним" функціоналом для класу.

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


3
WRT. Передача ресурсів: "Ви ніколи цього не робіть", це не скорочує ІМХО. Часто дуже хороший дизайн передбачає перенесення IDisposable( не сировинного ресурсу) з одного класу в інший: Просто перегляньте StreamWriter API, де це має ідеальний сенс для Dispose()пройденого потоку. І там лежить дірка в мові C # - ви не можете цього виразити в системі типу.
Мартін Ба

2
"Один із прикладів цього, який я бачив, - це те, що багато розробників C ++ хотіли б використовувати для циклів, оскільки вони швидші, ніж передбачувані." Це також погана ідея в C ++. Абстракція з нульовою вартістю - це велика потужність C ++, і foreach або інші алгоритми не повинні бути повільнішими, ніж рукописний текст для циклу в більшості випадків.
Себастьян Редл

1

Справа в тому, що було самим різним для мене була тенденція до нового все . Якщо ви хочете об'єкт, забудьте спробувати передати його у розпорядження та поділитися базовими даними, здається, що шаблон C # - це просто створити собі новий об’єкт. Загалом це, здається, набагато більше, ніж на C #. Спочатку ця модель здавалася дуже неефективною, але, мабуть, створення більшості об’єктів у C # - це швидка операція.

Однак є і статичні класи, які мене справді дратують, як і кожен раз, коли ви намагаєтесь створити один лише для того, щоб знайти, що вам доведеться просто ним користуватися. Я вважаю, що його не так просто зрозуміти, чим користуватися.

Я дуже радий програмі C # просто ігнорувати її, коли мені це більше не потрібно, але просто пам’ятайте, що містить цей об’єкт - зазвичай вам потрібно думати, чи є у нього якісь «незвичайні» дані, об’єкти, які складаються лише з пам’яті, просто відійдіть самі, інших доведеться утилізувати або вам доведеться подивитися, як їх знищити - вручну або за допомогою блоку, якщо ні.

ви дуже швидко підберете його, хоча C # навряд чи відрізняється від VB, тому ви можете трактувати його як той самий інструмент RAD, який він є (якщо це допомагає думати про C # як VB.NET, тоді це зробити, синтаксис лише незначний різні, і мої старі часи використання VB привели мене до правильного мислення для розвитку RAD). Єдиний складний біт - визначити, який біт з величезних бібліотек .net ви повинні використовувати. Google, безумовно, ваш друг тут!


1
Передача об'єкта в рутину для обміну базовими даними цілком добре в C #, принаймні з точки зору синтаксису.
Брайан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.