Calling element methods in an array of different types

My task is simple: I need to create a dynamic array (which means that its size can change throughout the run time) and fill it (depends on user input, as well as at run time) with objects of different types. Moreover, I should have access to the fields (and / or methods) of each object in the array. Obviously, the 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 that this structure is illogical, but this is 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 this is not so. The compiler says that Point does not have sideA . 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 the values ​​from the actual class ( rt1 , which is RightTriangle ) do not override the values ​​in the Point class, so I have 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 these links to access the fields and methods of objects. In addition, I know that I can write some Get/Set functions in a base class and override them in child classes, but this does not seem to be a beauty solution. Thank you in advance and please forget about my illiteracy (English is not my native language).

+5
source share
4 answers

You almost had this (theoretically):

 public class Point { public virtual double sideA, sideB, radius, height; } public class RightTriangle:Point { public override double sideA, sideB; } 

For properties and methods to be overridden in derived classes, they must be declared virtual in the base class. The override class must declare the override property / method as an override .

In practice, you cannot create virtual fields; there can only be properties and methods. Therefore, you need 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 this is a very poor design. Lower.

So what's the difference in new ?

When a method is overridden, it will be determined at run time whether the overridden or original implementation will be called.

So, when a method receives a Point argument as an argument, the Point instance can actually be a RightTriangle or Circle .

 Point p1 = new RightTriangle(); Point p2 = new Circle(); 

In the above example, both p1 and p2 are Point in terms of the code that uses p1 and p2 . However, "below them" they are in fact instances of derived classes.

So, with my solution, for example, when you use p1.sideA , the runtime will look β€œbelow” and check that there really is a Point : is it a RightTriangle ? Then check if there is an overridden implication of sideA and call it. Is it really Circle ? Then perform the same test (which will not be performed) and call the original sideA implantation.

Qualifying new , however, is doing something else. It will not override the method, but will create a completely new one (with the same name), which is processed differently at compile time.

So, with your solution, the compiler sees that you created a RightTriangle and saved it in a variable of type Point . When you now access p1.sideA , for example, the compiler will compile this access so that it sideA from the base class, since the instance variable you are dealing with is of the base type.

If you still want to access the new implementation, then your code using p1 must pass it into the correct derived RightTriangle type:

 var w = ((RightTriangle)p1).sideA; // Gets correct value. 

So what's the problem?

As you already noticed, using new not a good solution. Code that uses all types of Point needs to know whether a particular derived value of Point implements a field with new or not. In addition, he should know when he receives an instance of Point , which instance is under it. This will lead to many very complex if - else that check which p1 points are processed and the corresponding actions are performed.

Using virtual and override eliminates the last problem, but only if the base class implements all the methods and properties that any derived class implements. Which in essence is crazy. You tried this, and I'm sure you noticed that it makes no sense to give Point a sideA and sideB and radius . How does Point ever implement these properties meaningfully? And you cannot make them abstract , because then Circle also had to implement sideA , etc.

So how to solve the problem better?

Think about what you want to do with these instances of differnet points. You put them together on a list for some reason: they have something in common. You also iterate over this list and do something with each of them for a specific reason. But what are you trying to do for sure? Can you describe this in an abstract way?

Maybe you get lateral lengths and radii to calculate the area? Then give Point a virtual the GetArea() method and override it in each derived class. The GetArea() method makes sense for all derived types, although it is implemented differently in each of them.

You can also make GetArea() a abstract instead of virtual . This means that it is not implemented at all in the base class, and all derived types enforce it independently.

Maybe you do not want to process all Point in the list, but only RightTriangle ? Then the code executing this should receive only RightTriangle s:

 public void HandleRightTriangles(IEnumerable<RightTriangle> rts) { // Can work with sideA and sideB directly here, because we know we only got triangles. } 

Call the following address:

 // using System.Linq; HandleRightTriangles(objs.OfType<RightTriangle>()); 
+3
source
 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 does not compile since you are trying to get the sideA property from a Shape class that does not exist. Since not all classes have sideA , I would not add this property to the Shape class, since not all classes use the sideA property, it would be bad if the OO design placed the properties inside the classes that they need them to. Instead, you need to check the type of the class and discard it:

 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 learn more about object type validation, be sure to link to this MSDN article.

EDIT

So your complete 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(); } } 

A cone inherits a radius from a circle.

Then you will need some checks to see what to do with them, such a method may be possible:

 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 } } 

This is where dotnetfiddle works.

Remember that by using the as keyword, Cone can be considered a Circle , but a Circle cannot be considered a Cone

+3
source

It makes no sense to redo methods on derived classes. This should work for you:

 public class Point { public double x, y} public class RightTriangle:Point { } public class Circle:Point { public double radius; } public class Cone:Circle { public height; } 

NOTE: renamed "side" to axis names.

0
source

You have a collection of List Point s.

When you add a RightTriangle or Circle or Cone they are added as the base class of a Point . Therefore, when you index your collection, you return an object of type Point , which, as you know, has no fields.

After you have extracted your object from the collection, you will have to drop its specific class from Point , for example:

 var rightTriangle = new RightTriangle { sideA = 10d, sideB = 15d }; var points = new List<Point> { rightTriangle }; var indexedRightTriangle = (RightTriangle)points[0]; sideA_tb.Text = indexedRightTriangle.sideA; 

I would also look at a keyword that comes in handy when checking what type of object will be when returning from the collection. This can be used as:

 var indexedPoint = points[0]; if(indexedPoint is RightTriangle) { var indexedRightTriangle = (RightTriangle)indexedPoint; sideA_tb.Text = indexedRightTriangle.sideA; } 

You have not been too far. But I recommend sticking to my first implementation, since the second has Point implement all the different fields that are not needed for this type.

-2
source

All Articles