在不同类型的数组上调用item方法

时间:2016-06-10 08:21:17

标签: c# arrays oop dynamic

我的任务很简单:我必须创建一个动态数组(这意味着它的大小可以在所有运行时间内更改)并使用不同类型的对象填充它(取决于用户输入,也在运行时)。而且,我应该可以访问数组中每个对象的字段(和/或方法)。显然,每种类型的字段都不同。简化结构:

public class Point {}
public class RightTriangle:Point { public double sideA, sideB; }
public class Circle:Point { public double radius; }
public class Cone:Circle { public double radius, height; }

因此,您会看到:所有类都继承了一个基类。我知道,这些结构不合逻辑,但这不是我的选择。所以,我想要这个代码工作:

RightTriangle rt1 = new RightTriangle();
Cone cn1 = new Cone();
List<Point> objs = new List<Point>();
objs.Add(rt1);
sideA_tb.Text = objs[0].sideA.ToString();

但事实并非如此。编译器说sideA中没有Point。但是,这也不起作用:

public class Point { public double sideA, sideB, radius, height; }
public class RightTriangle:Point { public new double sideA, sideB; }
public class Circle:Point { public new double radius; }
public class Cone:Circle { public new double radius, height; }

似乎来自实际班级(rt1,即RightTriangle)的值不会覆盖Point班级中的值,所以我有这样的内容:

List<Point> objs = new List<Point>();
objs.Add(rt1); // rt1 has fields 'sideA' = 4, 'sideB' = 5
sideA_tb.Text = rt1.sideA.ToString(); // Got 4, okay
sideA_tb.Text = objs[0].sideA.ToString(); // Got 0, what the?

所以,基本上,我需要一个动态的链接数组,我想使用这个链接来访问对象字段和方法。另外,我知道我可以在基类中编写一些Get/Set函数并在子类中覆盖它们,但这似乎不是美容解决方案。 提前谢谢,请忘记我的文盲(英语不是我的母语)。

4 个答案:

答案 0 :(得分:4)

你几乎拥有它(理论上):

public class Point { public virtual double sideA, sideB, radius, height; }
public class RightTriangle:Point { public override double sideA, sideB; }

要使派生类中的属性和方法可覆盖,必须在基类中声明virtual。重写类必须将overriden属性/方法声明为override

实际上,您不能创建字段virtual,只能使用属性和方法。因此,您必须按如下方式更改代码:

public class Point { 
    public virtual double sideA { get; set; } 
    public virtual double sideB { get; set; }
    public virtual double radius { get; set; }
    public virtual double height { get; set; }
}

public class RightTriangle:Point { 
    public override double sideA { get; set; }
    public override double sideB { get; set; }
}

但是,你不应该这样做,因为这是非常糟糕的设计。更多信息如下。

那么与new的区别是什么?

当一个方法覆盖时,将在运行时决定 是否会调用被覆盖的或原始的实现。

因此,当方法收到Point作为参数时,Point实例可能实际上是RightTriangleCircle

Point p1 = new RightTriangle();
Point p2 = new Circle();

在上面的示例中,从使用p1p2的代码的角度来看,Pointp1都是p2。然而,&#34;在&#34;下面&#34;它们实际上是派生类的实例。

因此,使用我的解决方案,例如,当您访问p1.sideA时,运行时将在&#34;下面&#34;并检查Point到底是什么:它实际上是RightTriangle吗?然后检查sideA是否存在重写的实施并调用该实例。它实际上是Circle吗?然后执行相同的检查(将失败)并调用sideA原始建议。

new限定符可以做其他事情。它不会覆盖某个方法,但会创建一个完全 new 的方法(具有相同的名称),在编译时以不同的方式处理

因此,使用您的解决方案,编译器会看到您创建了RightTriangle并将其存储在Point类型的变量中。例如,当您现在访问p1.sideA时,编译器将以这样的方式编译此访问,即从 base 类读取sideA,因为您正在处理的实例变量具有基本类型

如果您仍想访问new实施,那么使用p1的代码必须将其转换为正确的派生类型RightTriangle

var w = ((RightTriangle)p1).sideA; // Gets correct value.

那么问题是什么?

正如您现在可能已经注意到的那样,使用new并不是一个好的解决方案。使用各种Point的代码必须知道Point的特定派生是否实现了new的字段。此外,它必须知道,当它收到一个Point实例时,它实际上是什么样的实例。这将导致大量精心设计的if - else语句,用于检查点p1正在处理并运行适当的behvaiour。

使用virtualoverride缓解了最后一个问题,但前提是基类实现了任何派生类实现的所有方法和属性。这基本上是一种疯狂。你试过了,我确定你注意到在提供Point sideA sideBradius 和<的方式上没有多大意义/ em> a Pointabstract如何有意义地实现这些属性?而你也不能Circle,因为sideA也必须实现Point等等。

那么如何以更好的方式解决它?

