Разделяемые классы (C++/CX)
Разделяемый класс — это конструкция, предназначенная для ситуаций, когда вы изменяете часть определения класса, и программное обеспечение для автоматического построения кода (например, конструктор XAML) также изменяет код в этом классе. Использование разделяемых классов позволяет предотвратить изменение вашего кода конструктором. В проекте Visual Studio к сгенерированному файлу автоматически применяется модификатор partial .
Синтаксис
Чтобы определить разделяемый класс, укажите ключевое слово partial непосредственно перед ключевым словом класса, которое иначе было бы обычным определением класса. Результирующее ключевое слово, например partial ref class , является контекстным ключевым словом, содержащим пробелы. Определения разделяемых классов поддерживаются в следующих конструкциях.
В этом примере представлен разделяемый класс ref class :
partial ref class MyClass* . */> ;
Содержимое
Определение разделяемого класса может содержать любые конструкции, которые могли бы содержаться в полном определении класса, если бы ключевое слово partial не было указано. Оно может содержать любые допустимые конструкции (за единственным исключением), такие как базовые классы, данные-члены, функции-члены, перечисления, объявления дружественных функций и атрибуты. Встроенные определения и статические данные-члены также допускаются.
Единственное исключение — ключевые слова, указывающие возможность доступа к классу. Например, выражение public partial class MyInvalidClass* . */> ; является ошибкой. Ключевые слова в определении разделяемого класса MyInvalidClass, указывающие возможность доступа, не влияют на возможность доступа по умолчанию в последующем разделяемом или полном определении класса MyInvalidClass.
В следующем фрагменте кода демонстрируются ключевые слова, указывающие возможность доступа. В первом разделяемом классе Method1 является открытым, поскольку для него задано ключевое слово public. Во втором разделяемом классе Method2 является закрытым, поскольку по умолчанию класс является закрытым.
partial ref class N < public: int Method1(); // Method1 is public. >; ref class N < void Method2(); // Method2 is private. >;
Объявление
Частичное определение класса, например MyClass только объявление MyClass. То есть он вводит только имя MyClass . MyClass нельзя использовать таким образом, чтобы требуется определение класса, например знание размера MyClass или использование базового или члена MyClass . MyClass считается определенным только в том случае, если компилятор обнаруживает не частичное определение MyClass .
Следующий пример демонстрирует принципы объявления разделяемого класса. После объявления #1 MyClass можно использовать как если бы оно было записано в качестве объявления пересылки. ref class MyClass; Объявление #2 эквивалентно объявлению #1. Объявление #3 допустимо, так как это объявление пересылки в класс. Однако объявление №4 недопустимо, потому что
MyClass не полностью определен.
Объявление #5 не использует partial ключевое слово, а объявление полностью определяет MyClass . Поэтому объявление #6 допустимо.
// Declaration #1 partial ref class MyClass <>; // Declaration #2 partial ref class MyClass; // Declaration #3 MyClass^ pMc; // OK, forward declaration. // Declaration #4 MyClass mc; // Error, MyClass is not defined. // Declaration #5 ref class MyClass < >; // Declaration #6 MyClass mc; // OK, now MyClass is defined.
Количество и порядок указания
Для каждого полного определения класса может существовать одно определение разделяемого класса, несколько таких определений или ни одного.
Каждое разделяемое определение класса должно лексически предшествовать его единственному полному определению, но не обязательно — его опережающим объявлениям. Если у класса отсутствует полное определение, объявления разделяемого класса могут быть только опережающими объявлениями.
Все ключевые слова объявления класса, такие как class и struct , должны совпадать. Например, ошибкой будет создать код partial class X <>; struct X <>; .
В следующем примере демонстрируется количество и порядок указания объявлений. Последнее объявление разделяемого класса вызывает сбой, поскольку класс уже определен.
ref class MyClass; // OK partial ref class MyClass<>; //OK partial ref class MyClass<>; // OK partial ref class MyClass<>; // OK ref class MyClass<>; // OK partial ref class MyClass<>; // C3971, partial definition cannot appear after full definition.
Полное определение
Когда компилятор встречает полное определение класса X, он действует, как если бы именно в нем были объявлены все базовые классы, члены и т. п. в том порядке, в котором они определены в разделяемых классах. Таким образом, содержимое разделяемых классов обрабатывается так, как если бы оно находилось в полном определении класса, и поиск имени и другие языковые правила применяются к полному определению класса, как если бы в нем находилось содержимое разделяемых классов.
Следующие два примера кода аналогичны и действуют одинаково. В первом примере используется разделяемый класс, а втором — нет.
ref class Base1 < public: property int m_num; int GetNumBase();>; interface class Base2 < int GetNum(); >; interface class Base3< int GetNum2();>; partial ref class N : public Base1 < public: /*. */ >; partial ref class N : public Base2 < public: virtual int GetNum(); // OK, as long as OtherClass is //declared before the full definition of N void Method2( OtherClass^ oc ); >; ref class OtherClass; ref class N : public Base3 < public: virtual int GetNum2(); >;
ref class OtherClass; ref class N : public Base1, public Base2, public Base3 < public: virtual int GetNum(); virtual int GetNum2(); private: void Method2(OtherClass^ oc); >;
Шаблоны
Разделяемый класс не может быть шаблоном.
Ограничения
Разделяемый класс не может распространяться за пределы одной записи преобразования.
Ключевое слово partial поддерживается только в сочетании с ключевым словом ref class или value class .
Примеры
В следующем примере класс Address определяется в двух файлах кода. Конструктор изменяет файл Address.details.h , а пользователь — файл Address.h . Ключевое слово partial используется только в определении класса в первом файле.
// Address.Details.h partial ref class Address < private: Platform::String^ street_; Platform::String^ city_; Platform::String^ state_; Platform::String^ zip_; Platform::String^ country_; void ValidateAddress(bool normalize = true); >;
// Address.h #include "Address.details.h" ref class Address < public: Address(Platform::String^ street, Platform::String^ city, Platform::String^ state, Platform::String^ zip, Platform::String^ country); property Platform::String^ Street < Platform::String^ get(); >property Platform::String^ City < Platform::String^ get(); >property Platform::String^ State < Platform::String^ get(); >property Platform::String^ Zip < Platform::String^ get(); >property Platform::String^ Country < Platform::String^ get(); >>;
Partial class c что это
Классы могут быть частичными. То есть мы можем иметь несколько файлов с определением одного и того же класса, и при компиляции все эти определения будут скомпилированы в одно.
Например, определим в проекте два файла с кодом. Не столь важно как эти файлы будут называться. Например, PersonBase.cs и PersonAdditional.cs. В одном из этих файлов (без разницы в каком именно) определим следующий класс:
public partial class Person < public void Move() < Console.WriteLine("I am moving"); >>
А в другом файле определим следующий класс:
public partial class Person < public void Eat() < Console.WriteLine("I am eating"); >>
Таким образом, два файла в проекте содержит определение одного и того же класса Person, которые содержат два разных метода. И оба определенных здесь класса являются частичными. Для этого они определяются с ключевым словом partial .

