I have a weird design situation that I've never encountered before... If I were using Objective-C, I would solve it with categories, but I have to use C# 2.0.
First, some background. I have two abstraction layers in this class library. The bottom layer implements a plug-in architecture for components that scan content (sorry, can't be more specific than that). Each plug-in will do its scanning in some unique way, but also the plug-ins can vary by what type of content they accept. I didn't want to expose Generics through the plug-in interface for various reasons not relevant to this discussion. So, I ended up with an IScanner interface and a derived interface for each content type.
The top layer is a convenience wrapper that accepts a composite content format that contains various parts. Different scanners will need different parts of the composite, depending on what content type they are interested in. Therefore, I need to have logic specific to each IScanner-derived interface that parses the composite content, looking for the relevant part that is required.
One way to solve this is to simply add another method to IScanner and implement it in each plug-in. However, the whole point of the two-layer design is so that the plug-ins themselves don't need to know about the composite format. The brute-force way to solve this is by having type-tests and downcasts in the upper layer, but these would need to be carefully maintained as support for new content types is added in the future. The Visitor pattern would also be awkward in this situation because there really is only one Visitor, but the number of different Visitable types will only increase with time (i.e. -- these are the opposite conditions for which Visitor is suitable). Plus, double-dispatch feels like overkill when really all I want is to hijack IScanner's single-dispatch!
If I were using Objective-C, I would just define a category on each IScanner-derived interface and add the parseContent method there. The category would be defined in the upper layer, so the plug-ins wouldn't need to change, while simultaneously avoiding the need for type tests. Unfortunately C# extension methods wouldn't work because they are basically static (i.e. -- tied to the compile-time type of the reference used at the call site, not hooked into dynamic dispatch like Obj-C Categories). Not to mention, I have to use C# 2.0, so extension methods are not even available to me. :-P
So is there a clean and simple way to solve this problem in C#, akin to how it could be solved with Objective-C categories?
EDIT: Some pseudo-code to help make the structure of the current design clear:
interface IScanner
{ // Nothing to see here...
}
interface IContentTypeAScanner : IScanner
{
void ScanTypeA(TypeA content);
}
interface IContentTypeBScanner : IScanner
{
void ScanTypeB(TypeB content);
}
class CompositeScanner
{
private readonly IScanner realScanner;
// C-tor omitted for brevity... It takes an IScanner that was created
// from an assembly-qualified type name using dynamic type loading.
// NOTE: Composite is defined outside my code and completely outside my control.
public void ScanComposite(Composite c)
{
// Solution I would like (imaginary syntax borrowed from Obj-C):
// [realScanner parseAndScanContentFrom: c];
// where parseAndScanContentFrom: is defined in a category for each
// interface derived from IScanner.
// Solution I am stuck with for now:
if (realScanner is IContentTypeAScanner)
{
(realScanner as IContentTypeAScanner).ScanTypeA(this.parseTypeA(c));
}
else if (realScanner is IContentTypeBScanner)
{
(realScanner as IContentTypeBScanner).ScanTypeB(this.parseTypeB(c));
}
else
{
throw new SomeKindOfException();
}
}
// Private parsing methods omitted for brevity...
}
EDIT: To clarify, I have thought through this design a lot already. I have many reasons, most of which I cannot share, for why it is the way it is. I have not accepted any answers yet because although interesting, they dodge the original question.
The fact is, in Obj-C I could solve this problem simply and elegantly. The question is, can I use the same technique in C# and if so, how? I don't mind looking for alternatives, but to be fair that isn't the question I asked. :)