Creating class extensions


Summary
A class extension allows custom behavior to be applied to object classes and feature classes in a geodatabase. Typically, a class extension is used for tasks, such as implementing validation rules that are not possible out-of-the-box, custom rendering in a map, and listening for class events. Class extensions are the most important type of geodatabase customization.
This topic explains what class extensions are typically used for and how to perform each step in creating a class extension, from its initial creation in Visual Studio through implementation, component category registration, and application to a new or existing object class. It also explains how to implement each class extension interface (required and optional), create custom description classes, and effectively manage and use extension properties.


Common uses of class extensions

Use class extensions to customize geodatabase behavior that applies to a single object class or feature class. The following includes examples of functionality that can be implemented with class extensions:
  • Handling events triggered by object creation, deletion, or modification—The IObjectClassEvents interface can be implemented to obtain this functionality.
  • Validation rules too specific or complex to be applied out-of-the-box—The IObjectClassValidation interface allows extensions to customize the validation process.
  • Handling events from related datasets—The following interfaces can be used, depending on the requirements of the class extension: IRelatedObjectClassEvents, IRelatedObjectClassEvents2, and IConfirmSendRelatedObjectEvents.
  • Defining custom split policies for relationship classes—The IFeatureClassEdit interface can be used and despite its name, it can be used with feature or object class extensions.
  • Specifying custom renderers for feature classes—The IFeatureClassDraw interface can be implemented to associate a custom renderer with a feature class, with the option of making it the exclusive renderer for the class.
  • Storage of data associated with the class, such as text, numbers, or objects—This data can be stored in member variables and used by public and private extension members. These key-value pairs are referred to as class extension properties.
  • Methods and properties defined by a custom interface (or no interface) can be implemented by a class extension—This is a useful place to define properties to access and modify extension properties or to implement methods for extension specific behavior.
 

Creating a class extension

When creating a class extension, the following attributes and methods are required (or recommended) for the extension to function properly:
  • Three attributes from the System.Runtime.InteropServices namespace: globally unique identifier (GUID), ClassInterface, and ProgID
  • Component Object Model (COM) register and unregister methods, which call ArcGIS component category registration and unregistration methods
 
The following code example shows the attributes:
 
[C#]
[Guid("a78421ba-0d38-4104-be58-b049b16f637c")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("SampleClassExt.SampleClassExtension")]
[ComVisible(true)]
public class SampleClassExtension : IClassExtension, IObjectClassExtension
{
   // Class members here…
}
[VB.NET]
<Guid("a78421ba-0d38-4104-be58-b049b16f637c"), _
ClassInterface(ClassInterfaceType.None), _
ProgId("ClassExtensionArticle.SampleClassExtension"), _
ComVisible(True)> _
Public Class SampleClassExtension
 Implements IClassExtension, IObjectClassExtension

 ' Class members here…

End Class
The GUID's attribute parameter should be randomly generated by Visual Studio or another application. The ProgID's attribute parameter should be the fully qualified name of the class. Copying and pasting the values in the preceding code example without modification is not recommended.
The following code example shows the COM register and unregister methods:
 
[C#]
[ComRegisterFunction()]
[ComVisible(false)]
static void RegisterFunction(Type registerType)
{
   string regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
   GeoObjectClassExtensions.Register(regKey);
}

[ComUnregisterFunction()]
[ComVisible(false)]
static void UnregisterFunction(Type registerType)
{
   string regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
   GeoObjectClassExtensions.Unregister(regKey);
}
[VB.NET]
<ComRegisterFunction(), ComVisible(False)> _
Shared Sub RegisterFunction(ByVal registerType As Type)
  Dim regKey As String = String.Format("HKEY_CLASSES_ROOT\CLSID\{{{0}}}", registerType.GUID)
  GeoObjectClassExtensions.Register(regKey)
End Sub

<ComUnregisterFunction(), ComVisible(False)> _
Shared Sub UnregisterFunction(ByVal registerType As Type)
  Dim regKey As String = String.Format("HKEY_CLASSES_ROOT\CLSID\{{{0}}}", registerType.GUID)
  GeoObjectClassExtensions.Unregister(regKey)
End Sub
Although it is possible to manually add these methods, ArcGIS and Visual Studio provide the functionality for a class extension to be generated automatically. See the following steps:
  1. On the Solution Explorer, right-click the project that contains the class extension, click Add, then click New Item. The Add New Item dialog box appears. See the following screen shot:


  2. On the Add New Item dialog box, click ArcGIS under the Categories pane and click ArcGIS Class under the Templates pane. Type an appropriate name for the class extension in the Name text box and click Add. The ArcGIS Add Class Wizard dialog box appears. See the following screen shot:


  3. On the ArcGIS Add Class Wizard, under Customization Group, click the first drop-down arrow and choose All, then choose Geodatabase from the second drop-down arrow. Click Class Extension under Base Component and click Next. The ESRI GeoObject Class Extensions component category appears. See the following screen shot:


  4. Select the ESRI GeoObject Class Extensions check box and click Next. A  list of available optional interfaces appear, which is only a subset of the optional interfaces. Despite being termed optional, always choose IObjectClassExtension from this list and if the class extension applies to feature classes, also choose IFeatureClassExtension. Choose any other interfaces to implement and click Finish. See the following screen shot:


Create a class extension in a class library that is registered for COM interop. To ensure a class library is registered for COM interop, in Visual Studio right-click the project and click Properties. Select the COM interop check box near the bottom of the Build page.

Applying a class extension

There are several ways to apply an extension to a class, depending on whether the class already exists and whether the extension is applied through an application or programmatically. Two options exist to apply an extension to new object classes—creating description classes and passing the class extension's GUID as a parameter of the IFeatureWorkspace.CreateTable and CreateFeatureClass methods. For existing object classes, class extensions can be applied through the IClassSchemaEdit and IFeatureWorkspaceSchemaEdit interfaces.
Classes can have a maximum of one associated extension.
Creating and registering description classes for an extension has the benefit of allowing ArcCatalog to "find" the new class extension and letting users create classes with the extension through that application. For more information on creating description classes, see Creating custom class descriptions in this topic.
 
Passing the class extension's GUID to the IFeatureWorkspace.CreateTable or IFeatureWorkspace.CreateFeatureClass methods can be quicker during testing and debugging of the class extension, but in most cases, even custom clients benefit from the implementation of an object class description.
 
To apply an extension to an existing class, obtain an exclusive schema lock, then call the IClassSchemaEdit.AlterClassExtensionCLSID method. This method can also be used to remove a class extension by providing null values as both parameters. See the following code example:
 
[C#]
public void ChangeClassExtension(IObjectClass objectClass, String extensionUID, IPropertySet extensionProperties)
{
   ISchemaLock schemaLock = (ISchemaLock)objectClass;

   try
   {
       // Attempt to get an exclusive schema lock.
       schemaLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);

       // Cast the object class to the IClassSchemaEdit2 interface.
       IClassSchemaEdit2 classSchemaEdit = (IClassSchemaEdit2)objectClass;

       if (!String.IsNullOrEmpty(extensionUID))
       {
           // Create a unique identifier (UID) object and change the extension.
           UID extUID = new UIDClass();
           extUID.Value = extensionUID;
           classSchemaEdit.AlterClassExtensionCLSID(extUID, extensionProperties);
       }
       else
       {
           // Clear the class extension.
           classSchemaEdit.AlterClassExtensionCLSID(null, null);
       }
   }
   catch (COMException comExc)
   {
       throw new Exception("Could not change class extension.", comExc);
   }
   finally
   {
       schemaLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock);
   }
}
[VB.NET]
Public Sub ChangeClassExtension(ByVal objectClass As IObjectClass, ByVal extensionUID As String, ByVal extensionProperties As IPropertySet)
Dim schemaLock As ISchemaLock = CType(objectClass, ISchemaLock)
 Try
   ' Attempt to get an exclusive schema lock.
   schemaLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock)

   ' Cast the object class to the IClassSchemaEdit2 interface.
   Dim classSchemaEdit As IClassSchemaEdit = CType(objectClass, IClassSchemaEdit)
   If Not String.IsNullOrEmpty(extensionUID) Then
     ' Create a unique identifier (UID) object and change the extension.
     Dim extUID As UID = New UIDClass()
     extUID.Value = extensionUID
     classSchemaEdit.AlterClassExtensionCLSID(extUID, extensionProperties)
   Else
     ' Clear the class extension.
     classSchemaEdit.AlterClassExtensionCLSID(Nothing, Nothing)
   End If
 Catch comExc As COMException
   Throw New Exception("Could not change class extension.", comExc)
 Finally
   schemaLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock)
 End Try
