如何在单个列表中存储不同的对象

时间:2009-12-04 17:01:39

标签: c# interface

我有两个类,一个是Arc类,一个是Line类

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(){}
}

但是我想在同一个列表中存储弧和线,所以我尝试了这样的接口

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

然后我为每个类添加了适当的方法并添加了代码以使用该接口。现在我可以将两种类型的对象添加到列表中,但我想从线对象获取长度,并且不希望将长度方法添加到弧或接口。我唯一的选择是将线对象强制转换回这样的线对象吗?

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

*假设我在线类中有所有正确的方法 或者有更好/不同的方式来做到这一点?

7 个答案:

答案 0 :(得分:3)

如果您使用的是.NET 3.5或更高版本,则可以通过以下方式减少这种情况:

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

// add some lines and some arcs

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

然后你只需循环遍历lines,它将包含所有行(强类型为行),而不包含任何其他内容。

我不是说这是最好的方法;我只是说这是做你正在做的事情的一种方式。我同意Shmoopty这是一个架构问题。

答案 1 :(得分:2)

由于ArcLine共享数据(startx和其他一些字段),我建议您使用公共抽象类作为父类而不是接口。例如,Figure

演员阵容还可以,但我宁愿推荐:

Line myLine = Entities[0] as Line;

如果Entities[0]无法转换为Line,则返回null,而不是抛出异常。您可以在之后检查myLine是否为空。

答案 2 :(得分:1)

是的,鉴于你的限制,这是唯一的方法。

我建议在界面上添加长度(因为弧确实有长度)。

可以找到公式here

或者您可以将该方法添加到接口,并让它抛出NotImplementedException。

答案 3 :(得分:1)

让界面实现“大小”属性(或称之为“magnitue”或“Range”......)

这将映射到Arc的半径和线条长度。

然后你可以获得Entity.Size。

答案 4 :(得分:0)

当您将Arcs从列表中取出时,这取决于您希望如何处理Arcs。如果您尝试将Arc转换为Line,则会出现运行时错误,因此对于初学者,您应该检查您正在使用的实体是否为Line。

处理Arcs的一种方法是使用Null对象模式。向Arc添加一个返回0的长度方法可能是有意义的。这样,从列表中检索对象的代码就不必关心它们是什么类型。

答案 5 :(得分:0)

  

或者有更好/不同的方式   这样做?

如果您的对象来自公共类,则可以将它们存储在同一个集合中。为了在不丢弃类型安全的情况下对您的对象执行任何有用的操作,您需要实现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);
    }
}

一旦到位,只要您需要对列表执行有用的操作,就可以创建EntityVisitor的实例:

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

对于它的价值,如果你愿意尝试新语言,那么F#标记的工会+模式匹配are a handy alternative to the visitor pattern

答案 6 :(得分:0)

List<object> 可能会起作用,具体取决于您打算对列表执行的操作。如果您正在使用一定数量的类型

假设我有一个需要访问的不同类的属性列表。它们都存储在格式为 Model.Property 的字符串列表中。 然后我在对象列表中有一个对象列表。

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