Как отрефакторить код с приведением типов иерархии классов c проверкой каждого типа через element.GetType == typeof(Class)?

Хочу сократить количество кода. У меня идет добавление классов в один List<абстрактный класс>, при добавлении все хорошо, при выводе я делаю проверку соответствие экземпляра класса с его изночальным, типом а не цепочкой классов, если делать проверку через is, то мне отдаст true, если класс можно привести к его родителю, а это не то что мне надо. Я сделал проверку, но я пишу лишний код, и думаю что его можно сократить.

 if (figure.Count > 0)
            {
                foreach (var item in figure)
                {
                    var i = item.GetType();

                    if (i == typeof(Line))
                    {
                        Line line = item as Line;
                        Console.WriteLine(line.ToString());
                    }

                    else if (i == typeof(Ring))
                    {
                        Ring ring = item as Ring;
                        Console.WriteLine(ring.ToString());
                    }


                    else if (i == typeof(Square))
                    {
                        Square square = item as Square;
                        Console.WriteLine(square.ToString());
                    }


                    else if (i == typeof(Rectangle))
                    {
                        Rectangle rectangle = item as Rectangle;
                        Console.WriteLine(rectangle.ToString());
                    }


                    else if (i == typeof(Circle))
                    {
                        Circle circle = item as Circle;
                        Console.WriteLine(circle.ToString());
                    }


                    else if (i == typeof(Сircumference))
                    {
                        Сircumference circleWithoutArea = item as Сircumference;
                        Console.WriteLine(circleWithoutArea.ToString());
                    }
                };
            }

картинка с иерархией классов


Ответы (3 шт):

Автор решения: tym32167

Как вариант

if (figure.Count > 0)
{
    var types = new[] { typeof(Line),typeof(Ring), 
            typeof(Square), typeof(Rectangle), typeof(Circle), typeof(Сircumference) }; 
    
    foreach (var item in figure)
    {
        var type = item.GetType();          
        if (types.Contains(type)) Console.WriteLine(item.ToString());           
    };
}
→ Ссылка
Автор решения: aepot

В копилочку, как сократить код.

Фрагмент

if (i == typeof(Line))
{
    Line line = item as Line;
    Console.WriteLine(line.ToString());
}

Можно записать вот так с помощью паттерн-матчинга

if (item is Line line)
{
    Console.WriteLine(line);
}

То есть в условии произойдет комбо-действие, одновременно и проверка типа и присваивание значения типизированной переменной. При этом строчка var i = item.GetType() становится лишней.


Не всегда можно завернуть такое в массив (как в ответе @tym32167, но его вариант по-своему хорош и в некоторых случаях именно он и спасает). К тому же вариант с паттерн-матчингом может оказаться быстрее по производительности, чем Contains у массива, но я не проверял.

Проверка if (figure.Count > 0) лишняя, цикл foreach не будет выполняться, если коллекция пуста.

foreach (var item in figure)
{
    switch (item)
    {
        case Line line:
            Console.WriteLine(line);
            break;
        case Ring ring:
            Console.WriteLine(ring);
            break;
        case Square square:
            Console.WriteLine(square);
            break;
        case Rectangle rectangle:
            Console.WriteLine(rectangle);
            break;
        case Circle circle:
            Console.WriteLine(circle);
            break;
        case Сircumference circleWithoutArea:
            Console.WriteLine(circleWithoutArea);
            break;
    }
}

Но это для общего случая, то есть когда в каждом case может быть какой-то свой уникальный код. А для вашего конкретного случая код можно написать так:

foreach (var item in figure)
{
    Console.WriteLine(item);
}

Почему я убрал .ToString(), я предположил, что он у вас переопределен, ну с очень большой вероятностью.

public override ToString()
{
    // ...
}

А Console.WriteLine() принимает в себя тип object, который неявно наследуют все классы, и для вывода в консоль сам внутри вызыват object.ToString(), который у самого object является virtual. Здесь срабатывает эффект под названием полиморфизм, и отрабатывает именно нужный вам переопределенный метод.

→ Ссылка
Автор решения: VladD

В качестве варианта решения @aepot, если вы работаете на современной версии C# (9+)*, подойдёт такой компактный вариант:

foreach (var item in figure)
    if (item is Line or Ring or Square or Rectangle or Circle or Сircumference)
        Console.WriteLine(item);

Такой трюк делает виртуальный вызов ToString через ссылку на базовый класс, и будет работать в нормальном случае, если у вас нету перегруженного ToString() (new). С override будет работать в любом случае.

Есть подозрения, что вам в реальности вовсе не нужна проверка типа, и вам достаточно простого кода с неявным виртуальным вызовом ToString:

foreach (var item in figure)
    Console.WriteLine(item);

Если же вам реально нужно «достучаться» до перегруженного ToString(), можно использовать Console.WriteLine(((dynamic)item).ToString()). Расходами на dynamic по сравнению со скоростью ввода-вывода вполне можно пренебречь.

Но более хорошей идеей является никогда не перегружать ToString, а только переопределять (то есть, нужно использовать override вместо new).


*Если у вас не .NET 5, то можно просто прописать в .csproj-файле <LangVersion>9.0</LangVersion> в верхней PropertyGroup.

→ Ссылка