End Sub
If an extension is faulty or has not been properly installed on the client machine, opening the class might not be possible. If this occurs, the workspace can be cast to the IFeatureWorkspaceSchemaEdit interface and the AlterClassExtensionCLSID method of that interface can be called to remove the extension.

Implementing interfaces

Class extensions should implement, at a minimum, two interfaces—IClassExtension and IObjectClassExtension. If the extension is being designed for feature classes, also implement the IFeatureClassExtension interface. IClassExtension is the only required interface, but IObjectClassExtension and IFeatureClassExtension are indicator interfaces and implementing them will not increase development time.
Event handling within a class extension is slightly different from application-level event handlers, such as custom event listeners. Rather than explicitly "wiring" event handling methods to the source of the events, class extensions automatically receive events by implementing their optional event interfaces, such as IObjectClassEvents. Custom objects can also define event handlers for this interface. For more information, see Listening to object class events.
Numerous optional interfaces exist for object class and feature class extensions, which includes the following:
  • IObjectClassValidation
  • IObjectClassEvents
  • IObjectClassInfo and IObjectClassInfo2
  • IRelatedObjectClassEvents and IRelatedObjectClassEvents2
  • IConfirmSendRelatedObjectEvents
  • IFeatureClassEdit
  • IFeatureClassDraw (feature class extensions only)
  • IFeatureClassCreation (feature class extensions only)
 
Developers should also be aware of the persistence provided by extension properties. Extension properties are key-value pairs stored in the geodatabase and are unique to each class, not the extension, allowing each instance of the extension to maintain its own properties. Extension properties can be read from the geodatabase with the IClass.ExtensionProperties property, and written to the geodatabase using the IClassSchemaEdit2.AlterClassExtensionProperties method. If the properties are used frequently by the extension, they can be stored as member variables during extension initialization—they are passed in as a parameter of the IClassExtension.Init method. This approach minimizes the amount of serialization and geodatabase round-trips.
 
Finally, members not specified by any interface (or by a custom interface) can be created in an extension as a way of defining custom behavior and storing data. This is a common way of modifying extension properties. These members can then be accessed by custom clients or other ArcGIS customizations, such as editor or workspace extensions, custom commands, or custom property pages.
 
It is important to remember that a class extension can be called by any geodatabase client, including ArcMap, ArcCatalog, and custom clients. Because of this, the extension should never be responsible for output, as both graphical user interface (GUI) output or console output will be inappropriate for some applications. Writing to the local file system, while useful in some cases, should be implemented carefully, as not all client machines have a common file structure.

IClassExtension

A class extension must implement IClassExtension. It defines two methods—Init and Shutdown—which are called when the class extension is initialized and disposed of, respectively. Class extensions are initialized when their associated class is opened or when they are applied to an existing class. Shutdown is called when their associated class is disposed of or when the extensions are removed from the class.
Depending on the client, Shutdown might not be called when a class is deleted. This is because Shutdown is called when the class is freed from memory, which does not necessarily occur when a class is deleted from a geodatabase. If this is the case, it will be called when the client is closed.
The Init method provides references that can be crucial for other methods to operate correctly. It has two parameters, one of type IClassHelper and one of type IPropertySet. The class helper is an object that provides a reference to the extension's associated class, which many extensions require. It is highly recommended that this reference be stored in a member variable for use by other properties and methods. The property set contains the extension properties, if any exist. If none exist, a null value is passed into the method. For more information on extension properties, see Working with extension properties in this topic.
 
The following code example shows a simple implementation of the Init method:
 
[C#]
private IClassHelper classHelper = null;

public void Init(IClassHelper classHelper, IPropertySet extensionProperties)
{
    // Store the class helper reference.
    this.classHelper = classHelper;
}
[VB.NET]
Private classHelper As IClassHelper = Nothing

Public Sub Init(ByVal classHelper As IClassHelper, ByVal extensionProperties As IPropertySet) Implements IClassExtension.Init
  ' Store the class helper reference.
  Me.classHelper = classHelper
End Sub

Working with extension properties

Extension properties allow a set of objects to be persisted with a class as metadata. These properties can include strings, images, and other objects. Extension properties are stored on a class-by-class basis, not for the extension as a whole, allowing each class with the extension to be configured individually.
Extension properties are accessible from the extension's associated class, through the IClass.ExtensionProperties property, and are passed to the IClassExtension.Init method as a parameter. If the properties are to be used by other methods within the extension, save the properties in member variables during the Init method. Properties can be modified at any time by casting the class to the IClassSchemaEdit2 interface and calling the AlterClassExtensionProperties method with a new set of properties as a parameter. If the properties are to be modified by the extension, a reference to the class can be obtained through the extension's class helper, which is also passed into the IClassExtension.Init method as a parameter. As with other methods in the IClassSchemaEdit interfaces, obtain an exclusive schema lock for the class before modifying its extension properties.
When a class with an extension is created, it has no extension properties and the value passed into the IClassExtension.Init parameter is null. If properties are required for the extension to operate correctly, the Init method can check whether the incoming property set is null and if so, apply a default set of properties.
The following code example shows an excerpt from an extension that keeps a log file of class activity, by using a default location unless a property is set to override the default. The LogFile property is a member of a custom interface, accessible by a property page.
[C#]
private IPropertySet extensionProperties = null;
private IClassHelper classHelper = null;

private const String LOGFILE_PROPERTY = "LogFile";
private const String DEFAULT_LOGFILE = @"C:\Temp\LogFile.txt";

public void Init(IClassHelper classHelper, IPropertySet initExtensionProperties)
{
   // Store the class helper reference.
   this.classHelper = classHelper;

   // Store the extension properties.
   extensionProperties = initExtensionProperties;

   // Create extension properties if none exist.
   if (extensionProperties == null)
   {
       extensionProperties = new PropertySetClass();
       extensionProperties.SetProperty(LOGFILE_PROPERTY, DEFAULT_LOGFILE);
   }
}

public String LogFile
{
   get
   {
       return extensionProperties.GetProperty(LOGFILE_PROPERTY) as String;
   }

   set
   {
       // Get the base class and cast to the IClassSchemaEdit2 interface.
       IClass baseClass = classHelper.Class;
       IClassSchemaEdit2 classSchemaEdit = (IClassSchemaEdit2)baseClass;

       // Attempt to acquire an exclusive schema lock.
       ISchemaLock schemaLock = (ISchemaLock)baseClass;
       try
       {
           schemaLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);

           // Set the extension property and save it.
           extensionProperties.SetProperty(LOGFILE_PROPERTY, value);
           classSchemaEdit.AlterClassExtensionProperties(extensionProperties);
       }
       catch (COMException comExc)
       {
           throw new Exception(String.Format("Error updating extension properties: {0}",
               comExc.Message), comExc);
       }
       finally
       {
           schemaLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock);
       }
   }
}
[VB.NET]
Private extensionProperties As IPropertySet = Nothing
Private classHelper As IClassHelper = Nothing

