Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling item methods on an array of different types

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).

like image 726
Urffly Avatar asked Jun 10 '16 08:06

Urffly


People also ask

Can you mix different data type in an object array?

No, we cannot store multiple datatype in an Array, we can store similar datatype only in an Array.

Can you call a method on 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.

Can an array hold objects of varying types?

In java, is it possible to create an array or any sort of collection which can hold different data types? Yes.


2 Answers

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 Points 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 Points 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 Points in the list but the RightTriangles only? Then the code doing this should only receive the RightTriangles:

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>());
like image 83
Good Night Nerd Pride Avatar answered Oct 07 '22 03:10

Good Night Nerd Pride


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

like image 31
Draken Avatar answered Oct 07 '22 03:10

Draken