【问题标题】:Can A subclass be downcast while sent as a parameter to an overloaded function作为参数发送给重载函数时,子类可以向下转换吗
【发布时间】:2019-07-10 18:45:08
【问题描述】:

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

到目前为止,我所做的是 if 对象类型的条件然后转换它。但它似乎不干净。

这些是重载的函数。

        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 类。

【问题讨论】:

    标签: c# oop inheritance polymorphism overloading


    【解决方案1】:

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

    如果你使用:

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

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

    这在另一个问题中有更详细的描述:https://stackoverflow.com/a/40618674/3195477

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


    注意事项:

    我不确定这种类型的dynamic“cast”是否存在任何运行时损失。

    而且,每当我看到这种模式时,我都会怀疑是否可以改进类层次结构。即,是否应该将 WriteBlock 所做的任何事情实际移动到 Block 类中?这可能是“更多的多态性”。同样使用dynamic 可能是一种有点脆弱的方法,因为您可以添加新的Block 派生类型并忘记为它们重载WriteBlock,这可能会导致错误。 (这更多地证明了一些 WriteBlock 应该并入 Block 类本身)。

    例如,将virtual PrepareForWriting() 添加到基类Block 中,该类将返回BlockWritable。那么你只需要一个WriteBlock(BlockWritable data) 来完成写作工作。 BlockWritable 可以是字符串、Json、XML 等。这假设您能够修改 Block 类(您似乎不能)。

    【讨论】:

    • 谢谢。它确实有效!
    • 一般来说我不会使用dynamic,除非你被逼到角落里别无选择。这通常表明我们正在与编译器对抗,而不是与编译器一起工作。这也有点类似于“放弃”并将事物声明为object。在 99% 的正常情况下,当我们处理已知类型时,我们不应该使用 dynamic
    • @ScottHannen 我同意,因此我的 cmets 关于重构类结构。在这种情况下,OP 提到某些类超出了他们的控制范围,因此也许这是一个有效的案例。
    【解决方案2】:

    没有。鉴于此:

    public void WriteBlocks(List<Block> blocks)
    

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

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

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

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

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

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

    因此,BlockWriter 也必须是通用的,以便您可以对不同类型的Block 有不同的实现。注入它可能更有意义。无论哪种方式,您都可能认为您已经“解决了”问题。如果调用WriteBlocks 的类“知道”Block 的类型,那么该方法确定要使用的BlockWriter 的类型可能更有意义。


    正如您在评论中提到的,该列表可能包括不同类型的 Block,而不仅仅是一种。这需要根据Block 的类型返回特定BlockWriter 的方法或类。这意味着运行时类型检查,这并不理想,但如果将它保存在一个地方也不算太糟糕。

    这是一个简单的例子:

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

    (当您调用 Write 方法时,NullBlockWriter 将只是一个不执行任何操作的类。)

    这种类型检查并不理想,但至少可以将其隔离到一个类中。现在您可以创建(或注入)工厂的一个实例,并调用GetBlockWriter,并且该方法中的其余代码仍然不会“知道”关于BlockBlockWriter 的不同类型的任何信息。

    BlockWriter w = new BlockWriter();
    

    会变成

    BlockWriter w = blockWriterFactory.GetBlockWriter(block);
    

    ...然后其余的还是一样的。

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

    【讨论】:

    • 感谢您的回答。但实际上该列表包含不同类型的块,所以我想如果不编辑 Block 类是不可能的。
    • 在这种情况下,您可以有一个方法或类为给定类型的块返回 BlockWriter&lt;T&gt; - 例如,GetBlockWriter(Block block)。该方法将包含不幸的类型检查,如果不支持 Block 的类型,您可能希望返回默认值或 NullBlockWriter。但至少你的其余代码将是多态的。
    • 您的意思是在每个if 语句中使用相同的块编写器吗? (即所有TextBlock)?
    猜你喜欢
    • 2016-06-16
    • 1970-01-01
    • 2017-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-20
    相关资源
    最近更新 更多