【问题标题】:Creating a generic list of objects in C#在 C# 中创建通用对象列表
【发布时间】:2012-07-06 01:05:44
【问题描述】:

作为介绍,我正在为个人学习目的创建一个基本的四叉树引擎。我希望这个引擎能够处理许多不同类型的形状(目前我正在使用圆形和正方形),它们都会在窗口中移动并在发生碰撞时执行某种动作。

这是我目前拥有的形状对象:

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

现在我的问题是,如何在 C# 中创建一个通用列表 (List<T> QObjectList = new List<T>();),这样我就可以拥有一个包含所有这些可能具有不同属性的各种形状的列表(例如,QCircle 具有“半径”属性,而 QSquare有“sideLength”属性)?一个实施示例也会有所帮助。

我只知道这个问题有一个非常明显的答案,但无论如何我都会很感激任何帮助。我正在尝试回到 C#;显然已经有一段时间了......

【问题讨论】:

  • 列表 ??
  • 这样的列表会包括继承自 QShape 的类吗?如果是这样,我是对的;答案显而易见。
  • @Djentleman:是的。但是你只能访问 QShape 属性,除非你强制转换单个对象。
  • 我能否以答案形式获得,包括您如何实现该功能的示例(即如何从 List 访问 QCircle 和 QSquare 特定属性)?
  • @Djentleman 你不能。不知道为什么他的评论被赞成。您明确声明要访问不包含在父类中的不同属性,因此使用基类作为类型将不起作用。

标签: c# list generics object quadtree


【解决方案1】:

你需要使用向下转换

将对象与基类一起存储在列表中

 List<QShape> shapes = new List<QShape>

如果你知道它是什么,你就可以安全地向上转换对象,例如

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

你也可以隐式向下转换对象

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

For more information read the Upcasting and Downcasting sections here

【讨论】:

  • 如果您在处理泛型集合中的项目时必须测试类型和强制转换,这表明出现了问题。
  • 我不太清楚你的意思,它是访问类型特定属性的接口的替代方案。
  • 我相信你可以在你的例子中使用 is 关键字,它已经有很长一段时间了。 if(myObject is MyType)
  • @ChrisGessler 谢谢,我不知道。
  • 我在这个网站上学到了很多我不知道的东西......事实上,我每天都在学习新东西。
【解决方案2】:

您应该实现Interface。例如

public interface IHasLength
{
    int Length;
}

那么在实现中你可以这样做

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

最后,在您的列表中:

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

现在,您的列表可以包含任何实现 IHasLength 的内容,无论是 QCircleQShape,甚至是 QDuck,只要它实现 IHasLength

【讨论】:

  • 我喜欢这个答案,但是在这种情况下使用接口而不是超类有什么特别的原因吗?
  • @Djentleman 因为你的类没有通用的属性名称。因此,如果您要创建一个 QShape 列表并希望对其进行迭代并获取所有形状的长度,QCircleQSquare 您不能。如果您实现了接口,那么您将能够做到这一点。
  • 啊,我明白了。很不错。是时候阅读接口了!谢谢:)
  • 只是为了您的利益而做的简短说明。除了将 IHasLength 更改为 ISpatialNode 之外,我已经采用了这种方法。此界面用于 x,y 定位。更有意义,意味着我可以将任何需要定位的对象添加到我的四叉树中。
  • @Djentleman 当然,您想使用对您的实施有意义的任何名称/属性。我只是选择Length 作为示例,因为这就是您在问题中提到的内容(radiussideLength)。我相信现在一切都清楚了;)
【解决方案3】:

这是你想要的吗?

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

【讨论】:

  • 一个问题:遍历列表一次并在所述迭代中使用条件对不同类型执行操作不是更有效,而不是遍历它两次(每个单独的形状一次)和你一样吗?
  • @Djentleman:是的,但是...对于小列表(
【解决方案4】:

您可以将它们存储在 List&lt;QShape&gt; 中,但这意味着您无法访问特定于类型的属性。

通常,您可以通过在基类中提供公共接口并在子类中覆盖行为来解决此问题。通过这种方式,一个通用界面可以隐藏各种不同的行为。例如,Grow 方法可以隐藏不同形状的增长项目的复杂性,并且可以在不明确知道其操作的形状的情况下调用。

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

【讨论】:

  • QShape 也应该是抽象的。另外,我在您的示例中看不到接口的使用。
  • 我以松散的方式使用术语接口。由于提供方法和属性,所有对象都有一个接口……不仅仅是那些继承interface 实现的对象。
  • 我喜欢这个,类似于皮特的回答。谢谢!
【解决方案5】:

我觉得我错过了什么,但是......

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

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

会很好用。

编辑:

啊,我不明白你在问什么。

是的,因为您的 QCircleQSquare 类继承自 QShape,您可以这样做。

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

值得注意的是,如果您想访问该列表中所有QCircleradius 属性,那么您将不得不根据类型过滤列表。

【讨论】:

  • OP 想要一个 List 包含两者。因此,它将是 List.
  • 是的,这可行,但他希望 ONE 列表包含任何类型的 QShapeQCircleQSquare
  • 我应该更清楚,但如果可能的话,我想要一个列表。
【解决方案6】:

您可以使用 Ian Mercer 的评论 List&lt;QShape&gt;

下面是你如何填写它:

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

shapes.Add(circle);

拆箱:

QCircle circle = (QCircle) shapes[0];

如果需要从基类中调用方法,不需要拆箱,直接使用即可。

【讨论】:

  • 很好的答案,非常简单但内容丰富。 Joel 的回答类似,但更详细一些,但还是要感谢!
【解决方案7】:

存储

您的类定义已经走在正确的轨道上。您需要做的是创建超类的List(在本例中为QShape),它将能够容纳您的所有形状。

这里有一个例子说明你是如何做到的:

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

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

访问

这里的问题是区分列表中的所有内容。这是通过 C# 的 getType()typeof() 方法完成的。 (Jon Skeet has an excellent answer about how to do 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);

完成此操作后,您可以像平常一样继续使用您的对象。但是,如果您尝试从列表中访问它们,则只能使用QShape 拥有的方法和变量,因为放入列表中的每个对象都会被强制转换为它。

【讨论】:

  • WTF 是 objects.get().getType() ??为什么不把它缩短为 if(myObject is MyType)
  • 虽然您的某些语法可能看起来不正确,但我明白您的意思,非常感谢!
  • @ChrisGessler 测试对象类型是否与已知类型相等与 is 运算符不同(无论如何,99% 的程序员都希望 is 运算符的行为,因此类型相等检查通常是一个错误)。
【解决方案8】:
public Class abstract  Base<T>
{
    public abstract List<T>GetList();
}

然后这样做

public class className:Base<ObjectName>
{
    public override List<T>GetList()
    {
        //do work here 
    }
}

【讨论】:

  • 一些解释会很有用。
猜你喜欢
  • 1970-01-01
  • 2019-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-11
  • 1970-01-01
相关资源
最近更新 更多