An interface isn’t limited only to defining methods. An interface can also define properties, indexers, and events. Example 3-7 shows the syntax for defining all of these in an interface and the corresponding implementation.
Example 3-7. An interface can define methods, properties, indexers, and events
public delegate void NumberChangedEventHandler(int number);
public interface IMyInterface { void Method1(); //A method int SomeProperty{ get; set; }//A property int this[int index]{ get; set;}//An indexer event NumberChangedEventHandler NumberChanged;//An event }
public class MyClass : IMyInterface { public event NumberChangedEventHandler NumberChanged;
public void Method1() {...} public int SomeProperty { get {...} set {...} } public int this[int index] { get {...} set {...} } }
Interfaces and Structs
An interesting use of interfaces with properties involves structs. In .NET, a struct (a Structure in Visual Basic 2005) can’t have a base struct or a base class, because it’s a value type. However, .NET does permit structs to implement one or more interfaces. The reason for this is that sometimes you want to define abstract data storage, and there are a number of possible implementations for the actual structure. By defining an interface (preferably with properties only, but it can have methods as well), you can pass around the interface instead of the actual struct and gain the benefits of polymorphism, even though structs aren’t allowed to derive from a common base struct. Example 3-8 demonstrates the use of an interface (with properties only) as a base type for structs.
Example 3-8. Using an interface as a base type for structs
public interface IMyBaseStruct { int SomeNumber{ get; set; } string SomeString{ get; set; } }
struct MyStruct : IMyBaseStruct { public int SomeNumber { get{...} set{...} } public string SomeString { get{...} set{...} } //Rest of the implementation } struct MyOtherStruct : IMyBaseStruct { public int SomeNumber { get{...} set{...} } public string SomeString { get{...} set{...} } //Rest of the implementation } //A method that accepts a struct, without knowing exactly the type public void DoWork(IMyBaseStruct storage) {...}
Partial types allow the component architect to define interface derivation for a class but have another developer implement it (similar to the old C++ distinction between header files and CPP files):
A class can derive from as many interfaces as required (see Example 3-5), but from at most one base class. When a class derives from a base class and from one or more interfaces, the base class must be listed first in the derivation chain (a requirement the compiler enforces):
public interface IMyInterface {} public interface IMyOtherInterface {} public class MyBaseClass {} public class MySubClass : MyBaseClass,IMyInterface,IMyOtherInterface {}
Even such a trivial example raises a number of questions. What if both interfaces define identical methods? What are the available ways to resolve such collisions? What if the base class already derives from one or more of the interfaces?
When a class derives 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, and the second is to provide separate method implementations. For example, consider two interfaces that define the identical methodMethod1():
public interface IMyInterface { void Method1(); } public interface IMyOtherInterface { void Method1(); }
If you want to channel both interface methods to the same method implementation, 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 ofMyClasschooses to use, calls toMethod1()will be channeled to that single implementation:
IMyInterface obj1; IMyOtherInterface obj2;
obj1 = new MyClass(); obj1.Method1();
obj2 = obj1 as IMyOtherInterface; Debug.Assert(obj2 != null); obj2.Method1();
To provide separate implementations, use explicit interface implementation by qualifying the method implementation with the name of 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. You can even have separate explicit implementations for some of the common methods and channel the others to the same implementation. However, as mentioned before, for the sake of consistency it’s better to avoid mixing and matching.
If you want to both use explicit interface implementation and channel the implementation from one interface to the other, you will need to use thethisreference to query for the desired interface and delegate the call:
public class MyClass : IMyInterface, IMOtherInterface { void IMyInterface.Method1 ( ) {...} void IMyOtherInterface.Method1 ( ) { IMyInterface myInterface = this; myInterface.Method ( ); } //Other methods and members }
Using thethis reference this way is the only way to call an explicit interface method by its own implementing class.
In component-oriented programming, you focus on defining and implementing interfaces. In object-oriented programming, you model your solution by using class hierarchies. 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 Example 3-9, which illustrates that when defining an interface only at the root of a class hierarchy, each level must override its base-class declarations to preserve semantics.
Example 3-9.Overriding an interface in a class hierarchy
using System.Diagnostics;//For the Trace class
public interface ITrace { void TraceSelf(); } public class A : ITrace { public virtual void TraceSelf() { Trace.WriteLine("A"); } } public class B : A { public override void TraceSelf() { Trace.WriteLine("B"); } } public class C : B { public override void TraceSelf() { Trace.WriteLine("C"); } }
In a typical class hierarchy, the topmost base class should derive from the interface, providing polymorphism with the interface to all subclasses. The topmost base class must also define all the interface members asvirtual, so that subclasses can override them. Each level of the class hierarchy can override its preceding level (using theoverride inheritance qualifier), as shown in Example 3-9. When the client uses the interface, it then gets the desired interpretation of the interface. For example, if the client code is:
C# Inheritance Directives
In C#, you are required to explicitly indicate the semantics of inheritance when you supply a method with a name and signature identical to those of a base-class method. If you wish to override the base-class method when instantiating a base-class type with a subclass reference, you need to use the override directive:
public class BaseClass { public virtual void TraceSelf() { Trace.WriteLine("BaseClass"); } } public class SubClass : BaseClass { public override void TraceSelf() { Trace.WriteLine("SubClass"); } } BaseClass obj = new SubClass(); obj.TraceSelf(); //Outputs "SubClass"
If you want to provide the base-class behavior instead, use thenewdirective, with or withoutvirtual, at the base class:
public class BaseClass { public virtual void TraceSelf() { Trace.WriteLine("BaseClass"); } } public class SubClass : BaseClass { public new void TraceSelf() { Trace.WriteLine("SubClass"); } } BaseClass obj = new SubClass(); obj.TraceSelf(); //Outputs "BaseClass "
ITrace obj = new B(); obj.TraceSelf();
the object traces “B” to the output window, as expected.
Things are less obvious if the subclasses use thenewinheritance qualifier. Thenewmodifier gives subclass behavior only 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 Example 3-9 was written as:
public class A : ITrace { public virtual void TraceSelf()//virtual is optional { 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 this 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 thenew inheritance modifier is available. Imagine a client that somehow depends on the base class’s particular implementation. If a new subclass is used instead of the base class, thenewmodifier ensures that the client will get the implementation it expects. However, this nuance makes sense only when you’re dealing with clients that don’t use interface-based programming but rather program directly against the objects:
A obj = new B(); obj.TraceSelf();//Traces "A"
You can support such clients and still provide interface-based services to the rest of the clients. To achieve that, each class in the hierarchy can reiterate its polymorphism with the interface by explicitly deriving from the interface (in addition to having the base class derive from the interface). Doing so (as shown in Example 3-10) makes thenewmodifier yield the same results as theoverridemodifier for the interface-based clients:
ITrace obj = new B(); obj.TraceSelf();//Traces "B"
Note that usingvirtualat the base-class level is optional.
In general, you should use theoverridemodifier, as in Example 3-9, with virtual interface members at the topmost base class. Such code is readable and straightforward. Code such as that in Example 3-10 makes for an interesting exercise but is rarely of practical use.
Example 3-10. Deriving from the interface explicitly at each level of the class hierarchy
using System.Diagnostics;//For the Trace class
public interface ITrace { void TraceSelf(); } public class A : ITrace { public virtual void TraceSelf()//virtual is optional { Trace.WriteLine("A"); } } public class B : A,ITrace { public new void TraceSelf() { Trace.WriteLine("B"); } } public class C : B,ITrace { public new void TraceSelf() { Trace.WriteLine("C"); } }
If you want to combine explicit interface implementation and class hierarchy, you should do so in a way that allows a subclass to call its base-class implementation. Because with explicit interface implementation, the implementation is private, you will need to add at the topmost base class a protected virtual method for each interface method. Only the topmost base class should explicitly implement the interface, and its implementation should call the protected virtual methods. All the subclasses should override the protected virtual methods:
public class A : ITrace { protected virtual void TraceSelf() { Trace.WriteLine("A"); } void ITrace.TraceSelf() { TraceSelf(); } } public class B : A { protected override void TraceSelf() { Trace.WriteLine("B"); base.TraceSelf(); } }
Please check back next week for the continuation of this article.