.NET Interface-based Programming (Cont.) Interface Methods Collusion When deriving a class from two or more interfaces that define an identical method, you have two options: the first is to channel both interface methods to the same actual method implementation. The second is to provide separate method implementations. For example, consider two interfaces that define the identical method Method1(): public interface IMyInterface { void Method1(); } public interface IMyOtherInterface { void Method1(); }
If you want to channel both interface methods to the same method implementation, then all you have to do is derive from the interfaces and implement the method once: public class MyClass : IMyInterface,IMyOtherInterface { public void Method1(){...} //Other methods and members }
Regardless of which interface the client of MyClass chooses to use, calls to Method1() are channeled to that single implementation: IMyInterface obj1; IMyOtherInterface obj2;
obj1 = new MyClass(); obj1.Method1();
obj2 = (IMyOtherInterface)obj1; obj2.Method1();
To provide separate implementations, you need to use explicit interface implementation by qualifying the method implementation with the interface that defines it: public class MyClass : IMyInterface,IMyOtherInterface { void IMyInterface.Method1(){...} void IMyOtherInterface.Method1(){...} //Other methods and members }
Now, when the client calls an interface method, that interface-specific method is called.
Interfaces and Class Hierarchy In component-oriented programming, you focus on defining and implementing interfaces. In object-oriented programming, you model your solution using class hierarchies. The question is, how do the two concepts interact? The answer depends on the way you override or redefine the interface methods at the different levels of the class hierarchy. Consider the code in Listing 6: In a typical class-hierarchy, the top-most base class should derive from the interface, providing polymorphism to all sub classes with the interface. The top-most base class must also define all the interface members as virtual so that sub classes could override them. Each level of the class hierarchy would then override its preceding level (using the override inheritance qualifier), as shown in Listing 6. When the client uses the interface, it will get the desired interpretation of the interface. For example, if the client code is: ITrace obj = new B(); obj.TraceSelf();
Then the object would trace "B" to the output Window as expected. Things get less obvious if the subclasses use the new inheritance qualifier. The new modifier gives only subclass behavior when dealing with an explicit reference to a subclass, such as: B obj = new B();
In all other cases, the base class implementation is used. If the code in Listing 6 was: public class A : ITrace { public virtual void TraceSelf() { Trace.WriteLine("A"); } } public class B : A { public new void TraceSelf() { Trace.WriteLine("B"); } } public class C : B { public new void TraceSelf() { Trace.WriteLine("C"); } }
Then the client code: ITrace obj = new B(); obj.TraceSelf();
would now trace "A" to the output Window instead of "B." Note that this is exactly why the new inheritance visibility modifier is available in the first place. Imagine a client that somehow depends on the base class particular implementation. If a new subclass is used instead of the base class, the new modifier ensures that the client will get the implementation it expects. However, this nuance makes sense only when dealing with clients that are not using interface-based programming but rather program directly against the objects: A obj = new B(); obj.TraceSelf();//Traces "A"
You can still support such clients, however, and provide interface-based services to the rest of the clients. To achieve that, each of the classes in the hierarchy can reiterate its polymorphism with the interface by explicitly deriving from it (on top of having the base class derive from the interface). Doing so (as shown in Listing 7), makes the new modifier yield the same result as the override modifier for the interface-based clients: ITrace obj = new B(); obj.TraceSelf();//Traces "B"
In general, I prefer code such as Listing 6 using the override visibility modifier with virtual interface members at the top-most base class. Such code is readable and straightforward. Code such as Listing 7 makes for an interesting exercise, but rarely is of practical use.
| & | | .NET Interfaces and COM IIDs
.NET does not use IIDs to uniquely identify an interface. In .NET, an interface, like any other type, is identified by its name, its namespace, its assembly name, the assembly strong name (if one is provided) and the assembly version. .net always loads for a .NET client a compatible .NET assembly. COM uses IID as a way of enforcing versioning. The COM prime directive is that if you change an interface, you need to change the IID as well. When exporting a .NET assembly to COM, COM does not inspect the assembly version number because all it knows about are IIDs. To support COM interoperation, when exporting a .NET assembly to COM, .NET generates IIDs for the exported interfaces based on a deterministic hash of the assembly version and the interface methods, so that in effect, a change to an interface (or the assembly version) manifests itself as a new COM IID. When you re-export an assembly to COM, you will get new IIDs if the interfaces (or the version) were changed, so that existing COM clients will not be affected. You can alsu use the [GUID] attribute to explicitly assign an IID to a .NET interface, and should always have .NET use that IID when exporting an assembly to COM. |