You almost had it (in theory):
public class Point { public virtual double sideA, sideB, radius, height; }
public class RightTriangle:Point { public override double sideA, sideB; }
To make properties and methods overridable in derived classes they have to be declared virtual
in the base class. The overriding class has to declare the overriden property/method as override
.
In practice you cannot make fields virtual
, only properties and methods can be. So you have to change your code as follows:
public class Point {
public virtual double sideA { get; set; }
public virtual double sideB { get; set; }
public virtual double radius { get; set; }
public virtual double height { get; set; }
}
public class RightTriangle:Point {
public override double sideA { get; set; }
public override double sideB { get; set; }
}
However, you should not do this as it is very bad design. More below.
So what's the difference to new
?
When a method is overriden it will be decided at runtime whether the overridden or the original implementation will be called.
So when a method receives a Point
as argument, that Point
instance might actually be a RightTriangle
or a Circle
.
Point p1 = new RightTriangle();
Point p2 = new Circle();
In the above example, both p1
and p2
are Point
s from the perspective of the code that uses p1
and p2
. However, "underneath" they are actually instances of the derived classes.
So with my solution, when you acess p1.sideA
for example, the the runtime will look "underneath" and check what the Point
really is: Is it actually a RightTriangle
? Then check if there is an overridden implmentation of sideA
and call that one. Is it actually a Circle
? Then do the same check (which will fail) and call the original implmentation of sideA
.
The new
qualifier however does something else. It will not override a method, but create a completely new one (with the same name), that is handled differently at compile time.
So with your solution, the compiler sees that you created a RightTriangle
and stored it in a variable of type Point
. When you now access p1.sideA
for example, the compiler will compile this access in such a way that it reads sideA
from the base class, since the instance variable your are dealing with has the base type.
If you still want to access the new
implementation, then your code using p1
has to cast it to the correct derived type RightTriangle
:
var w = ((RightTriangle)p1).sideA; // Gets correct value.
So what's the problem?
As you may have already noticed now, using new
is not a good solution. The code that uses all kinds of Point
s has to know whether a specific derivate of Point
implements a field with new
or not. Moreover it has to know, when it receives a Point
instance what kind of instance it acutally is underneath. This will lead to lots of very elaborate if
-else
statements that check what kind of point p1
is being dealt with and run the appropriate behvaiour.
Using virtual
and override
alleviates the last problem, but only if the base class implements all methods and properties any derived class implements. Which is basically kind of insane. You tried that and I'm sure you noticed on the way that it doesn't make much sense to give a Point
a sideA
and sideB
and a radius
. How could Point
ever implement those properties meaningfully? And you cannot make them abstract
either, because then a Circle
also had to implement a sideA
and so on.
So how to solve it in a better way?
Think about what you want to do whith this differnet point instances. You collect them together in a list for a reason: they have something in common. Also you iterate over this list doing something with each one of them for a specific reason as well. But what is it you are trying to do exactly? Can you describe it in an abstract way?
Maybe you are getting side lengths and radiuses to calculate the area? Then give Point
a virtual
method GetArea()
and override it in each derived class. A method GetArea()
makes sense on all derived types, although it is implemented differently in each one of them.
You could also make GetArea()
an abstract
method instead of a virtual
one. This means it is not implemented in the base class at all and all derived types are forced to implement it on their own.
Maybe you don't want to handle all Point
s in the list but the RightTriangle
s only? Then the code doing this should only receive the RightTriangle
s:
public void HandleRightTriangles(IEnumerable<RightTriangle> rts)
{
// Can work with sideA and sideB directly here, because we know we only got triangles.
}
Call it with:
// using System.Linq;
HandleRightTriangles(objs.OfType<RightTriangle>());