How can I store different objects in a single list
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开发者_JAVA百科 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?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.
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.
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.
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.
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.
List<object>
could work depending on what you intend to do with the list. If you have a set number of types you are working with
Lets say I have a list of properties from different classes I need to access. They are all stored in a List of string in format Model.Property. Then I have a list of objects in a List of objects.
foreach(object model in models)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Type type = assembly.GetType(model.GetType().FullName);
foreach (string match in matches)
{
string property = match.Replace($"{model.GetType().Name}.", "");
if (match == $"{model.GetType().Name}.{property}")
{
PropertyInfo prop = type.GetProperty(property);
string value = prop.GetValue(model).ToString();
}
}
}
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.
精彩评论