Private Const LOGFILE_PROPERTY As String = "LogFile"
Private Const DEFAULT_LOGFILE As String = "C:\Temp\LogFile.txt"

Public Sub Init(ByVal classHelper As IClassHelper, ByVal extensionProperties As IPropertySet) Implements IClassExtension.Init
 ' Store the class helper reference.
 Me.classHelper = classHelper

 ' Store the extension properties.
 extensionProperties = extensionProperties

 ' Create extension properties if none exist.
 If extensionProperties Is Nothing Then
   extensionProperties = New PropertySetClass()
   extensionProperties.SetProperty(LOGFILE_PROPERTY, DEFAULT_LOGFILE)
 End If
End Sub

Public Property LogFile() As String
 Get
   Return CType(extensionProperties.GetProperty(LOGFILE_PROPERTY), String)
 End Get
 Set(ByVal value As String)
   ' Get the base class and cast to the IClassSchemaEdit2 interface.
   Dim baseClass As IClass = classHelper.Class
   Dim classSchemaEdit2 As IClassSchemaEdit2 = CType(baseClass, IClassSchemaEdit2)

   ' Attempt to acquire an exclusive schema lock.
   Dim schemaLock As ISchemaLock = CType(baseClass, ISchemaLock)
   Try
     schemaLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock)

     ' Set the extension property and save it.
     extensionProperties.SetProperty(LOGFILE_PROPERTY, value)
     classSchemaEdit2.AlterClassExtensionProperties(extensionProperties)
   Catch comExc As COMException
     Throw New Exception(String.Format("Error updating extension properties: {0}", comExc.Message), comExc)
   Finally
     schemaLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock)
   End Try
 End Set
End Property

IObjectClassExtension and IFeatureClassExtension

The IObjectClassExtension and IFeatureClassExtension interfaces do not define any methods or properties, but act as indicator interfaces (they are used to identify the extension's type). It is recommended that all class extensions implement the IObjectClassExtension interface and those that have feature class specific functionality implement the IFeatureClassExtension interface.

IObjectClassEvents

Use the IObjectClassEvents interface to define custom behavior when objects are created, deleted, or modified. These events occur following the IRow.Store or IRow.Delete methods and before the notification of related or external objects. Because the events follow Store and Delete calls, Store and Delete should not be called on the object from the extension's implementation.
The following code example shows how to use the OnCreate method to time-stamp new objects with their creation date. Implement OnDelete and OnChange even though in this case, they do not define any functionality.
[C#]
private static readonly String timestampFieldName = "CREATED";

// Set position during IClassExtension.Init method using IClass.FindField.
private int timestampFieldPosition = -1;

public void OnCreate(IObject obj)
{
   // Set the time stamp field's value to the current date and time.
   obj.set_Value(timestampFieldPosition, DateTime.Now);
}

public void OnDelete(IObject obj)
{
   // Do nothing.
}

public void OnChange(IObject obj)
{
   // Do nothing.
}
[VB.NET]
Private Const TIMESTAMP_FIELD_NAME As String = "CREATED"

' Set position during IClassExtension.Init method using IClass.FindField.
Private timestampFieldPosition As Integer = -1

Public Sub OnCreate(ByVal obj As IObject) Implements IObjectClassEvents.OnCreate
  If timestampFieldPosition >= 0 Then
    obj.Value(timestampFieldPosition) = DateTime.Now
  End If
End Sub

Public Sub OnDelete(ByVal obj As IObject) Implements IObjectClassEvents.OnDelete
  ' Do nothing.
End Sub

Public Sub OnChange(ByVal obj As IObject) Implements IObjectClassEvents.OnChange
  ' Do nothing.
End Sub
If the OnCreate method is used to define custom behavior, it might be necessary to implement the IObjectClassInfo interface. For more information, see IObjectClassInfo and IObjectClassInfo2 in this topic.

IObjectClassValidation

The IObjectClassValidation interface is used to implement validation rules that are more complex than out-of-the-box rules can accommodate. Examples of this include the following:
  • Fields with string values that require the use of string functions for validation
  • Fields with a range of numeric values that must be validated using another field, also with a range of numeric values (making it unsuitable for subtyping)
  • Fields with values that are constrained by a combination of multiple fields
  • Fields with values that must be validated by checking external sources, such as a data dictionary table or the local file system
 
The following two methods are defined by IObjectClassValidation:
 
ValidateRow is called after the geodatabase's native validation has been completed. Typically, ValidateRow will be implemented to call ValidateField on each field requiring custom validation. Both of these methods have string return types. If the validation is successful, an empty string should be returned; otherwise, a description of the error should be returned.
 
The following code example shows how to perform regular expression matching as part of custom validation. A simple regular expression is used to validate e-mail addresses; note that this expression is overly simplified for the example and might not conform to standards in some cases. It is assumed that any class this would be applied to has a string field named "Email," but in a production environment, this value would likely be stored as an extension property.
[C#]
// The class extension's class helper.
private IClassHelper classHelper = null;

// The name of the class's Email field.
private const String EMAIL_FIELD = "Email";

// The index of the Email field.
private int emailFieldIndex = -1;

// A simple regular expression for e-mail validation.
private const String REGEX_STR = @"[\w-]+@([\w-]+\.)+[\w-]+";

// A Regex instance created when the class extension is initialized.
private Regex regex = null;

public void Init(IClassHelper classHelper, IPropertySet extensionProperties)
{
  // Keep a reference to the helper.
  this.classHelper = classHelper;

  // Find the dataset's Email field.
  IClass baseClass = classHelper.Class;
  emailFieldIndex = baseClass.FindField(EMAIL_FIELD);

  // Create the Regex instance.
  regex = new Regex(REGEX_STR);
}

public String ValidateField(IRow row, String fieldName)
{
  String validationDescription = String.Empty;

  // Check that the Email field is being validated.
  if (fieldName.Equals(EMAIL_FIELD))
  {
    // Get the e-mail address.
    String email = Convert.ToString(row.get_Value(emailFieldIndex));

    // Check the regular expression for a match.
    if (!regex.IsMatch(email))
    {
      validationDescription = String.Format("{0} is not a valid email address.", email);
    }
  }

  return validationDescription;
}

public String ValidateRow(IRow row)
{
  // Validate the Email field.
  return ValidateField(row, EMAIL_FIELD);
}
[VB.NET]
' The class extension's class helper.
Private classHelper As IClassHelper = Nothing

' The name of the class's Email field.
Private Const EMAIL_FIELD As String = "Email"

' The index of the Email field.
Private emailFieldIndex As Integer = -1

' A simple regular expression for e-mail validation.
Private Const REGEX_STR As String = "[\w-]+@([\w-]+\.)+[\w-]+"

' A Regex instance created when the class extension is initialized.
Private regex As Regex = Nothing

Public Sub Init(ByVal classHelper As IClassHelper, ByVal extensionProperties As IPropertySet) _
  Implements IClassExtension.Init

  ' Keep a reference to the helper.
  Me.classHelper = classHelper

  ' Find the dataset's Email field.
  Dim baseClass As IClass = classHelper.Class
  emailFieldIndex = baseClass.FindField(EMAIL_FIELD)

  ' Create the Regex instance.
  regex = New Regex(REGEX_STR)
End Sub

Public Function ValidateField(ByVal row As IRow, ByVal fieldName As String) As String _
  Implements IObjectClassValidation.ValidateField

  Dim validationDescription As String = String.Empty

  ' Check that the Email field is being validated.
  If fieldName.Equals(EMAIL_FIELD) Then
    ' Get the e-mail address.
    Dim email As String = Convert.ToString(row.Value(emailFieldIndex))

    ' Check the regular expression for a match.
    If Not regex.IsMatch(email) Then
      validationDescription = String.Format("{0} is not a valid email address.", email)
    End If
  End If

  Return validationDescription
End Function

Public Function ValidateRow(ByVal row As IRow) As String _
  Implements IObjectClassValidation.ValidateRow

  ' Validate the Email field.
  Return ValidateField(row, EMAIL_FIELD)
End Function
Fields are regularly accessed in the methods of several class extension interfaces, such as the IObjectClassValidation.ValidateField method in the preceding code example and the IObjectClassEvents.OnCreate method in the next code example. Finding a field's index during a method that can be called repeatedly is strongly discouraged, as doing so during the bulk validation or creation of objects can negatively affect performance. Instead, determine the field index during the IClassExtension.Init method and store it in a member variable.

IObjectClassInfo and IObjectClassInfo2

The IObjectClassInfo and IObjectClassInfo2 interfaces provide the following methods that indicate how the data of an object class should be created and modified:
  • CanBypassStoreMethod—Indicates if IRow.Store must be called when objects are created in the class
  • CanBypassEditSession—Indicates if the class requires data modification to take place in an edit session
 
Although CanBypassStoreMethod and CanBypassEditSession are accessed through the class, if IObjectClassInfo and IObjectClassInfo2 are implemented by an extension, the class defers its response to that of the extension.
By default, CanBypassStoreMethod returns true for object classes and simple feature classes, and false for feature classes with complex behavior. Classes with complex behavior often require the Store method to be called, as it triggers the IObjectClassEvents.OnCreate method. CanBypassStoreMethod is called by insert cursors when objects are added to a class; if it returns true, Store will not be called by the cursor. 
Insert cursors are used throughout ArcGIS for Desktop applications, such as in the ArcCatalog Simple Data Loader and in geoprocessing tools. If a class extension implements the IObjectClassEvents interface and has important functionality in the OnCreate method, or the class has other listeners of IObjectClassEvents, implement this method and it returns false. The following code example shows a typical implementation of this method:
 
[C#]
public Boolean CanBypassStoreMethod()
{
    return false;
}
[VB.NET]
Public Function CanBypassStoreMethod() As Boolean Implements IObjectClassInfo.CanBypassStoreMethod
  Return False
End Function
In addition to cases where custom behavior is defined by a class extension, it might be necessary to implement CanBypassStoreMethod if an editor extension is listening for events from a specific class. If IRow.Store is not called when an object is created or modified, geodatabase events will not be passed to the editor extension. For example, this can occur when using ArcMap's Object Loader, which by default, will not call IRow.Store for simple feature classes. If an editor extension depends on the reception of these events, the class should be given an extension and the extension's CanBypassStoreMethod returns false.
 
Alternatively, the IWorkspaceEditControl interface can be used to specify that Store should be called when editing any class in a workspace.
The CanBypassEditSession method indicates whether a class requires objects to be created, deleted, or changed outside of an edit session. Some classes, such as those participating in a network, require an edit session to ensure proper multiuser behavior and the correct management of objects internally cached database states. If a class extension requires edit events to be handled (such as those from the IWorkspaceEditEvents interface) to validate or modify classes or private datasets, implement this method and it returns false. Doing so prevents applications from editing the class outside an edit session. The following code example is a typical implementation of this method:
 
[C#]
public Boolean CanBypassEditSession()
{
    return false;
}
[VB.NET]
Public Function CanBypassEditSession() As Boolean Implements IObjectClassInfo2.CanBypassEditSession
  Return False
End Function
Class policies—whether it requires the Store method or an edit session—can only be tightened by these methods. For example, a feature class that participates in a topology must be edited in an edit session, and implementing a class extension that returns true from the CanByPassEditSession method will not change this.

IRelatedObjectClassEvents and IRelatedObjectClassEvents2

Use the IRelatedObjectClassEvents and IRelatedObjectClassEvents2 interfaces to handle editing events from a class that is related through a relationship class to the extension's class. The former interface defines only a single method, RelatedObjectCreated, which is called when a new object is created in a related class. The latter defines several more methods, most notably RelatedObjectChanged, which is called when an object in a related class is modified, as well as four other methods that are triggered by calls to the IFeatureEdit interface.
 
For any of these methods to be called on a class extension, the proper notification must be enabled in the relationship class. If the extension is on the origin class, notification must be set to Backward or Both, and if the extension is on the destination class, notification must be set to Forward or Both.
 
Unlike many similarly named interfaces, IRelatedObjectClassEvents2 does not inherit from IRelatedObjectClassEvents. For a class extension to define event handlers for both, it must explicitly implement both.
In most cases, creating a class extension for the source of these events and implementing the IObjectClassEvents interface is the best way to provide this functionality. The following are two typical scenarios where implementing a class extension that listens for related object class events is useful:
  • When the source class already has a class extension applied to it.
  • When creating a variation of composite relationship behavior. For example, creating a relationship class that will move and rotate related features, but will not cascade deletion.
 
The RelatedObjectSetMoved and RelatedObjectSetRotated methods can be implemented if a transformation of the related object's geometry needs to be reflected in the extension's class. This is particularly useful in a relationship between two feature classes, for example, a relationship class with a feature class of poles and a feature class of transformers. If a pole is moved or rotated and one or more transformers are attached to that pole, the transformers should move or rotate with it. This can also be useful in a relationship between a feature class and an object class, for example, if an object's attributes are derived from a related feature's geometry.
 
The following code example shows how to use the IRelatedObjectClassEvents2 interface to create this variation of composite relationship behavior:
[C#]
public void RelatedObjectSetMoved(IRelationshipClass relationshipClass, ISet objectsThatNeedToChange,
ISet objectsThatChanged, ILine moveVector)
{
// Get the first object to change and cast to the IFeatureEdit interface.
objectsThatNeedToChange.Reset();
IFeatureEdit featureEdit = objectsThatNeedToChange.Next() as IFeatureEdit;

// If it is the correct type of object, move the related objects.
if (featureEdit != null)
{
featureEdit.MoveSet(objectsThatNeedToChange, moveVector);
}
}

public void RelatedObjectSetRotated(IRelationshipClass relationshipClass, ISet objectsThatNeedToChange,
ISet objectsThatChanged, IPoint origin, Double angle)
{
// Get the first object to change and cast to the IFeatureEdit interface.
objectsThatNeedToChange.Reset();
IFeatureEdit featureEdit = objectsThatNeedToChange.Next() as IFeatureEdit;

// If it is the correct type of object, move the related objects.
if (featureEdit != null)
{
featureEdit.RotateSet(objectsThatNeedToChange, origin, angle);
}
}
[VB.NET]
Public Sub RelatedObjectSetMoved(ByVal relationshipClass As IRelationshipClass, _
ByVal objectsThatNeedToChange As ISet, ByVal objectsThatChanged As ISet, _
ByVal moveVector As ILine) Implements IRelatedObjectClassEvents2.RelatedObjectSetMoved

' Get the first object to change and cast to the IFeatureEdit interface.
objectsThatNeedToChange.Reset()
Dim featureEdit As IFeatureEdit = TryCast(objectsThatNeedToChange.Next(), IFeatureEdit)

' If it is the correct type of object, move the related objects.
If Not featureEdit Is Nothing Then
featureEdit.MoveSet(objectsThatNeedToChange, moveVector)
End If
End Sub

Public Sub RelatedObjectSetRotated(ByVal relationshipClass As IRelationshipClass, _
ByVal objectsThatNeedToChange As ISet, ByVal objectsThatChanged As ISet, _
ByVal origin As IPoint, ByVal angle As Double) Implements IRelatedObjectClassEvents2.RelatedObjectSetRotated

' Get the first object to change and cast to the IFeatureEdit interface.
objectsThatNeedToChange.Reset()
Dim featureEdit As IFeatureEdit = TryCast(objectsThatNeedToChange.Next(), IFeatureEdit)

' If it is the correct type of object, move the related objects.
If Not featureEdit Is Nothing Then
featureEdit.RotateSet(objectsThatNeedToChange, origin, angle)
End If
End Sub
The last two methods that must be implemented to honor the interface are RelatedObjectMoved and RelatedObjectRotated. These methods are currently reserved and should not define any functionality.
 
If a class with an extension that implements IRelatedObjectClassEvents or IRelatedObjectClassEvents2 is going to participate in multiple relationship classes, it is recommended that the IConfirmSendRelatedObjectEvents interface be implemented in tandem. For more information, see IConfirmSendRelatedObjectEvents in this topic.
There might not be a 1:1 relationship between the objects contained by the set parameters of these methods; therefore, iterating through the set of origin class objects and the set of destination class objects in parallel (and assuming a relationship exists) is not recommended. For example, if the relationship class has 1:M cardinality, there can be several objects in the objectsThatNeedToChange set that are related to a single object in the objectsThatChanged set.
 
These events are triggered from the IFeatureEdit interface, which is called by the ArcMap editor when features or sets of features are moved or rotated, but not when they are reshaped (the RelatedObjectChanged event is called in that case). Also, if the related object's geometry is modified directly at the geometry level, for example, by using the ITransform2D.Rotate or ITransform2D.Move methods, these events will not be triggered.

IConfirmSendRelatedObjectEvents

Implement the IConfirmSendRelatedObjectEvents interface to filter notifications received by the extension from any related classes. It consists of five methods with Boolean return values, each of which corresponds to a method in the IRelatedObjectClassEvents2 interface. If a class extension implements these and a method returns false, the corresponding IRelatedObjectClassEvents2 method will not be called, effectively "short-circuiting" the notification.
 
Despite the name of the interface and methods, apply extensions that implement this interface, not to the source of the notifications but the receiver. This can be thought of as confirming the reception of a notification rather than the transmission.
A common situation where this interface can be implemented is when change notifications should only be handled if a specific field of an object is changed (that is, the Shape field). By using the IRowChanges or IFeatureChanges interface, these methods can determine whether the notification is relevant. Another situation where this can be useful is if notification is dependent on the source object's subtype.
 
This interface should also be implemented if a class is receiving notifications from multiple related classes and if events are only relevant from a specific class. This can be done by checking the name of the relationship class when confirmation begins. If hard coding a relationship class name is not ideal, build a custom property page to save the relationship class name as an extension property.
 
The following code example shows how to restrict change notifications to those where a feature's geometry has been modified. Use this in a feature class extension, as it has no effect in an object class extension.
 
[C#]
// The name of the ownership relationship class. TODO: This should be set by implementing an
// extension specific method, along with a property page, and stored in the extension properties.
private String ownershipRelClassName = null;

public Boolean ConfirmSendRelatedObjectChanged(IRelationshipClass relClass, IObject objectThatChanged)
{
   // Make sure the object changed is from the correct related class.
   IDataset dataset = (IDataset)relClass;
   if (!dataset.Name.Equals(ownershipRelClassName))
   {
       return false;
   }

   // Cast to the IFeatureChanges interface and check for shape change.
   IFeatureChanges featureChanges = objectThatChanged as IFeatureChanges;
   if (featureChanges != null)
   {
       return featureChanges.ShapeChanged;
   }

   // Object is not a feature.
   return false;
}
[VB.NET]
' The name of the ownership relationship class. TODO: This should be set by implementing an
' extension specific method, along with a property page, and stored in the extension properties.
Private ownershipRelClassName As String = Nothing

Public Function ConfirmSendRelatedObjectChanged(ByVal relClass As IRelationshipClass, ByVal objectThatChanged As IObject) As Boolean Implements IConfirmSendRelatedObjectEvents.ConfirmSendRelatedObjectChanged
 ' Make sure the object changed is from the correct related class.
 Dim dataset As IDataset = CType(relClass, IDataset)
 If Not dataset.Name.Equals(ownershipRelClassName) Then
   Return False
 End If

 ' Cast to the IFeatureChanges interface and check for shape change.
 Dim featureChanges As IFeatureChanges = TryCast(objectThatChanged, IFeatureChanges)
 If Not featureChanges Is Nothing Then
   Return featureChanges.ShapeChanged
 End If

 ' Object is not a feature.
 Return False
End Function

IObjectInspector

The Attributes dialog box in ArcMap is used to view and edit an object's attributes during an edit session. It contains a tree view, which shows the currently selected objects and a list view, which contains the names and values for each field of the object selected in the tree view. The list view is the default object inspector or feature inspector (the terms are interchangeable).
 
See the following screen shot that shows the Attributes dialog box with the default object inspector:

 
The IObjectInspector interface allows the creation of a custom feature inspector as part of a class extension. Custom feature inspectors can offer a more application specific editing experience, for example, providing calendar controls to edit date values, displaying raster images, or providing an OpenFileDialog for fields that expect local file names.
 
A feature inspector should not be used as the primary method of restricting user input, such as using radio buttons or combo boxes. While these can be applicable to the class's schema, view form level logic as a supplement to domains, subtypes, and validation rules and not a replacement.
 
For an example of a class extension that provides a custom feature inspector, see the Tabbed feature inspector sample.
 
The buttons at the top of the Attributes dialog box—for example, for sorting—will not have any effect when used with a custom feature inspector.

IFeatureClassEdit

Use the IFeatureClassEdit interface for specifying advanced editing configuration for a feature class or object class (has two properties, CanEditWithProjection and CustomSplitPolicyForRelationship, and one method, HasCustomSplitPolicyForRelationship). 
 
ArcMap supports the editing of simple features in a different spatial reference than that of the feature class. If the associated feature class contains simple features, editing of the feature class in a different spatial reference can be prevented by implementing the CanEditWithProjectionproperty and returning false. See the following code example for a typical implementation: 
 
[C#]
public Boolean CanEditWithProjection
{
    get { return false; }
}
[VB.NET]
Public ReadOnly Property CanEditWithProjection() As Boolean Implements IFeatureClassEdit.CanEditWithProjection
  Get
    Return False
  End Get
End Property
When a feature with related objects is split, the geodatabase can delete the related objects, create relationships, or only maintain the relationships with the larger portion of the split feature, depending on the type and cardinality of the relationship class. If the default split policy is not the desired behavior, return true from HasCustomSplitPolicy. The CustomSplitPolicyForRelationship property can then be used to specify a custom split policy according to the relationship class and properties, such as subtype, of the feature being split. If this interface is implemented solely for the CanEditWithProjection property, false is returned (and the split policy property can return anything, as it will not be called).
 
An example of when this would be useful is with a relationship class between parcels and owners. If the relationship class is simple and has 1:M cardinality, when a parcel is divided, the owner remains related to the larger of the two new parcels, but the smaller will not have an owner. The following code example shows how to modify this behavior so that both parts of the parcel remains related to the owner:
 
[C#]
// Load from extension properties in IClassExtension.Init.
private String parcelOwnerRelClassName = null;

public Boolean HasCustomSplitPolicyForRelationship()
{
  return true;
}

public esriRelationshipSplitPolicy get_CustomSplitPolicyForRelationship(IRow row, IRelationshipClass relClass)
{
  IDataset rcDataset = (IDataset)relClass;
  if (rcDataset.Name.Equals(parcelOwnerRelClassName))
  {
      // If the relationship class (RC) is the parcel owner RC, use the custom policy.
      return esriRelationshipSplitPolicy.esriRSPPreserveOnAll;
  }
  else
  {
      // It is another RC. Use the default policy.
      if (relClass.IsComposite)
      {
          return esriRelationshipSplitPolicy.esriRSPDeleteParts;
      }
      else
      {
          return esriRelationshipSplitPolicy.esriRSPPreserveOnLargest;
      }
  }
}
[VB.NET]
Public Function HasCustomSplitPolicyForRelationship() As Boolean Implements IFeatureClassEdit.HasCustomSplitPolicyForRelationship
 Return True
End Function

Public ReadOnly Property CustomSplitPolicyForRelationship(ByVal row As IRow, ByVal relClass As IRelationshipClass) As esriRelationshipSplitPolicy Implements IFeatureClassEdit.CustomSplitPolicyForRelationship
 Get
   Dim rcDataset As IDataset = CType(relClass, IDataset)
   If rcDataset.Name.Equals(ownershipRelClassName) Then
     ' If the relationship class (RC) is the parcel owner RC, use the custom policy.
     Return esriRelationshipSplitPolicy.esriRSPPreserveOnAll
   Else
     ' It is another RC. Use the default policy.
     If relClass.IsComposite Then
       Return esriRelationshipSplitPolicy.esriRSPDeleteParts
     Else
       Return esriRelationshipSplitPolicy.esriRSPPreserveOnLargest
     End If
   End If
 End Get
End Property
An extension that uses this interface to define a custom split policy can be applied to the source or destination class of the relationship class. Although the CanEditWithProjection property is only applicable to feature class extensions, the ability to define a custom split policy also makes this interface relevant to object class extensions.

IFeatureClassDraw

Use the IFeatureClassDraw interface to specify custom drawing behavior for a feature class. Custom drawing can be achieved with a custom renderer or by creating custom features that implement the IFeatureDraw interface. Only implement this interface in a feature class extension.
ArcGIS applications with three-dimensional (3D) rendering (that is, ArcGlobe, ArcScene, and ArcGIS Explorer) might not honor custom rendering defined by a feature class extension.
An important point to remember is that custom renderers can be used by feature classes without a feature class extension. Associating a custom renderer with an extension is only required if the feature class should be rendered exclusively by a certain type of renderer, or if a renderer is kept "private" by not registering its property page in the appropriate component category. A custom property page is still required for this interface to implement correctly, but it is not necessary to be registered on the ESRI Renderer Property Pages component category. For more information on custom renderers, see Creating custom feature renderers.
 
The simplest method to implement is DoesCustomDrawing. It has a Boolean return type but should always return true. The following is a typical code example:
 
[C#]
public Boolean DoesCustomDrawing()
{
    return true;
}
[VB.NET]
Public Function DoesCustomDrawing() As Boolean Implements IFeatureClassDraw.DoesCustomDrawing
  Return True
End Function
The HasCustomRenderer method and ExclusiveCustomRenderer property have Boolean return types and are also easy to implement.
  • HasCustomRenderer returns true if the custom drawing behavior is implemented with a custom renderer and returns false if the behavior is defined in a custom feature.
  • ExclusiveCustomRenderer returns true if the custom renderer is the only renderer that layers can use when the feature class displays (false allows others to be used).
 
If an exclusive custom renderer is defined but the specified renderer is not available to a client, the client uses the default renderer (in ArcMap, the Single Symbol renderer). The following is a typical code example:
 
[C#]
public Boolean ExclusiveCustomRenderer
{
    get { return false; }
}

public Boolean HasCustomRenderer()
{
    return true;
}
[VB.NET]
Public ReadOnly Property ExclusiveCustomRenderer() As Boolean Implements IFeatureClassDraw.ExclusiveCustomRenderer
  Get
    Return True
  End Get
End Property

Public Function HasCustomRenderer() As Boolean Implements IFeatureClassDraw.HasCustomRenderer
  Return True
End Function
It is recommended that a feature class extension defining a custom drawing be applied before the feature class is included in ArcMap documents or layer files. If these files include the feature class before the application of an extension that defines a custom renderer as exclusive, these files will not honor the extension's specifications.
CustomRenderer and CustomRendererPropPageCLSID define the renderer used. CustomRenderer returns an instance of the renderer associated with the class and CustomRendererPropPageCLSID returns a unique identifier (UID) instance for the renderer's property page. If the custom drawing is implemented through a custom feature and not a custom renderer, null can be returned. See the following code example:
 
[C#]
public object CustomRenderer
{
    get
    {
        Type type = Type.GetTypeFromProgID("PointDispersal.Renderer");
        return Activator.CreateInstance(type);
    }
}

public UID CustomRendererPropPageCLSID
{
    get
    {
        UID uid = new UIDClass();
        uid.Value = "{CB3B689F-27A5-11D7-8B5C-00104BB6FCCB}";
        return uid;
    }
}
[VB.NET]
Public ReadOnly Property CustomRenderer() As Object Implements IFeatureClassDraw.CustomRenderer
 Get
   Dim type As Type = type.GetTypeFromProgID("PointDispersal.Renderer")
   Return Activator.CreateInstance(type)
 End Get
End Property

Public ReadOnly Property CustomRendererPropPageCLSID() As UID Implements IFeatureClassDraw.CustomRendererPropPageCLSID
 Get
   Dim uid As UID = New UIDClass()
   uid.Value = "{CB3B689F-27A5-11D7-8B5C-00104BB6FCCB}"
   Return uid
 End Get
End Property
The final property is only important if custom drawing is implemented through custom features. RequiredFieldsForDraw specifies the fields' (other than the Shape field) features required in their implementation of IFeatureDraw.Draw. By default, the feature cursor used in the drawing process only retrieves the features' geometries. If others are required, they should be returned as a fields collection by this property. The following code example is suitable in a scenario where the ObjectID (OID) field is used by the custom feature:
 
[C#]
public IFields RequiredFieldsForDraw
{
   get
   {
       // Get the class's OID field.
       IClass baseClass = classHelper.Class;
       String oidFieldName = baseClass.OIDFieldName;
       int oidFieldIndex = baseClass.FindField(oidFieldName);
       IFields baseClassFields = baseClass.Fields;
       IField oidField = baseClassFields.get_Field(oidFieldIndex);

       // Create a new fields collection.
       IFields fields = new FieldsClass();
       IFieldsEdit fieldsEdit = (IFieldsEdit)fields;
       fieldsEdit.AddField(oidField);

       return fields;
   }
}
[VB.NET]
Public ReadOnly Property RequiredFieldsForDraw() As IFields Implements IFeatureClassDraw.RequiredFieldsForDraw
 Get
   ' Get the class's OID field.
   Dim baseClass As IClass = classHelper.Class
   Dim oidFieldName As String = baseClass.OIDFieldName
   Dim oidFieldIndex As Integer = baseClass.FindField(oidFieldName)
   Dim baseClassFields As IFields = baseClass.Fields
   Dim oidField As IField = baseClassFields.Field(oidFieldIndex)

   ' Create a new fields collection.
   Dim fields As IFields = New FieldsClass()
   Dim fieldsEdit As IFieldsEdit = CType(fields, IFieldsEdit)
   fieldsEdit.AddField(oidField)

   Return fields
 End Get
End Property

Creating custom class descriptions

Class descriptions provide applications and developers with a template for dataset creation. Two frequently used examples of this are object class descriptions and feature class descriptions, both included with ArcGIS. ArcCatalog uses these to determine the required fields of a new object or feature class, and their instance of class identifiers (CLSIDs) and extension CLSIDs. ArcGIS also includes other class descriptions for classes, such as annotation, dimensions, and raster catalogs. Creating a feature class description makes a new type of feature class visible to ArcGIS and both object and feature class descriptions are convenient ways for developers to access information about the class.
If the new class type contains nonspatial data, the description class should implement the IObjectClassDescription interface and if it contains features, it should also implement the IFeatureClassDescription interface. Like a class extension, a class description requires component category registration but should be registered to the GeoObjectClassDescriptions category rather than the GeoObjectClassExtensions category.
The name properties of a class description are easy to implement. The following are the name properties that are required for a description:
  • Name—Returns what users see in the Type section of the ArcCatalog New Feature Class dialog box when creating a new feature class
  • AliasName—Provides an alternate name for the object class description
  • ModelName—Provides an additional way of guaranteeing a class is unique
In most cases, the AliasName and ModelName properties are unused and it is safe to return empty strings. In addition to the three previously mentioned properties, the ModelNameUnique property indicates if the model name is unique. If a blank model name is used, false is returned.
The following code example shows a typical implementation of the Name, AliasName, ModelName, and ModelNameUnique properties:
[C#]
public String Name
{
    get { return "Timestamped Class"; }
}

public String AliasName
{
    get { return String.Empty; }
}

public String ModelName
{
    get { return String.Empty; }
}

public Boolean ModelNameUnique
{
    get { return false; }
}
[VB.NET]
Public ReadOnly Property Name() As String Implements IObjectClassDescription.Name
  Get
    Return "Timestamped Class"
  End Get
End Property

Public ReadOnly Property AliasName() As String Implements IObjectClassDescription.AliasName
  Get
    Return ""
  End Get
End Property

Public ReadOnly Property ModelName() As String Implements ESRI.ArcGIS.Geodatabase.IObjectClassDescription.ModelName
  Get
    Return ""
  End Get
End Property

Public ReadOnly Property ModelNameUnique() As Boolean Implements ESRI.ArcGIS.Geodatabase.IObjectClassDescription.ModelNameUnique
  Get
    Return False
  End Get
End Property
The InstanceCLSID and ClassExtensionCLSID properties return UID instances containing the GUIDs of the classes' objects (or features) and of the classes' extension, respectively. Many classes return UIDs containing the GUIDs of the object or feature class, but classes designed to use custom features return a UID containing the value of the custom feature's GUID. The ClassExtensionCLSID property returns a UID containing the appropriate class extension's GUID. The following code example shows a typical implementation of these two properties:
 
[C#]
public UID InstanceCLSID
{
   get
   {
       // Return the UID of feature.
       UID uid = new UIDClass();
       uid.Value = "{52353152-891A-11D0-BEC6-00805F7C4268}";
       return uid;
   }
}

public UID ClassExtensionCLSID
{
   get
   {
       // Return the UID of TimestampedClassExtension.
       UID uid = new UIDClass();
       uid.Value = "{31b0b791-3606-4c58-b4d9-940c157dca4c}";
       return uid;
   }
}
[VB.NET]
Public ReadOnly Property InstanceCLSID() As UID Implements IObjectClassDescription.InstanceCLSID
 Get
   Dim uid As UID = New UIDClass()
   uid.Value = "{52353152-891A-11D0-BEC6-00805F7C4268}"
   Return uid
 End Get
End Property

Public ReadOnly Property ClassExtensionCLSID() As UID Implements IObjectClassDescription.ClassExtensionCLSID
 Get
   Dim uid As UID = New UIDClass()
   uid.Value = "{31b0b791-3606-4c58-b4d9-940c157dca4c}"
   Return uid
 End Get
End Property
RequiredFields returns a fields collection containing the required fields for the description's class type to operate properly. For example, an object class description returns a fields collection containing one ObjectID field, whereas a feature class description returns an ObjectID field and a Shape field. The following code example shows an implementation of this property for a time stamped class, which requires "Created" and "Modified" date fields:
 
[C#]
public IFields RequiredFields
{
    get
    {
        // Get the feature class required fields.
        IFeatureClassDescription fcDescription = new FeatureClassDescriptionClass();
        IObjectClassDescription ocDescription = (IObjectClassDescription)fcDescription;
        IFields requiredFields = ocDescription.RequiredFields;
        IFieldsEdit requiredFieldsEdit = (IFieldsEdit)requiredFields;

        // Add a created date field.
        IField createdField = new FieldClass();
        IFieldEdit createdFieldEdit = (IFieldEdit)createdField;
        createdFieldEdit.Name_2 = "Created";
        createdFieldEdit.Required_2 = false;
        createdFieldEdit.IsNullable_2 = true;
        createdFieldEdit.Type_2 = esriFieldType.esriFieldTypeDate;
        requiredFieldsEdit.AddField(createdField);

        // Add a modified date field.
        IField modifiedField = new FieldClass();
        IFieldEdit modifiedFieldEdit = (IFieldEdit)modifiedField;
        modifiedFieldEdit.Name_2 = "Modified";
        modifiedFieldEdit.Required_2 = false;
        modifiedFieldEdit.IsNullable_2 = true;
        modifiedFieldEdit.Type_2 = esriFieldType.esriFieldTypeDate;
        requiredFieldsEdit.AddField(modifiedField);

        return requiredFields;
    }
}
[VB.NET]
Public ReadOnly Property RequiredFields() As IFields Implements IObjectClassDescription.RequiredFields
  Get
    ' Get the feature class required fields.
    Dim fcDescription As IFeatureClassDescription = New FeatureClassDescriptionClass()
    Dim ocDescription As IObjectClassDescription = CType(fcDescription, IObjectClassDescription)
    Dim fields As IFields = ocDescription.RequiredFields
    Dim fieldsEdit As IFieldsEdit = CType(fields, IFieldsEdit)

    ' Add a created date field.
    Dim createdField As IField = New FieldClass()
    Dim createdFieldEdit As IFieldEdit = CType(createdField, IFieldEdit)
    createdFieldEdit.Name_2 = "Created"
    createdFieldEdit.Required_2 = False
    createdFieldEdit.IsNullable_2 = True
    createdFieldEdit.Type_2 = esriFieldType.esriFieldTypeDate
    fieldsEdit.AddField(createdField)

    ' Add a modified date field.
    Dim modifiedField As IField = New FieldClass()
    Dim modifiedFieldEdit As IFieldEdit = CType(modifiedField, IFieldEdit)
    modifiedFieldEdit.Name_2 = "Modified"
    modifiedFieldEdit.Required_2 = False
    modifiedFieldEdit.IsNullable_2 = True
    modifiedFieldEdit.Type_2 = esriFieldType.esriFieldTypeDate
    fieldsEdit.AddField(modifiedField)

    Return fields
  End Get
End Property
If the class description is for a feature class, the following properties are also required by the IFeatureClassDescription interface:
  • FeatureType—Returns the feature type to be created (that is, simple, simple junction, or simple edge)
  • ShapeFieldName—Returns the default Shape field name (by default, SHAPE for feature classes)
 
The following code example shows a typical implementation of these properties:
 
[C#]
public esriFeatureType FeatureType
{
    get { return esriFeatureType.esriFTSimple; }
}

public String ShapeFieldName
{
    get
    {
        IFeatureClassDescription fcDescription = new FeatureClassDescriptionClass();
        return fcDescription.ShapeFieldName;
    }
}
[VB.NET]
Public ReadOnly Property FeatureType() As esriFeatureType Implements IFeatureClassDescription.FeatureType
  Get
    Return esriFeatureType.esriFTSimple
  End Get
End Property

Public ReadOnly Property ShapeFieldName() As String Implements IFeatureClassDescription.ShapeFieldName
  Get
    Dim fcDescription As IFeatureClassDescription = New FeatureClassDescription()
    Return fcDescription.ShapeFieldName
  End Get
End Property
When a feature class description is implemented and registered in the appropriate component category, it can be created in ArcCatalog. See the following screen shot:
 
 
In addition to availability in ArcCatalog, having a class description also makes a developer's job easier, as descriptions are a convenient way to get UIDs and required fields in a custom client. See the following code example:
 
[C#]
// Create an instance of the TimestampedClassDescription.
Type tsDescriptionType = Type.GetTypeFromProgID("TimestampedClassDescription.TimestampedClassDescription");
object tsDescriptionObject = Activator.CreateInstance(tsDescriptionType);

// Cast the description object to ArcGIS class description interfaces.
IFeatureClassDescription fcDescription = (IFeatureClassDescription)tsDescriptionObject;
IObjectClassDescription ocDescription = (IObjectClassDescription)tsDescriptionObject;

// Create a new feature class using the class description.
IFeatureClass featureClass = featureWorkspace.CreateFeatureClass("MyTimestampClass", ocDescription.RequiredFields,
   ocDescription.InstanceCLSID, ocDescription.ClassExtensionCLSID, esriFeatureType.esriFTSimple,
   fcDescription.ShapeFieldName, "");
[VB.NET]
' Create an instance of the TimestampedClassDescription.
Dim tsDescriptionType As Type = Type.GetTypeFromProgID("TimestampedClassDescription.TimestampedClassDescription")
Dim tsDescriptionObject As Object = Activator.CreateInstance(tsDescriptionType)

' Cast the description object to ArcGIS class description interfaces.
Dim fcDescription As IFeatureClassDescription = CType(tsDescriptionObject, IFeatureClassDescription)
Dim ocDescription As IObjectClassDescription = CType(tsDescriptionObject, IObjectClassDescription)

' Create a new feature class using the class description.
Dim featureClass As IFeatureClass = featureWorkspace.CreateFeatureClass("MyTimestampClass", _
 ocDescription.RequiredFields, ocDescription.InstanceCLSID, ocDescription.ClassExtensionCLSID, _
 esriFeatureType.esriFTSimple, fcDescription.ShapeFieldName, "")


See Also:

Geodatabase extensions
Creating workspace extensions
How to register COM components
Listening to object class events




To use the code in this topic, reference the following assemblies in your Visual Studio project. In the code files, you will need using (C#) or Imports (VB .NET) directives for the corresponding namespaces (given in parenthesis below if different from the assembly name):
Development licensing Deployment licensing
ArcGIS for Desktop Basic ArcGIS for Desktop Basic
ArcGIS for Desktop Standard ArcGIS for Desktop Standard
ArcGIS for Desktop Advanced ArcGIS for Desktop Advanced
Engine Developer Kit Engine: Geodatabase Update