将子类作为参数发送给重载函数时可以向下转换吗

Can A subclass be downcast while sent as a parameter to an overloaded function

我有一个名为 Block 的超类和另外 3 个子类class。我要实现的 class 包含 3 个重载函数,每个函数都将子 class 之一的对象作为参数。当我使用其中一个函数时,我只有一个 Block 对象(来自超类的对象)。我的问题是选择要调用的函数的最简洁方法是什么。

到目前为止,我所做的是根据对象类型的条件进行转换。但看起来不干净。

这些是重载函数。

        public void WriteBlock(TableBlock block) { }
        public void WriteBlock(TextBlock block) { }
        public void WriteBlock(ListBlock block) { }

这就是我要实现的功能。

        public void WriteBlocks(List<Block> blocks)
        {
            BlockWriter w = new BlockWriter();

            foreach (var block in blocks)
            {
                w.WriteBlock(block);
            }
        }

请注意,我无法访问 Blocks classes。

没有。鉴于此:

public void WriteBlocks(List<Block> blocks)

关于列表中的每一项,编译器唯一知道的是它是一个 Block。这就是它应该知道的全部。这就是使多态性成为可能的原因。可以有任意数量的 class 继承自 Block,但在这种情况下,这些区别并不重要。

但是如果编译器只知道每个项目都是 Block,它就无法知道任何单个项目是否可能是 TableBlockTextBlock 或其他一些继承类型。如果在编译时,它不知道运行时类型是什么,它甚至无法知道该特定类型是否存在重载。

假设您尝试执行的操作可以编译,因为您对从 Block 继承的每个类型都有一个重载。如果您添加了一个新类型 - class PurpleBlock : Block - 并且它没有过载,将会或应该发生什么?这是否应该仅仅因为您添加了一个新类型而不再编译?

如果 调用 WriteBlocks 的方法知道列表中 Block 的类型,那么它可以提供该信息:

public void WriteBlocks<TBlock>(List<TBlock> blocks) where TBlock : Block

现在您可以调用 WriteBlock<TextBlock>(listOfTextBlocks),编译器将知道列表中的每一项都是 TextBlock,而不仅仅是 Block

因此,BlockWriter 也必须是通用的,以便您可以对不同类型的 Block 进行不同的实现。注入它可能更有意义。无论哪种方式,您都可能认为自己 "moved" 遇到了问题。如果调用 WriteBlocks "knows" 的 class 是 Block 的类型,那么该方法确定要使用的 BlockWriter 的类型可能更有意义.


如您的评论所述,该列表可能包括不同类型的 Block,而不仅仅是一种。这需要一种方法或 class 来 returns 一个特定的 BlockWriter,具体取决于 Block 的类型。这意味着运行时 type-checking,这并不理想,但如果将其保存在一个地方也不会太糟糕。

这是一个简单的例子:

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();
    }
}

(一个 NullBlockWriter 只是一个 class,当你调用它的 Write 方法时什么都不做。)

这种 type-checking 并不理想,但至少可以将其隔离为一个 class。现在您可以创建(或注入)工厂的一个实例,并调用 GetBlockWriter,该方法中的其余代码仍然不会 "know" 关于 [=15= 的不同类型的任何内容] 或 BlockWriter.

BlockWriter w = new BlockWriter();

会变成

BlockWriter w = blockWriterFactory.GetBlockWriter(block);

...然后剩下的还是一样。

这是最简单的工厂示例。还有其他方法可以创建这样的工厂。您可以将所有实现存储在 Dictionary<Type, BlockWriter> 中并尝试使用 block.GetType().

检索实例

是的,可以使用允许这样做的 dynamic 类型。

如果您使用:

        foreach (var block in blocks)
        {
            w.WriteBlock(block as dynamic);
        }

它应该调用预期的 WriteBlock 重载。

这在另一个问题中有更详细的描述:

还有这里:method overloading and dynamic keyword in C#.


注意事项:

我不确定是否存在与此类 dynamic "cast" 相关的任何运行时惩罚。

此外,每当我看到这种模式时,我都会想知道是否可以改进 class 层次结构。即,无论 WriteBlock 将做什么,实际上是否应该移动到 Block classes 中?那可能是 "more polymorphic"。此外,使用 dynamic 可能是一种有点脆弱的方法,因为您可以添加新的 Block 派生类型,而忘记为它们重载 WriteBlock,这可能会导致错误。 (这更多地证明了 WriteBlock 中的一些应该合并到 Block classes 本身)。

例如,在 Block class 基础上添加一个 virtual PrepareForWriting(),即 returns 一个 BlockWritable。那么你只需要一个 WriteBlock(BlockWritable data) 就可以完成写作工作。 BlockWritable 可以是字符串,Json、XML 等。这假设您能够修改 Block classes(您似乎不能) .