Наследование функций C# и использование возможности повторного использования

Я совершенно не знаю, как назвать то, что хочу делать (отсюда и трудности, с которыми я сталкиваюсь, пытаясь это исследовать). Но у меня есть вот это:

public interface IShapes
{
    public void Draw();
}
public class Square : IShapes
{
    public void Draw()
    {
        Console.WriteLine(GetType().FullName?.Substring(GetType().FullName.IndexOf('.') + 1) + " - Draw");
    }
}
public class Circle : IShapes
{
    public void Draw()
    {
    Console.WriteLine(GetType().FullName?.Substring(GetType().FullName.IndexOf('.')+1) + " - Draw");
    }
}

Я хочу избавиться от дублирования кода, изменив интерфейс на что-то подходящее, например, на класс, а затем используя отражение для вызова правильного (т. е. «самого» дочернего наследования) метода Draw.

Итак, в другом месте я мог бы сделать вызов типа (чтобы следовать примеру кода выше):

var shapes = new List<IShapes>();
shapes.Add(new Circle());
shapes.Add(new Square());

shapes.ForEach((s) => 
{
s.Draw();
});

В результате получится «Круг — ничья» и «Квадрат — ничья».

Я попытался изменить интерфейс на класс (и абстрактный), но чувствую, что упускаю что-то очевидное.

Я также пытался в классах Square и Circle: Shapes определить новые общедоступные методы void Draw(), которые напрямую вызывают base.Draw(), но это, похоже, тоже не работает.

если у вас есть какая-то общая функциональность, создайте (абстрактный) базовый класс. Так что да, какой-нибудь Shape-класс вполне возможен. Хотя нет нужды размышлять. Полиморфизм — это использование наиболее конкретного переопределения функции.

MakePeaceGreatAgain 18.07.2024 11:08

Прежде всего, вы значительно упростите свои методы, просто используя nameof для получения имени класса.

JonasH 18.07.2024 11:09

@JonasH, здесь нельзя использовать nameof — точнее, он делает что-то другое; существующий код извлекает имя конкретного типа, которое nameof не может знать; поскольку Square и Circle не являются sealed, возможно, это не они

Marc Gravell 18.07.2024 11:12

Возможно, вы захотите рассмотреть структуру посетителей . Поскольку это может позволить вам реализовать методы рисования для каждого типа фигур без необходимости зависеть от какой-либо конкретной библиотеки рисования.

JonasH 18.07.2024 11:45
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
110
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

(Примечание. В этом ответе предполагается, что вы действительно хотите иметь разные реализации Draw() для производных классов. Если вам не нужно этого делать, вы можете использовать один из других ответов на свой вопрос!)

Вы можете ввести промежуточный базовый класс abstract, который реализует функциональность:

public interface IShapes
{
    public void Draw();
}

public abstract class ShapesBase : IShapes
{
    public virtual void Draw()
    {
        Console.WriteLine(GetType().FullName?.Substring(GetType().FullName.IndexOf('.') + 1) + " - Draw");
    }
}

public class Square : ShapesBase
{
    public override void Draw()
    {
        base.Draw();
    }
}

public class Circle : ShapesBase
{
    public override void Draw()
    {
        base.Draw();
    }
}