Затем мы можем использовать все методы класса Person:
class Program < static void Main(string[] args) < Person tom = new Person(); tom.Move(); tom.Eat(); Console.ReadKey(); >>
Частичные методы
Частичные классы могут содержать частичные методы. Такие методы также опреляются с ключевым словом partial . Причем определение частичного метода без тела метода находится в одном частичном классе, а реализация этого же метода — в другом частичном классе.
Например, изменим выше определенные классы Person. Первый класс:
public partial class Person < partial void Read(); public void DoSomething() < Read(); >>
public partial class Person < partial void Read() < Console.WriteLine("I am reading a book"); >>
В первом классе определен метод Read() . Причем на момент определения первого класса неизвестно, что представляет собой этот метод, какие действия он будет выполнять. Тем не менее мы знаем список его параметров и можем вызвать в первом классе.
Второй класс уже непосредственно определяет тело метода Read() .
class Program < static void Main(string[] args) < Person tom = new Person(); tom.DoSomething(); >>
Стоит отметить, что по умолчанию к частичным методам применяется ряд ограничений:
- Они не могут иметь модификаторы доступа
- Они имеют тип void
- Они не могут иметь out-параметры
- Они не могут иметь модификаторы virtual, override, sealed, new или extern
Если же они не соответствуют какому-то из этих ограничений, то для них должна быть предоставлена реализация. Как, например, в следующем примере частичные методы применяют модификатор public :
// первая реализация класса и его методов public partial class Person < public partial void Read(); public void DoSomething() < Read(); >> // вторая реализация класса и его методов public partial class Person < public partial void Read() < Console.WriteLine("I am reading a book"); >>
Классы отделенного кода XAML

