В чём разница между частичным методом и частичным классом?
Так же, как «fixed» и «into», «partial» используется в С# двумя похожими-но-разными способами.
Задача частичного класса в том, чтобы позволить вам разбивать объявление класса на несколько частей, обычно расположенных в различных файлах. Мотиватором этой возможности был машинно-генерируемый код, который пользователю нужно было расширять путём прямых добавлений. Когда вы рисуете форму в дизайнере форм, дизайнер генерирует для вас класс, представляющий эту форму. Вы можете затем далее изменять этот класс, добавляя в него новый код. Если вы можете редактировать код, сгенерированный машиной, то возникает масса проблем. Что, если он будет сгенерирован повторно? Что, если машина использует сам код для хранения информации о дизайне формы, и ваши правки сбивают с толку парсер машины? Гораздо лучше просто поместить машинно-генерируемую половину в отдельный файл, сгенерировать комментарий «не трогайте это», и разместить пользовательский код в другом месте.
Есть и другие применения частичных классов, не связанные с автоматически генерируемым кодом, но они относительно редки. Вот некоторые случаи, где я вижу применение частичных классов:
- Если класс действительно большой, и реализует пачку интерфейсов, то, иногда, реализация каждого интерфейса в отдельном файле имеет смысл. Хотя, чаще это признак плохого кода, присущий классу, который пытается делать слишком много всего; разделение этой штуки на несколько классов может оказаться лучше.
- Иногда приятно выносить вложенные классы в отдельные файлы; единственный приличный способ этого добиться – это сделать содержащий их класс частичным.
- И так далее
Частичные методы – немножко другая история. Как и частичные классы, частичные методы – про склеивание множества объявлений одного и того же метода для улучшения сценариев, связанных с машинной генерацией кода. Но, несмотря на то, что высокоуровневые задачи этой возможности те же, детали весьма отличаются.
Способ, которым работают частичные методы – это наличие двух объявлений, «латентного», и «актуального». У латентного объявления нет тела, как будто это абстрактный метод. У актуального – есть. Латентное объявление располагается на машинно-сгенерированной стороне частичного класса, актуальное объявление попадает в часть, написанную человеком. Если у нас есть актуальное объявление, то латентное полностью игнорируется. Но если актуального объявления нет, то все вызовы метода устраняются, как будто он был скомпилирован с условным атрибутом! И, фактически, латентное объявление также устраняется в момент порождения метаданных для сгенерированного класса; как будто его и не было.
Причина для такого поведения – в том, что мы хотели сделать возможным такой сценарий:
// Машинно-генерируемый код:
partial class MyFoo
{
void ButtonClickEventHandler(/*всякое*/)
{
// вызвать пользовательский код чтобы посмотреть, не хочет ли он чего-нибудь сделать
OnBeforeButtonClick(всякое);
тра ля ля
// опять вызвать пользовательский код
OnAfterButtonClick(всякое);
}
partial void OnBeforeButtonClick(/*всякое*/);
partial void OnAfterButtonClick(/*всякое*/);
...
Пользователь будет модифицировать частичный класс; вставляя частичные методы, машинно-сгенерированная часть может повсеместно создать простые точки расширения, которые пользователь потом сможет реализовать. Но подумайте о негативных последствиях этого в мире без частичных методов. Пользователя заставляют реализовывать пустые методы. Если он этого не сделает, то получит ошибку. Потенциально там нужно реализовать сотни этих пустых методов, а это бесит. И каждый из этих методов генерирует нетривиальное количество метаданных, делая результирующую библиотеку больше, чем нужно. Дисковое пространство дёшево, но латентность сети заменила дисковое пространство в качестве фактора против больших сборок. Было бы здорово, если бы мы могли избавиться от этого груза метаданных.
Частичные методы удовлетворяют требованиям. Пользователь может предоставлять реализации для методов по собственному выбору, и это становится моделью с «платой за фактическое использование»; вы получаете столько расходов на реализацию, сколько методов вы реализуете.
Эта возможность во время процесса проектирования называлась «латентные и актуальные методы»; мы серьёзно рассматривали добавление новых ключевых слов «latent» и «actual». Но, поскольку эта возможность имеет смысл только в сценариях с частичными классами, то мы решили повторно использовать существующее контекстное ключевое слово «partial» и переименовали возможность. Надеюсь, что созвучие между двумя применениями «partial» помогает больше, чем тонкая разница вредит.
СУПЕР ЭКСТРА БОНУС: Ещё немного частичности
Мы рассматривали добавление третьего типа «частичности» в C# 4.0; эта возможность прошла через фазу проектирования, но была выброшена до реализации. (Если будет высокий спрос, то мы рассмотрим добавление её в гипотетических будущих версиях C#.)
Иногда вы участвуете в сценарии машинной генерации кода типа такого:
// сгенерировано машиной
partial class C
{
int blah;
...
И затем на той стороне, которую генерирует пользователь, вы хотите сделать что-то вроде:
// User generated
partial class C : ISerializable
{
И, о, чёрт, мне нужно пометить blah атрибутом NotSerialized, но я не могу редактировать текст blah потому, что когда его перегенерируют, это всё пропадёт.
Идея этого нового типа частичности – в том, чтобы повторять объявление члена – поля, метода, вложенного типа, чего угодно – с атрибутами метаданных. Это как латентные/актуальные методы, только наоборот; «актуальная» часть – на машинно-генерируемой стороне, а «латентная» часть на стороне, генерируемой пользователем, нужна только для добавления метаданных:
[NotSerialized] partial int blah; // на самом деле не объявляет поле
Мне нравится такая возможность, но во время процесса проектирования я сопротивлялся, как мог, использованию ключевого слова «partial» в третьем значении, слегка отличающемся от первых двух. Добавление такой путаницы один раз казалось оправданным, но дважды? Это перевешивает. Так что мы сошлись на добавлении другого контекстного ключевого слова:
[NotSerialized] existing int blah; // на самом деле не объявляет поле
Оформление объявления при помощи «existing» значило бы «это не настоящее объявление, это упоминание существующего объявления; пожалуйста, проверьте, что такое объявление существует где-то еще в этом частичном классе, и добавьте эти метаданные к тому члену».
Как я сказал, эта полезная возможность была выброшена из-за ограниченности ресурсов. Если у вас есть реально обалденные сценарии, которые бы она облегчила, то я был бы счастлив о них услышать; очевидно, я не могу делать никаких обещаний о будущих возможностях необъявленных, полностью гипотетических продуктов. Но реальные пользовательские сценарии являются сильным мотивирующим фактором в выделении бюджета для новых возможностей.