What is the difference in the code below between instantiating circle
as type Shape
and rectangle as type Rectangle
?
Shape
is the base class, Circle
and Rectangle
are both derived from Shape
. Each instantiation is commented with what the VS Code debugger shows as the variable information.
I'm not sure if I'm using the right terminology; I've been learning C# for a bit over a month so forgive me if I'm not.
var shape = new Shape(); // shape = {Shape}
Shape circle = new Circle(); // circle [Shape] = {Circle}
Rectangle rectangle = new Rectangle(); // rectangle = {rectangle}
class Shape {}
class Circle : Shape {}
class Rectangle : Shape {}
What is the difference in the code below between instantiating circle
as type Shape
and rectangle as type Rectangle
?
Shape
is the base class, Circle
and Rectangle
are both derived from Shape
. Each instantiation is commented with what the VS Code debugger shows as the variable information.
I'm not sure if I'm using the right terminology; I've been learning C# for a bit over a month so forgive me if I'm not.
var shape = new Shape(); // shape = {Shape}
Shape circle = new Circle(); // circle [Shape] = {Circle}
Rectangle rectangle = new Rectangle(); // rectangle = {rectangle}
class Shape {}
class Circle : Shape {}
class Rectangle : Shape {}
Share
Improve this question
asked Jan 20 at 17:02
akTedakTed
2132 silver badges9 bronze badges
6
|
Show 1 more comment
2 Answers
Reset to default 3The difference is that Circle
and Rectangle
are interchangeable as Shape
, but Rectangle
and Circle
are not interchangeable on their own.
In other words you can create a typed array of Shape
objects and add both Rectangle
and Circle
to it:
Shape[] shapes = new Shape[2];
shapes[0] = circle;
shapes[1] = rectangle;
But your Rectangle
and Circle
classes don't add anything, so it's not very useful.
What you're doing is called inheritance. Your Shape
class is called the base class. And your Circle
and Rectangle
classes are called the derived classes. Inheritance allows you have:
- Shared behavior between objects.
- Override that behavior if needed in the derived classes using
virtual
andoverride
methods. - Add specific/unshared behavior to the derived classes.
Let's say your Shape
class looked like this:
class Shape {
public void Draw() { ... }
}
Now you can implement drawing on screen logic and call rectangle.Draw()
and circle.Draw()
on the array we made above:
foreach (Shape shape in shapes) {
shape.Draw();
}
To finish it off, let's add something specific to Circle:
class Circle : Shape {
public double Radius { get; set; }
}
A Rectangle
doesn't a radius, so it makes sense to derive from Shape
and add our Radius
property in Circle
specifically.
Note that this won't work:
foreach (Shape shape in shapes) {
// will not compile because Radius is only defined in Circle
var radius = shape.Radius;
}
More information: https://www.programiz.com/csharp-programming/inheritance
Let us add some "flesh" to your classes to better see the differences.
abstract class Shape
{
public Point Location { get; set; }
public abstract void Draw(Graphics g);
}
class Circle : Shape
{
public int Radius { get; set; }
public override void Draw(Graphics g)
{
int d = 2 * Radius;
g.DrawEllipse(Pens.Black, Location.X - Radius, Location.Y - Radius, d, d);
}
}
class Rectangle : Shape
{
public int Width { get; set; }
public int Height { get; set; }
public override void Draw(Graphics g)
{
g.DrawRectangle(Pens.Black, Location.X, Location.Y, Width, Height);
}
}
And let us also use them in a little example. I assume that we will put this code into a Window Forms Form:
private List<Shape> Shapes = new() {
new Circle { Location = new Point(250, 250), Radius = 140 },
new Rectangle { Location = new Point(200, 100), Width = 160, Height = 135 }
};
private void Form1_Paint(object sender, PaintEventArgs e)
{
foreach (Shape shape in Shapes) {
shape.Draw(e.Graphics);
}
}
Now, to the explanation. A Shape
class has no specific geometrical shape defined. It may have properties that apply to all possible shapes like border color, fill color and border width. In this example I added only a Location
property.
It is also useful to add it a Draw
method that will be inherited and available for all derived shapes; however, we don't know what to draw. Therefore I made the Shape
class and the Draw
method abstract
. This means that we cannot instantiate a Shape
class with new Shape()
.
But we can derive classes that derive from Shape
. These class must implement the abstract method, unless they are abstract themselves.
The Circle
class has a Radius
property, the Recangle
has Width
and Height
properties. They both implement (override) the Draw
method to draw themselves.
In the usage example I have created a list of shapes. We cannot add it a Shape
instance (because Shape
is abstract
), but Circle
and Rectangle
instances.
In the foreach
-loop we can call the Draw
method and we could also access the Location
property, however we cannot access the Radius
, Width
and Height
properties. But we don't need to, since the different Draw
implementations are doing this for us. This behavior is called polymorphism and is a very powerful instrument in object oriented programming.
You asked "What is the difference [...] between instantiating circle as type Shape and rectangle as type Rectangle?". Well, you cannot instantiate a Circle
as Shape
, but you can assign a Circle
instance to a Shape
variable. "Instantiate" is what you do with the new
keyword. Note that this does not change the circle in any way, but through the Shape shape
variable you can see only the Shape
members (properties, methods, events, etc.). The class members introduced in the derived classes are still there, but not directly accessible. This is like looking through red glasses. You see only the red colors around you, but this does not change the real colors of your surrounding.
Why should we assign a Circle
or Rectangle
to a Shape
variable and lose access to some of its class members? It makes sense, if the Shape
variable can be a Circle
or a Rectangle
, because then we can perform actions on it that apply to all possible shapes without having to know what shape they are (as you can see in Form_Paint
).
Circle
specific methods you'd have no access to them from thecircle
variable in the remainder of your code. You just get theShape
methods.rectangle
variable gets bothRectangle
methods andShape
methods. – Ivan Petrov Commented Jan 20 at 17:07Circle
instance to aShape
variable is called polymorphism. The third line is the same as the first// rectangle {Rectangle} = {Rectangle}
– Sinatr Commented Jan 20 at 17:23var
you'd get aCircle
reference, notShape
. It might make sense ifShape
was an interface andCircle
implemented it via EIMIsIShape.Method()
..but that's a bit too advanced I think. – Ivan Petrov Commented Jan 20 at 17:24