views:

610

answers:

4

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.

+2  A: 

First, I wonder if the entire scenario isn't a little artificial; are you really going to need this level of abstraction? Perhaps subscribe to YAGNI?

Why does your MyGraphics only work with a MyFont? Can it work with an IFont? That would be a better use of interfaces, and would avoid this entire issue...

One option might be a bit of a re-design, so that the IFont just describes the metadata for the font (size, font-face, etc), and you have things on the concrete MyGraphics like:

[public|internal] MyFont GetFont(IFont font) {...} // or just Font

and it becomes the job of the graphics to do the translation - so then used something like:

public void drawString(string text, IMyFont font, Point point)
{
    using(System.Drawing.Font theFont = GetFont(font))
    {
        theGraphics.DrawString(word.text, theFont, Brushes.Block, point);
    }
    // etc
}

Of course, Point might need translation too ;-p

Marc Gravell
Ultimately I want to make a System API call: the DrawString method of the System.Drawing.Graphics class takes a System.Drawing.Font instance as a parameter.
ChrisW
See my update for more clarification...
Marc Gravell
"are you really going to need this level of abstraction" ... I'm trying to adapt the "humble dialog box" unit-testing idiom to testing a custom control: I want to unit-test the logic, but it's easier for the test to assert the output if the output is to something other than ...
ChrisW
... a System.Drawing.Graphics instance (for example, if instead the output is to a two-dimensional in-memory array of characters).
ChrisW
If the GetFont method actually constructed a Font instance from font metadata then it could be expensive at run-time ... but that does point to one solution: which could be to have a 'map' or 'dictionary' in which MyFontHandle instances are keys, which map to corresponding Font instances.
ChrisW
+2  A: 

You're effectively saying "I know better than the compiler - I know that it's bound to be an instance of MyFont." At that point you've got MyFont and MyGraphics being tightly coupled again, which reduces the point of the interface a bit.

Should MyGraphics work with any IFont, or only a MyFont? If you can make it work with any IFont you'll be fine. Otherwise, you may need to look at complicated generics to make it all compile-time type safe. You may find my post on generics in Protocol Buffers useful as a similar situation.

(Side suggestion - your code will be more idiomatically .NET-like if you follow the naming conventions, which includes Pascal case for methods.)

Jon Skeet
I'll edit to replace 'upcast' with 'downcast'.
ChrisW
Righto. Will edit the answer appropriately when you've done so :)
Jon Skeet
A: 

You don't like the cast from IFont to MyFont? You can do this:

interface IFont {
    object Font { get; }
}

class MyFont : IFont {
    object Font { get { return ...; } }
}

Sure you still need to cast from System.Object to System.Drawing.Font in the drawing method, but you've just eliminated the dependency on particular class implementation (MyFont).

public void DrawString(string text, IFont font, Point point)
{
    System.Drawing.Font f = (Font)font.Font;
    graphics.DrawString(text, f, Brushes.Block, point);
}
arul
If I did this then IFont would depend on Font and my logic, which depends on IFont, would thus implicitly also depend on Font (and therefore couldn't be used with an ICanvass and IFont which don't use the System.Drawing types in their implementation).
ChrisW
No, IFont depends on System.Object, it doesn't know and doesn't care what is stored in the 'Font' variable.
arul
I see now, you're right. However, as you said, there's still a downcast (from object to Font).
ChrisW
+1  A: 

I am currently not quite aware of C# anymore - has been some time now. But if you do not want to cast all your stuff there, you might be forced to use generics.

I can just provide Java code, but C# should be able to do the same via the where keyword.

Make your Interface a generic interface. In Java that would be

IMyGraphics<T extends IMyFont> and then MyGraphics : IMyGraphics<MyFont>

Then redefine the drawString Signature to take T font as the second parameter instead of IMyFont. This should enable you to write

public void drawString(string text, MyFont font, Point point)

directly into your MyGraphics class.


In C# that IMyGraphics<T extends IMyFont> should be public interface IMyGraphics<T> where T:IMyFont, but I am not 100% sure about that.

Marcel J.
It looks like this is a good answer. I'll try it.
ChrisW