考虑一下这个不同点实例你想做什么。你将它们一起收集在列表中是有原因的:它们有一些共同之处。此外,您还可以根据特定原因对此列表进行迭代,并对每个列表执行某些操作。但你究竟要做的是什么呢?你能用抽象的方式描述它吗?

也许你会得到边长和半径来计算面积?然后给virtual GetArea()方法GetArea()并在每个派生类中覆盖它。方法GetArea()对所有派生类型都有意义,尽管它们的每一个都有不同的实现方式。

您还可以abstract virtual方法而不是Point方法。这意味着它根本没有在基类中实现,所有派生类型都是强制来自己实现它。

也许您不想处理列表中的所有RightTriangle,但仅限RightTriangle s?那么执行此操作的代码应该只接收public void HandleRightTriangles(IEnumerable<RightTriangle> rts) { // Can work with sideA and sideB directly here, because we know we only got triangles. } s:

// using System.Linq;
HandleRightTriangles(objs.OfType<RightTriangle>());

用以下方式调用:

{{1}}

答案 1 :(得分:3)

RightTriangle rt1 = new RightTriangle();
Cone cn1 = new Cone();
List<Shape> objs = new List<Shape>();
objs.Add(rt1);
sideA_tb.Text = objs[0].sideA.ToString();

这不会编译,因为您尝试从类sideA获取Shape属性,该属性不存在。由于并非所有类都有sideA,我也不会将该属性添加到Shape类,因为并非所有类都使用sideA的属性,因此对OO设计来说不好将属性放在不需要它们的类中。相反,你需要检查类的类型并将其转换回来:

RightTriangle rt1 = new RightTriangle();
Cone cn1 = new Cone();
List<Shape> objs = new List<Shape>();
objs.Add(rt1);
Shape object = objs[0];
RightTriangle rt = objs[0] as RightTriangle;
if (rt != null)
    sideA_tb.Text = rt.sideA.ToString();

要了解有关检查对象类型的更多信息,请记得参考this MSDN article

修改

所以你的全班结构可能是:

public abstract class Shape {
    public abstract double getArea();

    public override string ToString() {
        return "Area: " + this.getArea();
    }
}
public class RightTriangle:Shape {

    public double sideA {get; set;}
    public double sideB {get; set;}

    public override double getArea() {
        return (this.sideA * this.sideB)/2;
    }

    public override string ToString() {
        return "Side A: " + this.sideA +" Side B: " + sideB + " " + base.ToString();
    }


}
public class Circle:Shape {

    public double radius {get; set;}

    public override double getArea() {
        return Math.Pow(this.radius, 2) * Math.PI;
    }

    public override string ToString() {
        return "Radius: " + this.radius  + " " + base.ToString();
    }
}
public class Cone:Circle {

    public double height {get; set;}

    public override double getArea() {
        //πr(r+sqrt(h^2+r^2))
        return Math.PI * this.radius * (this.radius + Math.Sqrt(Math.Pow(this.height, 2) + Math.Pow(this.radius, 2)));

    }

    public override string ToString() {
        return "Height : "+ height + " " + base.ToString();
    }
}

Cone将从Circle继承半径。

然后你需要一些检查来看看如何处理它们,这样的方法可能是一种可能性:

public void doSomething(Shape obj) {
    RightTriangle rt = obj as RightTriangle;
    if (rt != null) {
        //Do something
    }
    Circle circle = obj as Circle;
    if (circle != null) {
        //Do something
    }
    Cone cone = obj as Cone;
    if (cone != null){
        //Do something
    }
}

Here's a dotnetfiddle of it working

请记住,使用as关键字,Cone可以被视为Circle,但Circle对象不能被视为Cone

答案 2 :(得分:0)

在派生类上重新定义方法没有意义。这应该适合你:

public class Point { public double x, y}
public class RightTriangle:Point { }
public class Circle:Point { public double radius; }
public class Cone:Circle { public height; }

注意:重命名&#34; side&#34;到轴名称。

答案 3 :(得分:-2)

您拥有的集合是List的{​​{1}}。

当您添加PointRightTriangleCircle时,这些会被添加为基类Cone。 因此,当您索引到您的集合时,您将返回类型为Point的对象,您知道该对象没有字段。

在您将对象从集合中移出后,您将不得不从Point退回到其具体类别,例如:

Point

我还会查看is关键字,当从集合中获取对象时,检查对象的类型将会派上用场。 这可以像:

一样使用
var rightTriangle = new RightTriangle
{
    sideA = 10d,
    sideB = 15d
};

var points = new List<Point>
{
    rightTriangle
};

var indexedRightTriangle = (RightTriangle)points[0];
sideA_tb.Text = indexedRightTriangle.sideA;

你离我太远了。但我建议坚持使用您的第一个实现,因为第二个实现var indexedPoint = points[0]; if(indexedPoint is RightTriangle) { var indexedRightTriangle = (RightTriangle)indexedPoint; sideA_tb.Text = indexedRightTriangle.sideA; } 实现了所有不同类型的字段。