My task is simple: I have to create a dynamic array (which means that its size can change during all runtime) and fill it (depends on user input, also in runtime) with objects of different types. Moreover, it should be possible for me to access fields (and/or methods) of every object in the array. Obviously, fields are different for each type. Simplified structure:
public class Point {}
public class RightTriangle:Point { public double sideA, sideB; }
public class Circle:Point { public double radius; }
public class Cone:Circle { public double radius, height; }
So, you see: all classes inherit one base class. And I know, these structure kind of illogical, but that's not my choice. So, I want this code to work:
RightTriangle rt1 = new RightTriangle();
Cone cn1 = new Cone();
List<Point> objs = new List<Point>();
objs.Add(rt1);
sideA_tb.Text = objs[0].sideA.ToString();
But it doesn't. Compiler says that there is no sideA
in Point
. However this does not work either:
public class Point { public double sideA, sideB, radius, height; }
public class RightTriangle:Point { public new double sideA, sideB; }
public class Circle:Point { public new double radius; }
public class Cone:Circle { public new double radius, height; }
It seems that values from actual class (rt1
, which is RightTriangle
) do not override ones in Point
class, so I've got something like this:
List<Point> objs = new List<Point>();
objs.Add(rt1); // rt1 has fields 'sideA' = 4, 'sideB' = 5
sideA_tb.Text = rt1.sideA.ToString(); // Got 4, okay
sideA_tb.Text = objs[0].sideA.ToString(); // Got 0, what the?
So, basically, I need a dynamic array of links, and I want to use this links to access objects fields and methods. Also, I know that I can write some Get/Set
functions in base class and override them in child classes, but this does not seems like beauty solution.
Thanks in advance and please forget my illiteracy (English isn't my native).
No, we cannot store multiple datatype in an Array, we can store similar datatype only in an Array.
Passing Array To The Method In Java Arrays can be passed to other methods just like how you pass primitive data type's arguments. To pass an array as an argument to a method, you just have to pass the name of the array without square brackets. The method prototype should match to accept the argument of the array type.
In java, is it possible to create an array or any sort of collection which can hold different data types? Yes.
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.
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.
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.
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>());
RightTriangle rt1 = new RightTriangle();
Cone cn1 = new Cone();
List<Shape> objs = new List<Shape>();
objs.Add(rt1);
sideA_tb.Text = objs[0].sideA.ToString();
This doesn't compile as you are trying to get a sideA
property from the class Shape
, which doesn't exist. Since not all classes have a sideA
, I wouldn't add that property to the Shape
class either, since not all classes use the property of sideA
, it would be bad OO design to put properties inside classes that wouldn't need them. Instead you need to check the type of class and cast it back:
RightTriangle rt1 = new RightTriangle();
Cone cn1 = new Cone();
List<Shape> objs = new List<Shape>();
objs.Add(rt1);
Shape object = objs[0];
RightTriangle rt = objs[0] as RightTriangle;
if (rt != null)
sideA_tb.Text = rt.sideA.ToString();
To see more about checking the type of an object remember to refer to this MSDN article
EDIT
So your full class structure could be:
public abstract class Shape {
public abstract double getArea();
public override string ToString() {
return "Area: " + this.getArea();
}
}
public class RightTriangle:Shape {
public double sideA {get; set;}
public double sideB {get; set;}
public override double getArea() {
return (this.sideA * this.sideB)/2;
}
public override string ToString() {
return "Side A: " + this.sideA +" Side B: " + sideB + " " + base.ToString();
}
}
public class Circle:Shape {
public double radius {get; set;}
public override double getArea() {
return Math.Pow(this.radius, 2) * Math.PI;
}
public override string ToString() {
return "Radius: " + this.radius + " " + base.ToString();
}
}
public class Cone:Circle {
public double height {get; set;}
public override double getArea() {
//πr(r+sqrt(h^2+r^2))
return Math.PI * this.radius * (this.radius + Math.Sqrt(Math.Pow(this.height, 2) + Math.Pow(this.radius, 2)));
}
public override string ToString() {
return "Height : "+ height + " " + base.ToString();
}
}
Cone will inherit radius from the Circle.
Then you need some checks to see what to do with them, a method like this could be a possibility:
public void doSomething(Shape obj) {
RightTriangle rt = obj as RightTriangle;
if (rt != null) {
//Do something
}
Circle circle = obj as Circle;
if (circle != null) {
//Do something
}
Cone cone = obj as Cone;
if (cone != null){
//Do something
}
}
Here's a dotnetfiddle of it working
Do remember though, using as
keyword, a Cone
can be considered a Circle
, but a Circle
object cannot be considered a Cone
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With