Usage
Each component must be marked as belonging to one or more zones before it will be added to the Component Model. Zones are declared with zone definition types, and zone markers are used to associate components with zone definitions.
Zone definitions
A zone is declared by a type implementing the empty IZone
marker interface, and decorated with the [ZoneDefinition]
attribute. The type can be a class or an interface, with no significance attached to the choice, although it has implications on inheritance. The simplest definition declares a zone with no dependencies:
Zone definitions are hierarchical, and can specify dependencies on other zones that are required in order for that zone to work. For example, the IPsiLanguageZone
requires the PsiFeaturesImplZone
which in turn requires the ITextControlsZone
, IProjectModelZone
and IDocumentModelZone
. Any component that requires the IPsiLanguageZone
implicitly also requires all of its dependencies. All zone definitions in this dependency hierarchy must be active for the component to be added to the Component Model.
The above example declares a zone with no dependencies - all components that belong to this zone can be instantiated without requiring any other zone to be active.
Dependencies can be defined either by inheritance or by implementing the IRequire<TZone>
interface. The two methods are essentially the same, except inheritance also implies that depending zones are automatically activated. This is discussed in more detail in the Activation section. Generally speaking, unless there is an "is-a" relationship between zones (e.g. ILanguageCSharpZone
is-a IPsiLanguageZone
) dependencies should be implemented with the IRequire<TZone>
marker interface. The two approaches can be mixed, as appropriate.
If a zone definition is expected to be inherited, it makes sense to implement it as an interface. In this way, the inheriting zone definition has the ability to inherit from multiple zones. If the base zone definition is a class, the inheriting zone definition can only inherit a single zone.
This example states that IMyZone
has a dependency on NavigationZone
(and transitively, on anything that NavigationZone
depends on, such as the PSI, results lists and options dialogs).
A zone definition has to be activated before it can be used. Typically, the owning product will be responsible for activating all of the zones in the product. For example, the ReSharperZonesActivator
activates all zones used by ReSharper. When creating an extension that just consumes zones, activation is not needed, as the consumed zones will already be activated (or not, in which case the extension should not, and will not be loaded). However, if the extension creates zone definitions, it must activate them. This can be done either with explicitly with an activator class, or via the ZoneFlags.AutoEnable
parameter to the ZoneDefinitionAttribute
constructor. More details, including the differences between the approaches, can be found in the Activation guide.
Zone markers
Zone markers are used to declare that a component belongs to one or more zones. This means that the component can only be instantiated if all of those zones (and transitively, all dependent zones) are active in the product.
A zone marker is a class that has been decorated with the [ZoneMarker]
attribute, and must be named ZoneMarker
, or have a name that ends _ZoneMarker
. All dependencies are listed either by implementing the IRequire<TZone>
interface, or by passing the zone definitions to the ZoneMarkerAttribute
constructor. There is no difference in the approach.
This example declares that the Foo.Bar
namespace belongs to the IMyZone
zone. All components in the Foo.Bar
namespace and below (e.g. both Foo.Bar.ThisComponent
and Foo.Bar.Baz.Quux.ThatComponent
) require the IMyZone
zone, and all of its dependencies to be active, or they do not get added to the Component Model.
Multiple dependencies can be applied to the zone marker, meaning the components in the zone require multiple zones for their implementation. For example, a JavaScript unit test runner will require both the IUnitTestingZone
and the ILanguageJavaScriptZone
.
Creating a zone marker with multiple dependencies can be considered an "anonymous" or "implicit" zone, made up of the listed dependencies, rather than explicitly defined with a zone definition class. Typically, this should only be used when the components that make up the zone are only to be used for end user facing features. There is no need for an explicit zone definition, because nobody needs to depend on these components and therefore include the zone in an IRequire<TZone>
.
However, if the components are expected to be used by another zone (that is, the feature is an internal, code facing feature) then a zone definition should be created that encapsulates the dependencies. The zone marker should then only require this single, new zone definition, and no others. This gives depending code a zone definition it can require. Failure to do this means error prone repetition of zone dependencies in the dependent zones.
Put another way, consider the dependencies of a zone to be an implementation detail. Do not require depending code to know your dependencies in order to consume your components - provide a zone definition.
Class zone marker
The ZoneMarker
class and [ZoneMarker]
attribute apply to all types in a namespace. It is possible to apply a single class to a zone, by applying the [ZoneMarker]
attribute directly to a component class. Usage is exactly the same, either specify dependencies as parameters to the [ZoneMarker]
constructor, or implement IRequire<TZone>
directly on the component for each dependency.
Generally speaking, most registration will use ZoneMarker
classes to apply a zone to all components in a namespace.
Empty zone marker
A namespace can be marked with an empty zone marker, meaning it is zone aware, but does not require any other zone.
All components in the Foo.Bar
namespace and below are now zone aware, but do not require any other zones to be active. The component model filter will always allow these components through the zone filter. They are assumed to be infrastructure components, that is, essential to the correct running of the product, and always required.
Example
When creating the component containers, the Component Model applies the zone filter to every part, and checks that each part belongs to one or more active zone. It will walk the namespace segments of the type name of the part, and check for zones at each level. If there are any zones attached to that namespace, then all zones must be active, or the part is not included in the container. The filter will walk every segment in the namespace, and all zones in all namespace segments must be active, or the part is not included. If there are no zones specified for the part, it is not included.
Given the code above, when evaluating the MyComponent
part type, the filter will:
Look for any zones at the
Foo
namespace. None are defined, so continue.Look for any zones at the
Foo.Bar
namespace. TheFoo.Bar.ZoneMarker
class has applied theIMyZone
zone to this namespace.The
Foo.Bar.ZoneMarker
class has applied theIMyZone
zone to this namespace.The transitive dependency
IDependentZone
is automatically also applied to this namespace.If
IMyZone
is not active, the part is immediately rejected, andMyComponent
is not included in the container.Perform the same check for
IDependentZone
. It must be active, or the component is rejected.
Look for any zones in the
Foo.Bar.Baz
namespace. None are defined, so continue.There are no more namespace segments. Because zones were encountered (and since the component hasn't already been rejected), the part is included in the container. If no zones were encountered, the part is rejected.