I have some logic, which defines and uses some user-defined types, like these:
class Word
{
System.Drawing.Font font; //a System type
string text;
}
class Canvass
{
System.Drawing.Graphics graphics; //another, related System type
... and other data members ...
//a method whose implementation combines the two System types
internal void draw(Word word, Point point)
{
//make the System API call
graphics.DrawString(word.text, word.font, Brushes.Block, point);
}
}
The logic, after doing calculations with the types (e.g. to locate each Word
instance), indirectly uses some System
APIs, for example by invoking the Canvass.draw
method.
I'd like to make this logic independent of the System.Drawing
namespace: mostly, in order to help with unit testing (I think unit tests' output would be easier to verify if the draw
method were drawing to something other than a real System.Drawing.Graphics
instance).
To eliminate the logic's dependency on the System.Drawing
namespace, I thought I'd declare some new interfaces to act as placeholders for the System.Drawing
types, for example:
interface IMyFont
{
}
interface IMyGraphics
{
void drawString(string text, IMyFont font, Point point);
}
class Word
{
IMyFont font; //no longer depends on System.Drawing.Font
string text;
}
class Canvass
{
IMyGraphics graphics; //no longer depends on System.Drawing.Graphics
... and other data ...
internal void draw(Word word, Point point)
{
//use interface method instead of making a direct System API call
graphics.drawText(word.text, word.font, point);
}
}
If I did this, then different assemblies could have different implementations of the IMyFont
and IMyGraphics
interface, for example ...
class MyFont : IMyFont
{
System.Drawing.Font theFont;
}
class MyGraphics : IMyGraphics
{
System.Drawing.Graphics theGraphics;
public void drawString(string text, IMyFont font, Point point)
{
//!!! downcast !!!
System.Drawing.Font theFont = ((MyFont)font).theFont;
//make the System API call
theGraphics.DrawString(word.text, theFont, Brushes.Block, point);
}
}
... however the implementation would need an downcast as illustrated above.
My question is, is there a way to do this without needing a downcast in the implementation? By "this", I mean "defining UDTs like Word
and Canvass
which don't depend on specific concrete System
types"?
An alternative would be abstract UDTs ...
class Word
{
//System.Drawing.Font font; //declared in a subclass of Word
string text;
}
class Canvass
{
//System.Drawing.Graphics graphics; //declared in a subclass of Canvass
//concrete draw method is defined in a subclass of Canvass
internal abstract void draw(Word word, Point point);
}
... but this too would need a downcast in the implementation of the subclass.
I also thought of using the double dispatch idiom, but it depends on naming the various subclasses in the APIs.
Or, if not with interfaces or subclasses, is there some way using delegates?
--Edit:--
There have been two possible answers.
One answer is to use generics, precisely as suggested by 'Sir Lantis' answer below, and as suggested by the blog post to which John Skeet linked. I suspect this would work fine in most scenarios. The down-side from my point of view is that it means introducing TFont
as a template parameter: it isn't only a class like Word
(which contains a Font
instance) which needs to become a generic class (like WordT<TFont>
) ... it's also that any class which contains a WordT<TFont>
(e.g. Paragraph
) now also needs to become generic with a TFont
parameter (e.g. ParagraphT<TFont>
). Eventually, almost every class in the assembly has become a generic class. This does preserve type-safety and avoid the need to downcast ... but it's kind of ugly, and disturbs the illusion of encapsulation (the illusion that 'Font' is an opaque implementation detail).
Another answer is to use a map or dictionary in the user class. Instead of Font
in the reusable library, and instead of an abstract interface, define a 'handle' class like:
public struct FontHandle
{
public readonly int handleValue;
FontHandle(int handleValue)
{
this.handleValue = handleValue;
}
}
Then, instead of downcasting from FontHandle
, keep a Dictionary<int, Font>
instance which maps FontHandle
values to Font
instances.