Язык XAML позволяет конструировать пользовательский интерфейс, но для создания функционирующего приложения необходим способ подключения обработчиков событий. XAML позволяет легко это сделать с помощью атрибута Class, показанного ниже:
Префикс пространства имен «x» помещает атрибут Class в пространство имен XAML, что означает более общую часть языка XAML. Фактически атрибут Class сообщает анализатору XAML, чтобы он сгенерировал новый класс с указанным именем. Этот класс наследуется от класса, именованного элементом XML. Другими словами, этот пример создает новый класс по имени MainWindow, который наследуется от базового класса Window.
Класс MainWindow генерируется автоматически во время компиляции. И здесь начинается самое интересное. Вы можете предоставить часть класса MainWindow, которая будет объединена с автоматически сгенерированной частью этого класса. Указанная вами часть — блестящий контейнер для кода обработки событий.
Эта «магия» возможна благодаря средству C#, известному под названием частичные классы (partial class). Частичные классы позволяют разделить класс на две или более отдельных части во время разработки, которые соединяются вместе в скомпилированной сборке. Частичные классы могут применяться во многих сценариях управления кодом, но более всего удобны, когда код должен объединяться с файлом, сгенерированным визуальным конструктором.
Среда Visual Studio оказывает помощь, автоматически создавая частичный класс, куда можно поместить код обработки событий. Например, при создании приложения по имени WpfApplication1, содержащего окно по имени MainWindow. Visual Studio начнет с создания следующего базового каркаса класса:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfApplication1 < /// /// Логика взаимодействия для MainWindow.xaml /// public partial class MainWindow : Window < public MainWindow() < InitializeComponent(); >> >
Во время компиляции приложения код XAML, определяющий пользовательский интерфейс (такой как MainWindow.xaml), транслируется в объявление типа CLR, объединенного с логикой файла класса отделенного кода (подобного MainWindow.xaml.сs), формируя один общий модуль.
Метод InitializeComponent()
В данный момент класс MainWindow не содержит реальной функциональности. Однако он включает одну важную деталь — конструктор по умолчанию, который вызывает метод InitializeComponent(), когда создается экземпляр класса.
Метод InitializeComponent() играет ключевую роль в приложениях WPF. По этой причине никогда не следует удалять вызов InitializeComponent() из конструктора окна. В случае добавления к классу окна другого конструктора обязательно предусмотрите в нем вызов InitializeComponent().
Метод InitializeComponent() не видим в исходном коде, потому что генерируется автоматически при компиляции приложения. По существу все, что делает InitializeComponent() — это вызов метода LoadComponent() класса System.Windows.Application. Метод LoadComponent() извлекает код BAML (скомпилированный XAML) из сборки и использует его для построения пользовательского интерфейса.
При разборе BAML он создает объекты каждого элемента управления, устанавливает их свойства и присоединяет все обработчики событий.
Именование элементов
Есть еще одна деталь, которая должна приниматься во внимание. В классе отделенного кода часто требуется программно манипулировать элементами управления. Например, необходимо читать либо изменять свойства, а также присоединять или отсоединять обработчики событий на лету. Чтобы обеспечить такую возможность, элемент управления должен включать XAML-атрибут Name. В предыдущем примере элемент Grid не содержит атрибут Name, поэтому манипулировать им в отделенном коде не получится.
Ниже показано, как назначить имя элементу Grid:
Можно внести это изменение в документ XAML вручную или выбрать элемент в визуальном конструкторе Visual Studio и установить свойство Name в окне Properties (Свойства).
В обоих случаях атрибут Name сообщит анализатору XAML о необходимости добавить поле следующего вида к автоматически сгенерированной части класса MainWindow:
private System.Windows.Controls.Grid MyGrid;
Теперь с этим элементом можно взаимодействовать в коде класса MainWindow указывая имя MyGrid:
MessageBox.Show("Размер сетки: " + MyGrid.ActualWidth + "x" + MyGrid.ActualHeight);
Такая техника мало что дает простому примеру, но становится намного важнее, когда требуется читать значения из элементов управления вводом, таких как текстовые поля и списки.
Показанное ранее свойство Name является частью языка XAML и используется для того, чтобы помочь в интеграции класса отделенного кода. Из-за того, что многие классы определяют собственное свойство Name, происходит некоторая путаница. (Примером может служить базовый класс FrameworkElement, от которого наследуются все элементы WPF.) Анализаторы XAML элегантно справляются с этой проблемой. Можно либо установить XAML-свойство Name (используя префикс х:), либо свойство Name, относящееся к действительному элементу (опустив префикс).
В любом случае результат один и тот же — указанное имя используется в файле автоматически сгенерированного кода и применяется для установки свойства Name.
Это значит, что следующая разметка эквивалентна тому, что вы уже видели:
Такой трюк работает только в том случае, если включающий свойство Name класс оснащен атрибутом RuntimeNameProperty. Атрибут RuntimeNameProperty указывает на то, какое свойство должно трактоваться в качестве имени экземпляра этого типа. (Очевидно, обычно таким свойством является Name.) Класс FrameworkElement содержит атрибут RuntimeNameProperty, так что никаких проблем нет.
В традиционном приложении Windows Forms каждый элемент управления имеет имя. В приложении WPF такого требования нет. Однако при создании окна перетаскиванием элементов на поверхность визуального конструктора Visual Studio каждому элементу назначается автоматически сгенерированное имя. Таково соглашение. Если вы не собираетесь взаимодействовать с элементом в коде, то можете удалить атрибут Name из кода разметки.
Управление объявлениями классов и переменных-членов
Многие из этих ключевых полей вы увидите в действии там, где они понадобятся. Давайте в качестве простого примера рассмотрим следующее определение XAML , в котором используются ключевые слова ClassModifier и FieldModifier, а также x:Name и x:Class:
По умолчанию все определения сгенерированных типов C#/XAML в WPF являются внутренними (internal), а члены — общедоступными (public). Однако на основе показанного определения XAML результирующий автоматически сгенерированный файл содержит тип класса internal с public-членом Button:
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")] internal partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector < #line 6 "..\..\..\MainWindow.xaml" [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] internal System.Windows.Controls.Grid MyGrid; #line default #line hidden /// /// Btn1 Name Field /// #line 8 "..\..\..\MainWindow.xaml" [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] public System.Windows.Controls.Button Btn1;
Разделяемый (частичный) класс
Если вы работали с C#, или, возможно, даже с другим языком программирования, то привыкли к тому, что имя класса должно быть уникальным — т.е. не может быть двух классов с одинаковым именем внутри одного пространства имен. Однако, в какой-то момент, Microsoft решила изменить это, с введением частичных классов.
Когда вы определяете класс с помощью ключевого слова partial, вам или кому-либо другому разрешается ввести функциональные возможности класса с помощью другого класса, который также должен быть объявлен как partial. Это полезно в следующих ситуациях:
- Когда у вас есть очень большой класс — вы можете сохранить его в нескольких файлах, чтобы облегчить работу с различными частями классов. Например, вы можете иметь все свойства в одном файле и все методы в другом файле, имея при этом только один класс.
- При работе с конструктором, как в Visual Studio, например с WinForms, где весь автоматически сгенерированный код конструктора может храниться в одном файле, в то время как код хранится в другом файле.
Позвольте мне проиллюстрировать это примером. В моем проекте у меня обычный Programm.cs, представленный в консольном приложении. Кроме того, я добавил два файла: PartialClass1.cs и PartialClass2.цезий. Вот файлы и их содержимое:
using System;
namespace PartialClasses
public partial class PartialClass
public void HelloWorld()
Console.WriteLine("Hello, world!");
>
>
>
using System;
namespace PartialClasses
public partial class PartialClass
public void HelloUniverse()
Console.WriteLine("Hello, universe!");
>
>
>
Обратите внимание, что оба класса определены с помощью ключевого слова partial и имеют одинаковые имена. Также заметьте, что каждый из них определяет метод — HelloWorld () и HelloUniverse (). в нашей Programm.cs теперь мы можем использовать этот класс, как если бы он был определен только в одном месте, как и любой другой класс:
using System;
namespace PartialClasses
class Program
static void Main(string[] args)
PartialClass pc = new PartialClass();
pc.HelloWorld();
pc.HelloUniverse();
>
>
>
Резюме
С помощью разделяемых классов можно разделить классы на несколько файлов, либо потому, что определение класса очень большое, либо когда инструменты, с которыми вы работаете, приносят пользу, как с конструктором Visual Studio для WinForms.
This article has been fully translated into the following languages:
Is your preferred language not on the list? Click here to help us translate this article into your language!