Я совершенно не знаю, как назвать то, что хочу делать (отсюда и трудности, с которыми я сталкиваюсь, пытаясь это исследовать). Но у меня есть вот это:
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(), но это, похоже, тоже не работает.
Прежде всего, вы значительно упростите свои методы, просто используя nameof для получения имени класса.
@JonasH, здесь нельзя использовать nameof
— точнее, он делает что-то другое; существующий код извлекает имя конкретного типа, которое nameof
не может знать; поскольку Square
и Circle
не являются sealed
, возможно, это не они
Возможно, вы захотите рассмотреть структуру посетителей . Поскольку это может позволить вам реализовать методы рисования для каждого типа фигур без необходимости зависеть от какой-либо конкретной библиотеки рисования.
(Примечание. В этом ответе предполагается, что вы действительно хотите иметь разные реализации 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 тысяч человек в .... Голландии!
@PanagiotisKanavos Я сказал им не использовать мое имя во время операции по отвлечению внимания... правдоподобное отрицание и т. д.
Спасибо, похоже, это сработало для меня. На самом деле я думал, что вы не можете предоставить базовый логический код для интерфейсов (хотя я думаю, что именно это вы имели в виду под «целевой структурой»), поскольку весь смысл, как я думал, заключался в том, чтобы по сути написать «скелет» для списка общих компонентов. что-то должно было быть реализовано, чтобы стать «IShape».
@john да, это работает только в ... Я не проверял, но хочу сказать, что net6 и выше?
Используйте общий метод расширения. Это также позволяет вам использовать 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 (остальная часть кода была такой же, как и выше, которую я поместил в свой исходный пост) для обоих Круг и Квадрат.
Если вам нужен тип среды выполнения, вам нужно использовать GetType()
. Я обновил ответ.
если у вас есть какая-то общая функциональность, создайте (абстрактный) базовый класс. Так что да, какой-нибудь
Shape
-класс вполне возможен. Хотя нет нужды размышлять. Полиморфизм — это использование наиболее конкретного переопределения функции.