Может ли подкласс понижаться при отправке в качестве параметра перегруженной функции - PullRequest
2 голосов
/ 10 июля 2019

У меня есть суперкласс с именем Block и еще 3 подкласса. класс, который я хочу реализовать, содержит 3 перегруженных функции, каждая из которых принимает в качестве параметра объект одного из подклассов. Когда я использую одну из этих функций, у меня есть только объект 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.

Ответы [ 2 ]

1 голос
/ 10 июля 2019

Да, возможно использование типа dynamic, который допускает это.

Если вы используете:

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

Это должно вызвать предполагаемую WriteBlock перегрузку.

Более подробно это описано в другом вопросе: https://stackoverflow.com/a/40618674/3195477

А также здесь: перегрузка метода и динамическое ключевое слово в C # .


Предостережения:

Я не уверен, есть ли какие-либо штрафы во время выполнения, связанные с этим типом dynamic «cast».

Кроме того, всякий раз, когда я вижу этот шаблон, у меня возникает вопрос, можно ли улучшить иерархию классов. то есть, что бы WriteBlock будет на самом деле перемещать внутри Block классов? Это может быть «более полиморфным». Также использование dynamic может быть несколько хрупким, так как вы можете добавить новые Block производные типы и забыть о перегруженном WriteBlock для них, что может вызвать ошибку. (Это еще одно доказательство того, что некоторые из WriteBlock должны быть включены в Block сами классы).

Например, добавьте virtual PrepareForWriting() к базовому классу Block, который возвращает BlockWritable. Тогда вам понадобится только один WriteBlock(BlockWritable data), чтобы сделать написание. BlockWritable может быть строкой, Json, XML и т. Д. Это предполагает, что вы можете изменять классы Block (что, по-видимому, невозможно).

1 голос
/ 10 июля 2019

Нет.Учитывая это:

public void WriteBlocks(List<Block> blocks)

единственное, что компилятор знает о каждом элементе в списке, это то, что это Block.Это все, что нужно знать.Вот что делает возможным полиморфизм.Может быть любое количество классов, которые наследуются от Block, но в этом контексте эти различия не имеют значения.

Но если все, что знает компилятор, это то, что каждый элемент является Block, он может 'Не знаю, может ли какой-то отдельный элемент быть TableBlock, TextBlock или каким-либо другим унаследованным типом.Если во время компиляции он не знает, каким будет тип времени выполнения, он не может знать, существует ли даже перегрузка для этого конкретного типа.

Предположим, что то, что вы пытаетесь сделать, может скомпилироватьпотому что у вас есть перегрузка для каждого типа, унаследованного от 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();
    }
}

(A NullBlockWriter будет просто классом, который ничего не делает, когда вы вызываете его метод Write.)

Thisпроверка типа не идеальна, но, по крайней мере, это держит ее изолированной в одном классе.Теперь вы можете создать (или внедрить) экземпляр фабрики и вызвать GetBlockWriter, а остальная часть вашего кода в этом методе все равно не будет «ничего» знать о различных типах Block или BlockWriter.

BlockWriter w = new BlockWriter();

станет

BlockWriter w = blockWriterFactory.GetBlockWriter(block);

... и тогда все остальное будет таким же.

Это самый простой возможный заводской пример.Существуют и другие подходы к созданию такой фабрики.Вы можете сохранить все свои реализации в Dictionary<Type, BlockWriter> и попытаться получить экземпляр, используя block.GetType().

...