繁体   English   中英

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

[英]Calling item methods on an array of different types

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

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

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

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函数,并在子类中重写它们,但这似乎不是美的解决方案。 在此先感谢您,请忘记我的文盲(英语不是我的母语)。

从理论上讲,您几乎拥有它:

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

为了使属性和方法在派生类中可重写,必须在基类中将它们声明为virtual 重写类必须将重写属性/方法声明为override

实际上,您不能将字段设置为virtual ,而只能将属性和方法设置为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的代码的角度来看, p1p2都是Point 但是,它们“实际上”实际上是派生类的实例。

因此,在我的解决方案中,例如,当您访问p1.sideA时, 运行时将看起来“在下面”并检查Point真正含义是:它实际上是RightTriangle吗? 然后检查sideA是否有重写sideA并调用该实现。 它实际上是一个Circle吗? 然后执行相同的检查(这将失败)并调用sideA原始 sideA

但是, new限定词还有其他作用。 它不会覆盖一种方法,而是创建一个全新的 (具有相同名称)方法,该方法在编译时将以不同的方式处理。

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

如果仍然要访问new实现,则使用p1的代码必须将其RightTriangle转换为正确的派生类型RightTriangle

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

所以有什么问题?

您可能已经注意到,使用new并不是一个好的解决方案。 使用各种代码Point S有知道的特定衍生物是否Point实现了现场与new或没有。 而且,它必须知道,当它收到一个Point实例时,它实际位于哪种实例之下。 这将导致大量的非常复杂if - else ,入住什么点的语句p1被处理,并运行相应的behvaiour。

使用virtualoverride缓解最后一个问题,但前提是基类必须实现所有派生类实现的所有方法和属性。 这基本上是一种疯狂。 您已经尝试过了,而且我敢肯定,您注意到在给Point一个sideAsideB 以及一个radius并没有多大意义。 Point如何才能有效地实现这些属性? 而且您也不能使它们abstract ,因为那时Circle还必须实现sideA等等。

那么如何更好地解决呢?

考虑使用此不同网点实例时要执行的操作。 您将它们收集在一个列表中是有原因的:它们有一些共同点。 同样,您也出于特定原因遍历此列表,对每个列表进行处理。 但是,您到底想做什么? 您可以用抽象的方式描述它吗?

也许您正在获取边长和半径来计算面积? 然后为Point一个virtual方法GetArea()并在每个派生类中重写它。 尽管在每种派生类型中实现方法都不同,但GetArea()方法对所有派生类型有意义。

您也可以使GetArea()成为abstract方法,而不是virtual方法。 这意味着根本没有在基类中实现它,并且所有派生类型都被迫自己实现。

也许您不想只处理RightTriangle列表中的所有Point 然后,执行此操作的代码应仅接收RightTriangle

public void HandleRightTriangles(IEnumerable<RightTriangle> rts)
{
    // Can work with sideA and sideB directly here, because we know we only got triangles.
}

调用它:

// using System.Linq;
HandleRightTriangles(objs.OfType<RightTriangle>());
RightTriangle rt1 = new RightTriangle();
Cone cn1 = new Cone();
List<Shape> objs = new List<Shape>();
objs.Add(rt1);
sideA_tb.Text = objs[0].sideA.ToString();

当您尝试从Shape类中获取sideA属性时,该属性不会编译,该属性不存在。 由于并非所有类都具有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();

要查看有关检查对象类型的更多信息,请记住参考此MSDN文章。

编辑

因此,您的完整课程结构可能是:

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

圆锥将从圆继承半径。

然后,您需要进行一些检查以查看如何处理它们,可能会出现如下所示的方法:

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

这是一个工作的dotnetfiddle

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

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

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

注意:将“侧”重命名为轴名称。

您拥有的集合是PointList

当您添加RightTriangleCircleCone它们将被添加为Point的基类。 因此,当您对集合进行索引时,您将获得一个Point类型的对象,众所周知,该对象没有字段。

将对象从集合中移出后,您必须将PointPoint投射回其具体类,例如:

var rightTriangle = new RightTriangle
{
    sideA = 10d,
    sideB = 15d
};

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

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

我还要研究is关键字,当从集合中取回对象时,它在检查对象的类型时会派上用场。 可以这样使用:

var indexedPoint = points[0];
if(indexedPoint is RightTriangle)
{
    var indexedRightTriangle = (RightTriangle)indexedPoint;
    sideA_tb.Text = indexedRightTriangle.sideA;
}

你离得不太远。 但是,我建议您坚持使用第一个实现,因为第二个实现将Point实现所有不同的字段,而这对于它的类型来说是不必要的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM