C# Coding Solutions—How Marker Interfaces Dependencies Are Implemented
Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio
How Marker Interfaces Dependencies Are Implemented
Having covered the dependencies let’s look at how some of them could be implemented and their ramifications. When Application references IMarker like in the IMarker Object Reference what you want to be able to do is move from a general interface to a specific interface. The vague interface is used as the lowest common denominator interface, and the specific interface is used to perform some manipulation that only that instance supports. For every single general interface reference, there could be dozens of specific interfaces.
There are two solutions for implementing the IMarker Object Reference dependency: extending IMarker or using the Extension pattern. Extending the IMarker interface as in the following example adds methods and properties to it:
interface IMarker { void Method(); void ExtendedMethod(); }
This approach is very easy, but every class that implements IMarker has to implement the new method ExtendedMethod, whether it applies or not. Because of this disadvantage you should never use this method. Additionally adding methods or properties to an interface willy-nilly is asking for problems. This example, while silly, is meant to illustrate that if you ever feel the desire to extend a marker interface for one reason or another, don’t. The point of the marker interface is to stick to a minimalist declaration.
The other solution is to use the Extension pattern, which means to do a typecast. There are two types of Extension pattern implementations: static and dynamic. The following is an implementation of the static Extension pattern:
interface IMarker { void Method(); } interface IExtension : IMarker { void ExtendedMethod(); } class Implementation : IExtension { public void Method() { } public void ExtendedMethod() { } }
In the example the IMarker interface is defined as it originally was. The interface declaration IExtension (which subclasses the interface IMarker) is new and represents the specific interface. There could be multiple specific interfaces all deriving from IMarker, and each specific interface can have as many methods or properties as need. Don't concern yourself with making the specific interface minimalist as that is not the role of the specific interface. Implementing IExtension means implementing IMarker, and that provides a complete and neatly defined interface package. The class Implementation implements IExtension, which implies implementing IMarker. This strategy could be used for multiple extension interfaces, while making it possible for CommandImplementationsManager to interact with the IMarker interface. The application accesses the IExtension interface from the IMarker, as the following condensed example illustrates:
IExtension extension =
((IMarker)new Implementation()) as IExtension;
In the example the instance of Implementation is cast to IMarker. Then using the as operator the IMarker instance is cast to IExtension. The as operator must be used because IExtension is an optional interface that may or may not be implemented. If the IExtension pattern is implemented, the cast assigns a reference to the variable extension. If a cast cannot be performed, then the variable extension contains a null value. If we used a cast that contains brackets, an exception would be generated if a cast could not be carried out. Generating an exception is wrong because IExtension is a specific interface that may or may not be implemented by the IMarker interface instance.
Another way to implement the same code is to use two separate interfaces, as in the following example:
interface IMarker { void Method(); } interface IExtension { void ExtendedMethod(); } class Implementation : IExtension, IMarker { public void Method() { } public void ExtendedMethod() { } } // … IExtension extension = ((IMarker)new Implementation()) as IExtension;
The only real difference at a technical level is that IExtension does not subclass IMarker, and Implementation has to implement two interfaces. From the perspective of the application, the same code is used.
However, from an abstract logic perspective, implementing IExtension does not imply implement IMarker. The fact that the two interfaces are separate has the ramification that a specific interface does not have to be associated with the marker interface. The two interfaces can be used in other contexts. I tend to prefer this approach because not all specific interface implementations are required to be used in a marker context.
One advantage to this approach is that you may think having separate interfaces makes it simpler to implement abstract base classes. But as illustrated in the solution "Implementing Interfaces," abstract base classes do not have to implement interfaces.
In the context of the IMarker extension variation, there is another way to implement IMarker. In the preceding examples IMarker has a single method, which could have been replaced with a delegate defined as follows:
public delegate void MarkerDelegate();
To complete an implementation using a delegate, it is only required to implement IExtension. The modified Implementation class would appear similar to the following:
public class Implementation : IExtension { public void Method() { } public void ExtendedMethod() { } }
The change in the Implementation is the missing IMarker interface implementation. The method declarations remain the same. The marker interface, more appropriately called marker delegate, is created and retrieved using the following source code:
MarkerDelegate delegates = new MarkerDelegate( new MarkerImplementation().Method); IExtension extension = (delegates.GetInvocationList()[ 0].Target) as IExtension;
In the source code a delegate list is created and assigned to the delegates variable. The delegate list contains a single delegate that references the method MarkerImplementation.Method. Having a list of delegate method references is not the same as having a list of object references. Remember from Figure 4-2 it is necessary to convert the delegate into an object instance so that the IExtension interface can be manipulated. To be able to perform a typecast you need to iterate the individual delegates and then typecast the owner of the delegate method. The method GetInvocationList returns an array of delegates. From the example, we know that there is at least one delegate, and we can reference the delegate using the zeroth index. The zeroth index returns a delegate instance that references an object instance’s method.
To move from the delegate instance and get at the object instance the property Delegate. Target is referenced. The value of Delegate.Target is an Object instance that can be typecast to the desired IExtension interface. It is important to perform the typecast using the as operator because Target might be a null value, and the object might not have implemented the optional interface. The property Target will be null when the delegate references a method that has been declared using the static keyword. This is logical because static methods are not associated with a class instance.
This is the only time that the IMarker interface is converted to a delegate and is meant to illustrate how delegates can be used in place of interfaces. For the remaining examples, illustrating every example using both the interface and delegate would be tedious. So the remaining examples will use the IMarker interface for explanation purposes. However, other variations could be implemented using a delegate. The only real different between an interface and a delegate is that you need to access a method and a property to get at the object that would be used in the interface explanation.
In Figure 4-2 another dependency is the IMarker State variation. This variation is typically used when a series of immutable objects are wired together. The application has a state that is transferable and is to be manipulated by the IMarker instances. The objects are considered immutable because the state is passed only once and the IMarker instance does not ask for more information and does not alter the state of the passed information. The application expects that once the state has been assigned, the IMarker instances are self-sufficient and know what to do with the state without any further intervention by the application.
The following is an example of implementing the IMarker State variation dependency:
interface IMarker { void Method(); } class Implementation : IMarker { public Implementation( Object state) { } public void Method() { } } class Factory { public static IMarker Instantiate( Object state) { return new Implementation( state); } }
The interface IMarker remains as is, and Implementation implements IMarker. The IMarker State variation is implemented by the constructor of Implementation. In the example the constructor is a single parameter of type Object. However, it does not need to be a single parameter, nor of the type Object, and the parameter could be multiple .NET Generics parameters. The type Object is used as an arbitrary example of how state can be transferred. The single parameter presents some state (defined by the application) that is manipulated by the constructor of the interface implementation.
Requiring parameters for the constructor puts us into a bind; any factory that is used to instantiate Implementation must have the means to provide the state. In the example the method Factory.Instantiate has a single parameter that is identical to the constructor parameters. This illustrates that the state is passed through, not manipulated by the factory.
Another of Figure 4-2’s dependency variations between Application and IMarker implementation is the Application Object Reference. This variation occurs when the IMarker implementation requires information from the Application. This situation usually occurs when the IMarker implementation wants to carry out some logic but needs extra information from the application.
The Application Object Reference and IMarker State dependency variations are similar in that information is transferred from the application to the IMarker implementation. The difference is that Application Object Reference is a dynamic querying of application capabilities, and IMarker State is a static transfer of state. With IMarker State, the constructor provides all of the information that the IMarker needs. With Application Object Reference, the IMarker interface instance queries the information dynamically. The querying used is identical to the technique illustrated by IMarker Object Reference. For the IMarker interface instance to be able to query the application for supported interfaces, the application needs to provide an initial reference-object instance. The IMarker interface (or, more appropriately, an extension interface) could supply that reference-object instance.
The sequence of events would be for the Application to instantiate an IMarker instance. Then Application would typecast the IMarker for a special callback interface. If the special callback interface exists, then the parent assignment method is called. The parent assignment method provides an object that the IMarker instance can typecast for a particular interface. The interfaces used in the sequence of events are defined as follows:
interface IMarker { void Method(); } interface IApplicationCallback : IMarker { void AssignParent( IApplicationMarker parent); } interface IApplicationMarker { }
The interface IApplicationCallback extends the IMarker interface and has a single method that Application uses to know whether a parent should be assigned to the IMarker instance. If the IMarker implementation supports IApplicationCallback then Application typecasts to IApplicationCallback and calls the method AssignParent. The interface IApplicationMarker is empty for illustration purposes, and is used as a reference for typecasting the Application object instance.
The code to make all of this work is as follows:
class Implementation : IApplicationCallback { public void Method() { } IApplicationMarker _parent; public void AssignParent( IApplicationMarker parent) { _parent = parent; } } class Application : IApplicationMarker { public void CallIndividualIMarkerImplementation( IMarker child) { IApplicationCallback callback = child as IApplicationCallback; if( callback != null) { callback.AssignParent( this); } callback.Method(); } }
The class Implementation implements the IApplicationCallback interface, which implies implementing IMarker. In the implementation of AssignParent the passed-in parent instance is assigned to the data member _parent. From the perspective of the IPlaceholder instance the type IApplicationMarker interface is a marker interface that needs to be typecast using the as operator. The typecast may or may not be supported by the application, and it is up to the IMarker implementation to deal with potential unsupported interfaces.
The class Application implements the IApplicationMarker interface. In the method CallIndividualMarkerImplementation, the AssignParent method (if supported) must be called before the IMarker method is called. This is so the IMarker instance can initialize its state and prepare to be called.
If a class implements the IApplicationCallback interface, then the class cannot make any assumptions about the parent. For example, it would be bad programming practice to keep application object references after IMarker.Method is called.
The final dependency variation in Figure 4-2 is between IMarker instances. Such dependencies happen when one IMarker instance relies on data generated by another IMarker instance. Typically, having IMarker instances depend on each other using direct referencing techniques is bad, as the following code illustrates:
interface IMarker { void Method(); } class Implementation1 : IMarker { IMarker _sibling; public void Method() { } } class Implementation2 : IMarker { IMarker _sibling; public void Method() { } }
The code illustrates two IMarker implementations: Implementation1 and Implementation2. Within each class is a single data member that references another IMarker instance. Even though the data members reference the sibling using the IMarker interface, the direct referencing is wrong.
Imagine using the CommandsImplementationManager class to remove an IMarker instance. If one of the implementations had a direct reference to the IMarker instance, from the perspective of IMarker instance the deleted instance would never seem deleted. This is a programmatic flaw that should never occur.
To avoid such problems, use the Mediator pattern. The implementation of the Mediator pattern is an extension of Application Object Reference. The application and the IMarker instances do everything expected when implementing the Application Object Reference, plus they add a sibling querying method. The complete code is similar to the following:
interface IMarker { void Method(); } interface IApplicationCallback : IMarker { void AssignParent(IApplicationMarker parent); } interface IApplicationMarker { IMarker GetSibling( object identifier); } interface IExtension { void AnotherMethod(); } class Implementation1 : IApplicationCallback, IExtension { IApplicationMarker _parent; public void AssignParent(IApplicationMarker parent) { _parent = parent; } public void AnotherMethod() { } public void Method() { } } class Implementation2 : IApplicationCallback { IApplicationMarker _parent; public void AssignParent(IApplicationMarker parent) { _parent = parent; } public void Method() { IExtension ext = _parent.GetSibling( "some-identifier") as IExtension; if( ext != null) { ext.AnotherMethod(); } } }
In the example code the new code with respect to Application Object Reference is bolded. In the interface IApplicationMarker, the Application implements the method GetSibling, which completes the implementation of the Mediator pattern. The GetSibling method acts like a gopher, retrieving information for an IMarker instance. If the information can be retrieved, then GetSibling returns an IMarker instance. Which sibling to retrieve is based on the value of the parameter identifier. In the example the parameter identifier is an object type, but you can use whatever you want to identify the data to be retrieved.
The preceding example shows a query for Implementation2’s sibling when Implementations2’s IMarker interface method is called; the sibling is the class Implementation1. If the sibling can be found, the result is typecast to IExtension, and that results in a call to the method AnotherMethod.
Using the Mediator pattern decouples the IMarker implementations from each other, thereby reducing the chance that an object reference will be inadvertently kept by the IMarker instances.
Remember the following when dealing with placeholder interfaces or base classes:
- Use minimal placeholder interfaces so that types are decoupled from each other. The result is that when type implementations change, the ramifications of those changes are kept to a minimum.
- Minimal or placeholder interfaces keep code decoupled, but also make it more complicated for implementations to exchange information outside of the placeholder interfaces.
- To exchange state information between
ApplicationorIMarkerimplementations, the best approach is to use the Extension pattern that will typecast for the interface that it is interested in.
- To exchange state information between
- Optional interfaces must always be queried using the
asoperator so that if a typecast is not possible the code fails gracefully.
- Optional interfaces must always be queried using the
- Delegates can be typecast using extra method and property calls.
- When more-complicated interface or instance querying is required, the Mediator pattern (as illustrated by the
IMarkerState) or theObjectExchange dependency variation is used.
- When more-complicated interface or instance querying is required, the Mediator pattern (as illustrated by the
- If you use the techniques described in this section, there is absolutely no need for the
IMarkerimplementations to reference any objects other than the parent. Everything can be passed as a state using the constructor theIApplicationMarkerinstance.
- If you use the techniques described in this section, there is absolutely no need for the
|

