9

By way of an intro, I'm creating a basic Quadtree engine for personal learning purposes. I'm wanting this engine to have the capability of working with many different types of shapes (at the moment I'm going with circles and squares) that will all move around in a window and perform some sort of action when collision occurs.

Here are my shape objects as I have them so far:

public class QShape {
    public int x { get; set; }
    public int y { get; set; }
    public string colour { get; set; }
}

public class QCircle : QShape {
    public int radius;
    public QCircle(int theRadius, int theX, int theY, string theColour) {
        this.radius = theRadius;
        this.x = theX;
        this.y = theY;
        this.colour = theColour;
    }
}

public class QSquare : QShape {
    public int sideLength;
    public QSquare(int theSideLength, int theX, int theY, string theColour) {
        this.sideLength = theSideLength;
        this.x = theX;
        this.y = theY;
        this.colour = theColour;
    }
}

Now my question is, how do I create a generic list (List<T> QObjectList = new List<T>();) in C# so I can have one list containing all these various shapes that may have different properties (e.g., QCircle has the "radius" property while QSquare has the "sideLength" property)? An example of implementation would be helpful as well.

I just know that there is a stupidly obvious answer to this question but I'd appreciate any help anyway. I'm trying to get back into C#; it has obviously been a while...

Djentleman
  • 1,137
  • 5
  • 13
  • 28
  • 7
  • Would such a list include the classes that inherit from QShape? If so, I was right; the answer is stupidly obvious. – Djentleman Jul 06 '12 at 01:09
  • @Djentleman: Yes it would. But you'd only be able to access the QShape properties unless you cast the individual objects. – Cameron Jul 06 '12 at 01:09
  • Could I get that in answer form, including an example of how you'd implement that (i.e., how you'd access QCircle and QSquare-specific properties from List)? – Djentleman Jul 06 '12 at 01:12
  • @Djentleman You can't. Not sure why his comment is being upvoted. You clearly state you want to access different properties not contained in the parent class so using the base class as the type will not work. – Pete Jul 06 '12 at 01:18

8 Answers8

7

You need to use downcasting

Store the objects in a list with the base class

 List<QShape> shapes = new List<QShape>

You can then upcast the object safely if you know what it is e.g.

if(shapes[0] is QSquare)
{
     QSquare square = (QSquare)shapes[0]
} 

You can also implicitly downcast objects

QSquare square = new Square(5,0,0,"Blue");
QShape shape =  square

For more information read the Upcasting and Downcasting sections here

Joel
  • 587
  • 1
  • 5
  • 17
  • 2
    If you're having to test types and cast when dealing with items in generic collections, it's a sign that something has gone wrong. – spender Jul 06 '12 at 01:17
  • I'm not quite sure what you mean, its an alternative to an interface to access type specific properties. – Joel Jul 06 '12 at 01:19
  • 2
    I believe you can use the **is** keyword in your example, it's been available for quite some time. if(myObject is MyType) – Chris Gessler Jul 06 '12 at 01:27
  • I learn a lot I didn't know on this site as well... everyday in fact, I think I learn something new. – Chris Gessler Jul 06 '12 at 01:35
  • I like this answer. Nice and simple. Thanks! Just a quick question on this, however; why would implicitly downcasting objects be useful? – Djentleman Jul 06 '12 at 01:39
  • Again, because if you are iterating over a list of `QShape`s you can't access properties/methods of `QSquare` or `QCircle`. By checking in the if statement to see if it's a `QSquare` and casting it if it is you then have access to the properties/methods of `QSquare`. The method in this answer is ok for checking a couple of classes but I'm sure you can see where it quickly gets out of hand if you start adding more and more subclasses. – Pete Jul 06 '12 at 02:00
  • Sorry, I misunderstood the meaning of downcast. Great answer; very informative. Thank you. – Djentleman Jul 06 '12 at 02:14
  • @Joel note that `is` tests for assignment compatibility, not type equality, so it's not equivalent. Specifically, for a subclass of QSquare, `x.GetType() == typeof(QSquare)` returns false, while `x is QSquare` returns true – phoog Jul 06 '12 at 03:35
3

You should implement an Interface. For example

public interface IHasLength
{
    int Length;
}

Then in the implementation you can do

public class QSquare : QShape, IHasLength {
    public int sideLength;
    public QSquare(int theSideLength, int theX, int theY, string theColour) {
        this.sideLength = theSideLength;
        this.x = theX;
        this.y = theY;
        this.colour = theColour;
    }
    public int Length { get { return sideLength; } }
}
public class QCircle : QShape, IHasLength {
    public int radius;
    public QSquare(int theSideLength, int theX, int theY, string theColour) {
        this.sideLength = theSideLength;
        this.x = theX;
        this.y = theY;
        this.colour = theColour;
    }
    public int Length { get { return radius; } }
}

FInally, in your list:

List<IHasLength> shapesWithSomeLength = new List<IHasLength>();

Now your list can hold ANYTHING that implements IHasLength whether it's a QCircle, QShape, or even a QDuck if you want as long as it implements IHasLength.

Pete
  • 10,651
  • 9
  • 52
  • 74
  • I like this answer, but is there any particular reason for using an interface as opposed to a superclass in this instance? – Djentleman Jul 06 '12 at 01:32
  • 2
    @Djentleman Because your classes don't have common property names. So if you were to create a list of `QShape` and wanted to iterate over it and get the length for all shapes, `QCircle` and `QSquare` you can't. If you implement the interface then you will be able to do just that. – Pete Jul 06 '12 at 01:34
  • Ahh, I see. Very nice. Time to read up on interfaces! Thanks :) – Djentleman Jul 06 '12 at 01:36
  • Just a quick note for your benefit. I've gone with this sort of approach except I've changed IHasLength to ISpatialNode. This interface is for x,y positioning. Makes more sense, and means I can add any object that requires positioning into my Quadtree. – Djentleman Jul 06 '12 at 03:12
  • @Djentleman Of course you want to use whatever names/properties makes sense for your implementation. I just choose `Length` for the example since that's what you mentioned in your question (`radius`, `sideLength`). I'm sure it's all clear by now ;) – Pete Jul 06 '12 at 04:43
