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

C# 如何在单个列表中存储不同的对象,c#,interface,C#,Interface,我有两个班,一个是圆弧班,一个是直线班 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

我有两个班,一个是圆弧班,一个是直线班

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();
列表实体=新列表();
增加(新行(10,10,5,5));
行myLine=(行)实体[0]
双倍长度=myLine.length();
*假设line类中有所有正确的方法。

或者有更好的/不同的方法来实现这一点吗?

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

演员阵容还行,不过我还是建议:

Line myLine = Entities[0] as Line;

如果
实体[0]
无法转换为
行,而不是引发异常,则它将返回null。之后,您可以检查myLine是否为空。

是的,考虑到您的限制,这是唯一的方法

我建议给接口增加长度(因为弧确实有长度)

可以找到公式


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

让接口实现一个“Size”属性(或称之为“magnitue”或“Range”…)

这将映射到圆弧的半径和直线的长度


然后可以得到Entity.Size。

这取决于从列表中取出圆弧时要如何处理它们。如果尝试将圆弧投射到直线上,则会出现运行时错误,因此对于初学者,应检查正在使用的实体是否为直线

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

还是有更好的/不同的方法 这样做

如果对象来自公共类,则可以将它们存储在同一集合中。为了在不放弃类型安全性的情况下对对象执行任何有用的操作,您需要实现:

设置好后,只要需要对列表执行一些有用的操作,就可以创建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#的标记联合+模式匹配。

如果您使用的是.NET 3.5或更高版本,您可以通过以下方式使其不那么难看:

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

// add some lines and some arcs

var lines = entities.OfType<Line>();
列表实体=新列表();
//添加一些直线和圆弧
变量行=entities.OfType();
然后您只需循环通过
,其中将包含所有行(强类型为行),而不包含其他内容

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

List
可以根据您打算如何处理列表来工作。如果您有一组正在使用的类型

假设我有一个需要访问的不同类的属性列表。它们都以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();                 
           }
        }
     }

我经常看到像这样使用“as”。我不明白人们为什么在这种情况下使用它:NullReferenceException比InvalidCastException更可取吗?我发现“as”更具可读性。“as”与null一起工作,因此如果在使用对象之前检查null,这是一种方法。@蒂姆:不,但是
as
+
==null
is Line
+cast更可取您必须捕获InvalidCast,但是您可以做一个简单的if(myLine!=null)测试以避免空引用。仅仅因为某人没有按照您的方式编写代码,并不会使其出错。除非看到“as”周围的其他代码,否则您不知道它是如何使用的。这是一个架构问题,而不是语法问题。您正在创建一个对象列表,这些对象可能有长度,也可能没有长度。在你的问题中,你没有讨论的是如何或为什么你知道一个特定的实体何时会有一个长度。这两个量(弧半径)和线长度,在数学术语中,实际上映射到完全不同的东西。为什么不使用真正的弧长呢?弧确实有长度,但直线的半径是多少?发布的代码只是我代码的一个简单示例,旨在使问题更清楚。但是谢谢你的建议。
    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();                 
           }
        }
     }