Я опытный разработчик C ++, знаю язык очень подробно и интенсивно использую некоторые его особенности. Также я знаю принципы OOD и шаблоны проектирования. Сейчас я изучаю C #, но не могу избавиться от ощущения, что не могу избавиться от мышления C ++. Я так сильно привязался к сильным сторонам C ++, что не могу жить без некоторых функций. И я не могу найти какие-либо хорошие обходные пути или замены для них в C #.
Какие хорошие практики , шаблоны проектирования , идиомы , которые отличаются от C # с точки зрения C ++, вы можете предложить? Как получить идеальный дизайн C ++, не выглядящий немым в C #?
В частности, я не могу найти хороший C # -ish способ иметь дело (последние примеры):
- Управление временем жизни ресурсов, которые требуют детерминированной очистки (например, файлов). Это легко иметь под
using
рукой, но как правильно его использовать, когда право собственности на ресурс передается [... между потоками]? В C ++ я просто использовал бы общие указатели и позволял бы позаботиться о «сборке мусора» как раз в нужное время. - Постоянно борется с переопределением функций для конкретных обобщений (мне нравятся такие вещи, как частичная специализация шаблонов в C ++). Должен ли я просто отказаться от любых попыток сделать какое-либо общее программирование на C #? Может быть, дженерики ограничены по назначению, и это не C # -ish, чтобы использовать их, за исключением конкретной области проблем?
- Макро-подобная функциональность. Хотя в целом это плохая идея, для некоторых областей проблем другого обходного пути нет (например, условная оценка оператора, например, для журналов, которые должны идти только в версии Debug). Отсутствие их означает, что мне нужно поставить больше
if (condition) {...}
шаблонов, и это все еще не равно с точки зрения запуска побочных эффектов.
C#
для генерации другого кода. Вы можете прочитатьCSV
или,XML
или что вы подали в качестве ввода и сгенерироватьC#
илиSQL
файл. Это может быть более мощным, чем использование функциональных макросов.Ответы:
По моему опыту, нет книги, чтобы сделать это. Форумы помогают с идиомами и лучшими практиками, но, в конце концов, вам придется потратить время. Практикуйтесь, решая ряд различных проблем в C #. Посмотрите на то, что было трудно / неловко, и постарайтесь сделать их менее трудными и менее неловкими в следующий раз. Для меня этот процесс занял около месяца, прежде чем я «получил его».
Самая важная вещь, на которой нужно сосредоточиться - это отсутствие управления памятью. В C ++ это влияет на каждое ваше дизайнерское решение, но в C # это контрпродуктивно. В C # вы часто хотите игнорировать смерть или другие изменения состояния ваших объектов. Такая ассоциация добавляет ненужную связь.
В основном сосредоточьтесь на наиболее эффективном использовании новых инструментов, а не на избиении привязанностью к старым.
Понимая, что разные идиомы подталкивают к разным подходам к дизайну. Вы не можете просто портировать что-то 1: 1 между (большинством) языков и ожидать чего-то красивого и идиоматического на другом конце.
Но в ответ на конкретные сценарии:
Совместное использование неуправляемых ресурсов - это была плохая идея в C ++, и такая же плохая идея в C #. Если у вас есть файл, откройте его, используйте и закройте. Разделение его между потоками просто вызывает проблемы, и если только один поток действительно использует его, то пусть этот поток открывает / закрывает / закрывает. Если у вас действительно по какой-то причине действительно долгоживущий файл, то создайте класс для его представления и дайте приложению возможность управлять этим по мере необходимости (закрытие перед выходом, привязка близко к событиям, связанным с закрытием приложения, и т. Д.)
Частичная специализация шаблонов - вам здесь не повезло, хотя чем больше я использовал C #, тем больше я нахожу использование шаблонов в C ++, чтобы попасть в YAGNI. Зачем делать что-то общее, когда T всегда int? Другие сценарии лучше всего решаются в C # путем либерального применения интерфейсов. Вам не нужны универсальные шаблоны, когда интерфейс или тип делегата могут строго указывать то, что вы хотите изменить.
Условный препроцессор - C # имеет
#if
, офигеть. Более того, он имеет условные атрибуты , которые просто удаляют эту функцию из кода, если символ компилятора не определен.источник
Насколько я вижу, единственное, что допускает детерминированное управление временем жизни, - это то
IDisposable
, что выходит из строя (как в случае: без поддержки языка или системы типов), когда, как вы заметили, вам необходимо передать право собственности на такой ресурс.Находясь в той же лодке, что и вы - разработчик C ++, занимающийся C # atm. - отсутствие поддержки модели Ownership Transfer (файлы, db-соединения, ...) в типах / сигнатурах C # меня очень раздражало, но я должен признать, что это работает вполне нормально, чтобы «просто» полагаться на соглашение и API документы, чтобы получить это право.
IDisposable
для выполнения детерминированной очистки, если это необходимо.IDisposable
, просто убедитесь, что соответствующий API понятен, и не используйтеusing
в этом случае, потому чтоusing
нельзя, так сказать, «отменить».Да, это не так элегантно, как то, что вы можете выразить в системе типов с помощью современного C ++ (переместить семантику и т. Д.), Но оно выполняет свою работу, и (удивительно для меня) сообщество C # в целом, похоже, не Уход за собой, AFAICT.
источник
Вы упомянули две специфические особенности C ++. Я буду обсуждать RAII:
Обычно это не применимо, поскольку C # является сборщиком мусора. Наиболее близкой конструкцией C #, вероятно, является
IDisposable
интерфейс, используемый следующим образом:Не следует ожидать частой реализации
IDisposable
(и это немного продвинуто), так как это не требуется для управляемых ресурсов. Тем не менее, вы должны использоватьusing
конструкцию (или вызыватьDispose
себя, еслиusing
это невозможно) для чего-либо реализующегоIDisposable
. Даже если вы ошибетесь, хорошо спроектированные классы C # попытаются справиться с этим за вас (используя финализаторы). Однако в языке с мусором шаблон RAII работает не полностью (поскольку сбор недетерминирован), поэтому очистка вашего ресурса будет отложена (иногда катастрофически).источник
Disposing()
файл отвечает за файл и, вероятно, должен его использоватьusing
.Это очень хороший вопрос, так как я видел, как многие разработчики C ++ пишут ужасный код на C #.
Лучше не думать о C # как о «C ++ с более приятным синтаксисом». Это разные языки, которые требуют разных подходов в вашем мышлении. C ++ заставляет вас постоянно думать о том, что будет делать процессор и память. C # не так. C # специально разработан таким образом, чтобы вы не думали о процессоре и памяти, а вместо этого думали о бизнес-сфере, для которой вы пишете .
Один из примеров этого, который я видел, состоит в том, что многие разработчики на C ++ хотели бы использовать циклы, потому что они быстрее, чем foreach. Обычно это плохая идея в C #, поскольку она ограничивает возможные типы перебираемой коллекции (и, следовательно, возможность повторного использования и гибкость кода).
Я думаю, что лучший способ перенастроить C ++ на C # - это попробовать подойти к кодированию с другой точки зрения. Сначала это будет сложно, потому что с годами вы будете полностью привыкнуть использовать поток «что делает процессор и память» в вашем мозгу для фильтрации кода, который вы пишете. Но в C # вместо этого вы должны думать об отношениях между объектами в бизнес-сфере. «Что я хочу сделать», а не «что делает компьютер».
Если вы хотите что-то сделать со всем в списке объектов, вместо записи цикла for в списке, создайте метод, который принимает
IEnumerable<MyObject>
и используетforeach
цикл.Ваши конкретные примеры:
Вы никогда не должны делать это в C # (особенно между потоками). Если вам нужно что-то сделать с файлом, делайте это в одном месте одновременно. Это нормально (и действительно хорошая практика) написать класс-оболочку, который управляет неуправляемым ресурсом, который передается между вашими классами, но не пытайтесь передавать файл между потоками и иметь отдельные классы для записи / чтения / закрытия / открытия его. Не делитесь правами собственности на неуправляемые ресурсы. Используйте
Dispose
образец, чтобы иметь дело с очисткой.Обобщения в C # разработаны так, чтобы быть обобщенными. Специализации дженериков должны обрабатываться производными классами. Зачем
List<T>
вести себя по-другому, если этоList<int>
илиList<string>
? Все операцииList<T>
являются общими и применимы к любомуList<T>
. Если вы хотите изменить поведение.Add
метода в aList<string>
, то создайте производный классMySpecializedStringCollection : List<string>
или составной класс,MySpecializedStringCollection : IList<string>
который использует обобщенный тип для внутреннего использования, но работает по-другому. Это поможет вам не нарушать принцип подстановки Лискова и по-королевски подставлять других, кто использует ваш класс.Как уже говорили другие, вы можете использовать команды препроцессора для этого. Атрибуты еще лучше. В общем, атрибуты - лучший способ справиться с вещами, которые не являются «основной» функциональностью для класса.
Короче говоря, при написании C # имейте в виду, что вы должны полностью думать о бизнес-сфере, а не о процессоре и памяти. Вы можете оптимизировать позже , если это необходимо , но ваш код должен отражать отношения между деловыми принципами , которые вы пытаетесь картой.
источник
IDisposable
( не необработанного ресурса) из одного класса в другой: просто посмотрите API StreamWriter, где это имеет смысл дляDispose()
переданного потока. И здесь есть дыра в языке C # - вы не можете выразить это в системе типов.Для меня больше всего отличалась тенденция к новому всему . Если вам нужен объект, забудьте о его передаче в подпрограмму и обменивайтесь базовыми данными, кажется, что шаблон C # предназначен просто для создания нового объекта. Это, в общем-то, похоже на C #. Сначала этот шаблон казался очень неэффективным, но я думаю, что создание большинства объектов в C # - быстрая операция.
Тем не менее, есть также статические классы, которые действительно раздражают меня, так как каждый раз вы пытаетесь создать один только для того, чтобы найти, что вам нужно просто использовать его. Я считаю, что это не так легко узнать, какой использовать.
Я очень рад, что в программе на C # просто игнорирую ее, когда она мне больше не нужна, но просто вспоминаю, что содержит этот объект - обычно вам нужно только подумать, если у него есть какие-то «необычные» данные, объекты, которые состоят только из памяти, будут просто уходите сами, другим придется избавиться, или вам придется посмотреть, как их уничтожить - вручную или используя блок, если нет.
Вы очень быстро поймете это, хотя C # практически не отличается от VB, поэтому вы можете рассматривать его как тот же инструмент RAD, что и он (если он помогает думать о C # как VB.NET, тогда сделайте так, синтаксис немного другие, и мои старые времена использования VB привели меня к правильному мышлению для разработки RAD). Единственным сложным моментом является определение того, какую часть огромных библиотек .net вам следует использовать. Google, безусловно, ваш друг здесь!
источник