Комментарии 16
Хм. То есть в С# в последнем примере нельзя просто вернуть stream из функции?
А какой смысл возвращать поток, если вы в него пишете что-то?
С потоком все немного иначе — он Disposable, и по-хорошему закрывать его должен тот, кто создал. Если закрыть поток прямо в нашем методе и потом вернуть его — получится ерунда, потому что никто не сможет его прочитать. Если получить его на входе и его же и вернуть — получается избыточность, и вызывающей стороне будет неочевидно, какой поток использовать — исходный или тот, что пришел из метода (хотя это один и тот же поток). Если открывать файловый поток в методе и возвращать его не закрывая, то по сути теряются все преимущества использования абстракции потока — наш код внезапно наделяет ответственностью принимать решение, что делать, если файл существует, теряется возможность дописать в существующий файл(ну или наоборот, переписывания существующего) без доп. ухищрений в виде перегрузок методов или доп. аргументов.
Понимаете, строка — это что-то, что вы уже аллоцировали. Буфер — это то, что аллоцировал вызывающий, а вы туда записали (предпочтительно, без своей аллокации).
Аналогично и в случае с потоком: если вы его возвращаете с содержимым, значит, вы выделили на это содержимое память (за исключением того сложного случая, когда вы создаете специальный поток, который умеет читать из вашего объекта), а потом ваш потребитель создает новый поток, куда он будет писать (например, в файл). Зачем тогда вы выделяли память на своей стороне, если вы могли просто принять поток, куда писать?
На С++ я могу написать поток, который не аллоцирует никакой памяти, а отдаёт данные в методе чтения
На C# это тоже можно сделать, но зачем? Это совершенно избыточная сложность. Передать поток намного проще, и это будет идиоматичный код.
сейчас принято аллоцировать строку и возвращать её, а не писать в принимаемый буфер
А это, кстати, не обязательно так. Есть много интерфейсов (например, JSON-сериализация), которые пишут строку в TextWriter, а не возвращают ее. Ну и да, есть хелперный метод, который создает TextWriter, отдает в сериализатор, потом забирает из него строку.
На С++ я могу написать поток, который не аллоцирует никакой памяти, а отдаёт данные в методе чтения, можно перегрузить присваивание и т.д., т.е. семантика значения.
Проблема в том, что при таком подходе формировать результат придётся асинхронно, даже если сам результат нужен синхронный. То есть понадобится использовать либо конечные автоматы, либо сопрограмму. В обоих случаях возрастает сложность почти на пустом месте. И к тому же резко ограничиваются доступные библиотеки.
Впрочем, если асинхронность действительно нужна — решение и на C# есть, надо лишь использовать не потоки, а пайплайны. Возвращаем PipeReader, себе оставляем PipeWriter и в него пишем.
Если вам это кажется нонсенсом
Неудачная калька с английского. Лучше было бы "если вы не понимаете, о идет речь" или что-то в это роде.
должны не только экспортировать в файл, но и уметь писать в файл.
Что-то тут не так.
public static FileInfo SaveToFile(this IExportService self, Model model, string filePath)
{
using (var output = File.Create(filePath))
{
self.Save(model, output);
return new FileInfo(filePath);
}
}
Лучше так не делать. Если в коде будет вызываться этот метод, замокать его будет практически нереально — да, реализацию IExportService
можно будет подменить, чтобы в файл ничего не писалось, но сам файл на диске будет создаваться, со всеми вытекающими недостатками.
Минусы экстеншнов, как и любой статики — фиг замокаешь при написании тестов, стоит помнить об этом.
Например, для кода с бизнес-логикой extension-методы очень редко используются — их трудно мокать, как уже упомянули выше. Да и в целом такой код иногда нуждается в полиморфизме, то есть переопределении логики для классов-наследников.
Для инфраструктурного кода методы расширения — это очень мощный инструмент, который позволяет писать более декларативный код без необходимости изменения классов, с которыми он работает (а иногда к ним может вообще не быть доступа, например для классов из сторонних библиотек). Поскольку такой код обычно не содержит бизнес-логики, то вопрос с юнит-тестами не стоит так остро.
Впрочем, и для методов расширения можно реализовать механизмы, которые позволят переопределить логику работы в юнит-тестах, если внутри них обращаться к синглтону, который и содержит логику их работы, и может быть подменён при запуске тестов.
Можно пожалуйста не надо, особенно если это ваше перечисление. Лучше написать класс / структуру с нормальными методами без публичного конструктора и несколькими статическими риоднли членами из этого типа. Работает все точно так же как перечисление но без попыток натянуть сову на глобус.
Ну и вообще если вам надо добавить какую либо логику в само перечисление, значит вы делаете что-то не так.
Творческое использование методов расширения в C#