2

You could store them in a List<QShape> but this would mean that you could not access type-specific properties.

Generally, you might approach this by providing a common interface in your base class, and overriding behaviour in subclasses. In this way, a common interface can hide a diverse bunch of behaviours. For instance a Grow method could hide the complexities of growing items of different shape and could be called without explicit knowlege of the shape upon which it is operating.

public abstract class QShape {
    public abstract void Grow(int amt);
}
public class QSquare : QShape {
    private int sideLength;
    public override void Grow(int amt)
    {
        sideLength+=amt;
    }
}
public class QCircle : QShape {
    private int radius;
    public override void Grow(int amt)
    {
        radius+=amt;
    }
}
spender
  • 117,338
  • 33
  • 229
  • 351
2

Is this what you want?

public class QShape
{
    protected QShape() { }
    public int x { get; set; }
    public int y { get; set; }
    public string colour { get; set; }
}

public class QCircle : QShape
{
    public int radius;
    public QCircle(int theRadius, int theX, int theY, string theColour)
    {
        this.radius = theRadius;
        this.x = theX;
        this.y = theY;
        this.colour = theColour;
    }
}

public class QSquare : QShape
{
    public int sideLength;
    public QSquare(int theSideLength, int theX, int theY, string theColour)
    {
        this.sideLength = theSideLength;
        this.x = theX;
        this.y = theY;
        this.colour = theColour;
    }
}
class Program
{
    static void Main(string[] args)
    {
        List<QShape> list = new List<QShape>();
        list.Add(new QCircle(100, 50, 50, "Red"));
        list.Add(new QCircle(100, 400, 400, "Red"));
        list.Add(new QSquare(50, 300, 100, "Blue"));


        foreach (var item in list.OfType<QCircle>())
        {
            item.radius += 10;
        }

        foreach (var item in list.OfType<QSquare>())
        {
            item.sideLength += 10;
        }
    }
}
John Alexiou
  • 28,472
  • 11
  • 77
  • 133
  • One question: Wouldn't it be more efficient to iterate through the list once and perform actions on the different types using conditionals in said iteration, rather than iterating through it twice (once for each individual shape) as you have here? – Djentleman Jul 06 '12 at 02:26
  • @Djentleman: Yes, but...For small lists (<10,000 items) it probably makes no difference. It is really not clear from the OP what the goals/requirements are. – John Alexiou Jul 06 '12 at 12:52
1

I feel like i'm missing something but...

List<QCircle> circleObjects = new List<QCircle>();

and

List<QSquare> squareObjects = new List<QSquare>();

will work perfectly well.

EDIT:

Ah, I didn't understand what was being asked.

Yes, as your QCircle and QSquare classes inherit from QShape, you can just do.

List<QShape> shapes= new List<QShape>();

It's worth noting that if you want to access the radius property of all the QCircle's in that list, then you are going to have to filter the list based on type.

Alastair Pitts
  • 19,423
  • 9
  • 68
  • 97
1

You can use Ian Mercer's comment List<QShape>

And here's how you would fill it:

List<QShape> shapes = new List<QShape>();
QCircle circle = new QCircle(); 

shapes.Add(circle);

To unbox it:

QCircle circle = (QCircle) shapes[0];

If you need to call a method off the base class, no need to unbox, just use it.

Chris Gessler
  • 22,727
  • 7
  • 57
  • 83
  • Good answer, very simple and yet informative. Joel's answer is similar but a bit more detailed, but thanks nonetheless! – Djentleman Jul 06 '12 at 01:48
-1

Storing

You're already on the right track with your class definitions. What you have to do is make a List of the superclass (in this case, QShape), which will be able to hold all of your shapes.

Here's an example of how you would make it:

List<QShape> objects = new List<QShape>();

objects.add(new QCircle(...));
objects.add(new QSquare(...));

Accessing

The problem here is differentiating what is what once everything is in the list. That's done with the getType() and typeof() methods of C#. (Jon Skeet has an excellent answer about how to do this). Basically, it looks like this:

if(objects.get(some_position).getType() == typeof(QCircle))
  QCircle circle = objects.get(some_position);
else if(/* like above with QSquare */)
  QSquare square = objects.get(some_position);

After you do this, you can resume using your objects like normal. But if you try accessing them from the list, you can only use the methods and variables that QShape has, as every object put in the list will be cast to it.

Community
  • 1
  • 1
Jon Egeland
  • 12,470
  • 8
  • 47
  • 62
  • WTF is objects.get().getType() ?? And why not just shorten it to if(myObject is MyType) – Chris Gessler Jul 06 '12 at 01:24
  • While some of your syntax may appear to be off, I understand what you were saying, so thanks! – Djentleman Jul 06 '12 at 01:50
  • @ChrisGessler testing an object's type for equality with a known type is not the same as the `is` operator (and 99% of the time the programmer wanted the behavior of the `is` operator, anyway, so the type equality check is usually a bug). – phoog Jul 06 '12 at 03:29
-1
public Class abstract  Base<T>
{
    public abstract List<T>GetList();
}

then do this

public class className:Base<ObjectName>
{
    public override List<T>GetList()
    {
        //do work here 
    }
}
Niko
  • 26,516
  • 9
  • 93
  • 110