No. Given this:
public void WriteBlocks(List<Block> blocks)
the only thing the compiler knows about each item in the list is that it is a Block
. That's all it should know. That's what makes polymorphism possible. There can be any number of classes that inherit from Block
, but within this context those distinctions don't matter.
But if all the compiler knows is that each item is a Block
, it can't know whether any individual item might be a TableBlock
, TextBlock
, or some other inherited type. If, at compile time, it doesn't know what the runtime type will be, it can't know whether there even is an overload for that specific type.
Suppose what you're trying to do could compile, because you have an overload for every type that inherited from Block
. What would or should happen if you added a new type - class PurpleBlock : Block
- and there was no overload for it? Should this no longer compile just because you added a new type?
If the method that calls WriteBlocks
knows what sort of Block
is in the list, then it can supply that information:
public void WriteBlocks<TBlock>(List<TBlock> blocks) where TBlock : Block
Now you can call WriteBlock<TextBlock>(listOfTextBlocks)
and the compiler will know that each item in the list is a TextBlock
, not just a Block
.
It follows, then, that BlockWriter
would have to be generic also so that you could have different implementations for different types of Block
. It might make more sense to inject it. Either way, you're likely to perceive that you've "moved" the problem. If the class that calls WriteBlocks
"knows" the type of the Block
, then it might make more sense for that method to determine the type of BlockWriter
to use.
As mentioned in your comment, the list might include different types of Block
, not just one. That requires either a method or a class that returns a specific BlockWriter
depending on the type of Block
. That means runtime type-checking, which isn't ideal, but it's not too bad if you keep it in one place.
Here's a simple example:
public class BlockWriterFactory
{
public BlockWriter GetBlockWriter(Block block)
{
if (block is TextBlock)
return new TextBlockWriter();
if (block is TableBlock)
return new TableBlockWriter();
if (block is ListBlock)
return new ListBlockWriter();
// this could be a "null" class or some fallback
// default implementation. You could also choose to
// throw an exception.
return new NullBlockWriter();
}
}
(A NullBlockWriter
would just be a class that does nothing when you call its Write
method.)
This sort of type-checking isn't ideal, but at least this keeps it isolated into one class. Now you can create (or inject) an instance of the factory, and call GetBlockWriter
, and the rest of your code in that method still wouldn't "know" anything about the different types of Block
or BlockWriter
.
BlockWriter w = new BlockWriter();
would become
BlockWriter w = blockWriterFactory.GetBlockWriter(block);
...and then the rest would still be the same.
That's the simplest possible factory example. There are other approaches to creating such a factory. You could store all of your implementations in a Dictionary<Type, BlockWriter>
and attempt to retrieve an instance using block.GetType()
.