tags:

views:

170

answers:

6

I have two classes an Arc class and a Line class

public class Arc
{
     protected double startx;
     protected double starty;
     protected double endx;
     protected double endy;
     protected double radius;

     public Arc(){}
}
public class Line
{
     protected double startx;
     protected double starty;
     protected double endx;
     protected double endy;
     protected double length;
     public Line(){}
}

But I want to store arcs and lines in the same list, so I tried an interface like this

public interface Entity
{
     double StartX();
     double StratY();
     double EndX();
     double EndY();
}

Then I added the appropriate methods to each class and added the code to use the interface. Now I can add both types of objects to a list, but I want to get the length from a line object and don't want to add a length method to the arc or the interface. Is my only option to cast the line object back to a line object like this?

List<Entity> entities = new List<Entity>();
entities.Add(new Line(10,10,5,5));
Line myLine = (Line)Entities[0]
double length = myLine.Length();

*Assuming I have all the proper methods in the line class.
Or is there a better/different way to do this?

+2  A: 

Since Arc and Line share data (startx and some other fields), I suggest you use a common abstract class as parent class rather than an interface. For example, Figure.

The cast is okay, although I would rather recommend:

Line myLine = Entities[0] as Line;

It will return null if Entities[0] cannot be converted to a Line, rather than throwing an exception. You will be able to check whether myLine is null afterward.

CesarGon
I've seen 'as' used like this a lot. I don't understand why people use it in this situation: is a NullReferenceException preferable to an InvalidCastException?
Tim Robinson
I find "as" more readable. And "as" works with nulls, so it's the way to go if you check for null before you use the object.
ctford
@Tim: No, but `as` + `== null` is preferable to `is Line` + cast
dtb
You have to catch the InvalidCast, but you can do a simple if (myLine != null) test to avoid the NullReference.
Ron Warholic
Just because someone doesn't code something the way you would doesn't make it wrong. Unless you see the other code around the "as" you have no idea how it is used.
Matthew Whited
A cast is a way to tell the compiler that you know more that it knows. (Skeet dixit) So I tend to avoid casts. :-) Having said that, nothing is absolute, and casts have their place too.
CesarGon
+1 Abstract class.
C. Ross
+1  A: 

Yes, it is the only way, given your constraints.

I would suggest adding length to the interface (since arc does have a length).

The formula can be found here.

Or alternatively you could add the method to the interface, and have it throw a NotImplementedException.

C. Ross
It is true that arc has a length, but what is the radius of a line? The code posted is just a simple sample of my code, and was intended to make the question clearer. But thanks for the suggestions.
Tester101
+1  A: 

Have the interface implement a "Size" property (or call it "magnitue", or "Range". . .)

This maps to the Arc's radius, and to the lines length.

Then you can get Entity.Size.

Binary Worrier
Those two quantities (arc radius) and line length, really map to quite different things in math terms. Why not use a real arc length?
C. Ross
A: 

It depends how you want to treat Arcs when you get them out of the list. If you try and cast an Arc to a Line you will get a runtime error, so for starters you should check if the Entity you're working with is a Line.

One way to handle Arcs is to use the Null Object Pattern. It might make sense to add a length method to Arc that returns 0. That way the code that retrieves objects from the list doesn't have to care what kind they are.

ctford
A: 

Or is there a better/different way to do this?

If your objects descend from a common class, then you can store them in the same collection. In order to do anything useful with your objects without throwing away type safety, you'd need to implement the visitor pattern:

public interface EntityVisitor
{
    void Visit(Arc arc);
    void Visit(Line line);
}

public abstract class Entity
{
    public abstract void Accept(EntityVisitor visitor);
}

public class Arc : Entity
{
    protected double startx;
    protected double starty;
    protected double endx;
    protected double endy;
    protected double radius;

    public override void Accept(EntityVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class Line : Entity
{
    protected double startx;
    protected double starty;
    protected double endx;
    protected double endy;
    protected double length;

    public override void Accept(EntityVisitor visitor)
    {
        visitor.Visit(this);
    }
}

Once that's in place, you create an instance of EntityVisitor whenever you need to do something useful with your list:

class EntityTypeCounter : EntityVisitor
{
    public int TotalLines { get; private set; }
    public int TotalArcs { get; private set; }

    #region EntityVisitor Members

    public void Visit(Arc arc) { TotalArcs++; }
    public void Visit(Line line) { TotalLines++; }

    #endregion
}

class Program
{
    static void Main(string[] args)
    {
        Entity[] entities = new Entity[] { new Arc(), new Line(), new Arc(), new Arc(), new Line() };
        EntityTypeCounter counter = entities.Aggregate(
            new EntityTypeCounter(),
            (acc, item) => { item.Accept(acc); return acc; });

        Console.WriteLine("TotalLines: {0}", counter.TotalLines);
        Console.WriteLine("TotalArcs: {0}", counter.TotalArcs);
    }
}


And for what its worth, if your open to trying new languages, then F#'s tagged unions + pattern matching are a handy alternative to the visitor pattern.

Juliet
+2  A: 

If you're in .NET 3.5 or above, you can make this a bit less ugly this way:

List<Entity> entities = new List<Entity>();

// add some lines and some arcs

var lines = entities.OfType<Line>();

Then you just loop through lines, which will contain all the lines (strongly-typed as Lines) and nothing else.

I'm not saying this is the best approach; I'm only saying this is one way to do what you're doing. I agree with Shmoopty that it's an architecture problem.

Kyralessa