在将子类作为参数发送到重载函数时,是否可以对其进行下转换

时间:2019-07-10 18:45:08

标签: c# oop inheritance polymorphism overloading

我有一个名为Block的超类和另外3个子类。我要实现的类包含3个重载函数,每个重载函数将子类之一的对象作为参数。使用这些功能之一时,我只有一个Block对象(superClass中的一个对象)。我的问题是选择要调用的函数的最干净方法是什么。

到目前为止,我所做的是如果对象类型的条件随后进行了强制转换。但似乎不干净。

这些是重载的函数。

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

2 个答案:

答案 0 :(得分:1)

不。鉴于此:

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<TextBlock>(listOfTextBlocks),编译器将知道列表中的每个项目都是TextBlock,而不仅仅是Block

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


如您的评论中所述,列表可能包括不同类型的Block,而不仅仅是一种。这需要根据BlockWriter的类型返回特定的Block的方法或类。这意味着运行时类型检查,这不是理想的选择,但是如果将其放在一个位置也不会太糟糕。

这是一个简单的例子:

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只是当您调用其Write方法时不执行任何操作的类。)

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

BlockWriter w = new BlockWriter();

将成为

BlockWriter w = blockWriterFactory.GetBlockWriter(block);

...然后其余部分将保持不变。

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

答案 1 :(得分: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类(看来您不能)。