Обратите внимание: если вы переопределите метод Draw(), вы ДОЛЖНЫ помнить о необходимости вызова базового метода, иначе вы не получите желаемый результат (я предполагаю, что ваши конкретные классы обычно захотят реализовать Draw().

Если вы вообще опустите реализации Draw() из конкретных классов, это все равно будет работать:

public interface IShapes
{
    public void Draw();
}

public abstract class ShapesBase : IShapes
{
    public virtual void Draw()
    {
        Console.WriteLine(GetType().FullName?.Substring(GetType().FullName.IndexOf('.') + 1) + " - Draw");
    }
}

public class Square : ShapesBase
{
}

public class Circle : ShapesBase
{
}

Но обычно вы переопределяете виртуальные методы.


Кстати, если вы хотите заставить производные классы реализовать какой-то метод Draw(), вы можете использовать другой подход. Вы можете ввести абстрактный метод DrawImpl(), который должны реализовать производные классы и который вызывается из конкретной реализации Draw() абстрактного базового класса.

Этот подход также имеет то преимущество, что производным классам не нужно помнить о вызове версии базового класса:

public interface IShapes
{
    public void Draw();
}

public abstract class ShapesBase : IShapes
{
    protected abstract void DrawCore();

    public void Draw()
    {
        Console.WriteLine(GetType().FullName?.Substring(GetType().FullName.IndexOf('.') + 1) + " - Draw");
        DrawCore();
    }
}

public class Square : ShapesBase
{
    protected override void DrawCore()
    {
        Console.WriteLine("Square.DrawCore()");
    }
}

public class Circle : ShapesBase
{
    protected override void DrawCore()
    {
        Console.WriteLine("Circle.DrawCore()");
    }
}

Этот подход также работает, если вы хотите сделать переопределение метода DrawCore() необязательным, но при этом получить желаемый результат, просто объявив DrawCore() как virtual вместо abstract:

public interface IShapes
{
    public void Draw();
}

public abstract class ShapesBase : IShapes
{
    protected virtual void DrawCore()
    {
        // Do nothing.
    }

    public void Draw()
    {
        Console.WriteLine(GetType().FullName?.Substring(GetType().FullName.IndexOf('.') + 1) + " - Draw");
        DrawCore();
    }
}

public class Square : ShapesBase
{
}

public class Circle : ShapesBase
{
}

Это может быть полезно, если необходимо переопределить DrawCore() только для некоторых производных классов.

Ответ принят как подходящий

В зависимости от вашей целевой платформы вам могут подойти «методы интерфейса по умолчанию»:

using System;
public interface IShapes
{
    public void Draw()
    {
    
    Console.WriteLine(GetType().FullName?.Substring(GetType().FullName.IndexOf('.') + 1) + " - Draw");
    }
}
public class Square : IShapes {}
public class Circle : IShapes {}

Я собирался ответить на этот вопрос, когда получил два спам-звонка: один местный и один из города с населением около 60 тысяч человек в .... Голландии!

Panagiotis Kanavos 18.07.2024 11:19

@PanagiotisKanavos Я сказал им не использовать мое имя во время операции по отвлечению внимания... правдоподобное отрицание и т. д.

Marc Gravell 18.07.2024 15:08

Спасибо, похоже, это сработало для меня. На самом деле я думал, что вы не можете предоставить базовый логический код для интерфейсов (хотя я думаю, что именно это вы имели в виду под «целевой структурой»), поскольку весь смысл, как я думал, заключался в том, чтобы по сути написать «скелет» для списка общих компонентов. что-то должно было быть реализовано, чтобы стать «IShape».

John Barber 18.07.2024 17:43

@john да, это работает только в ... Я не проверял, но хочу сказать, что net6 и выше?

Marc Gravell 18.07.2024 18:18

Используйте общий метод расширения. Это также позволяет вам использовать typeof<T> вместо this.GetType(), что разрешается во время компиляции. Кроме того, я почти уверен, что вы можете просто использовать Name (вместо синтаксического анализа FullName), который дает неполное имя.

static class ShapeExtensions
{
    static void Draw<T>(this T shape) where T : IShapes
    {
        Console.WriteLine(typeof(T).Name + " - Draw");
    }
}

Обратите внимание, что typeof(T) даст вам только тип переменной (который определяется во время компиляции), а не экземпляр (который определяется во время выполнения). Итак, если у вас есть List<IShapes>, он всегда будет печатать IShapes - Draw. Если вам нужен тип среды выполнения экземпляра, вы все равно можете использовать GetType().

static void Draw<T>(this T shape) where T : IShapes
{
    Console.WriteLine(shape.GetType().Name + " - Draw");
}

Вы называете это точно так же.

shapes.ForEach((s) => 
{
    s.Draw();
});

Похоже, у меня это не сработало так, как я предполагал, по крайней мере, когда я вызвал метод s.Draw, я только что получил IShapes - Draw (остальная часть кода была такой же, как и выше, которую я поместил в свой исходный пост) для обоих Круг и Квадрат.

John Barber 18.07.2024 17:40

Если вам нужен тип среды выполнения, вам нужно использовать GetType(). Я обновил ответ.

John Wu 18.07.2024 21:19

Другие вопросы по теме