简体   繁体   English

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

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

My task is simple: I have to create a dynamic array (which means that its size can change during all runtime) and fill it (depends on user input, also in runtime) with objects of different types. 我的任务很简单:我必须创建一个动态数组(这意味着它的大小可以在所有运行时期间改变),并用不同类型的对象填充它(取决于用户输入,也取决于运行时)。 Moreover, it should be possible for me to access fields (and/or methods) of every object in the array. 而且,对于我来说,应该可以访问数组中每个对象的字段(和/或方法)。 Obviously, fields are different for each type. 显然,每种类型的字段都不同。 Simplified structure: 简化结构:

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

So, you see: all classes inherit one base class. 因此,您将看到:所有类都继承一个基类。 And I know, these structure kind of illogical, but that's not my choice. 而且我知道,这些结构有点不合逻辑,但这不是我的选择。 So, I want this code to work: 因此,我希望这段代码能够正常工作:

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

But it doesn't. 但事实并非如此。 Compiler says that there is no sideA in Point . 编译器说Point没有sideA However this does not work either: 但是,这也不起作用:

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

It seems that values from actual class ( rt1 , which is RightTriangle ) do not override ones in Point class, so I've got something like this: 似乎实际类( 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?

So, basically, I need a dynamic array of links, and I want to use this links to access objects fields and methods. 因此,基本上,我需要一个动态的链接数组,并且我想使用此链接来访问对象字段和方法。 Also, I know that I can write some Get/Set functions in base class and override them in child classes, but this does not seems like beauty solution. 另外,我知道我可以在基类中编写一些Get/Set函数,并在子类中重写它们,但这似乎不是美的解决方案。 Thanks in advance and please forget my illiteracy (English isn't my native). 在此先感谢您,请忘记我的文盲(英语不是我的母语)。

You almost had it (in theory): 从理论上讲,您几乎拥有它:

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

To make properties and methods overridable in derived classes they have to be declared virtual in the base class. 为了使属性和方法在派生类中可重写,必须在基类中将它们声明为virtual The overriding class has to declare the overriden property/method as override . 重写类必须将重写属性/方法声明为override

In practice you cannot make fields virtual , only properties and methods can be. 实际上,您不能将字段设置为virtual ,而只能将属性和方法设置为virtual So you have to change your code as follows: 因此,您必须按以下步骤更改代码:

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

However, you should not do this as it is very bad design. 但是,您不应该这样做,因为这是非常糟糕的设计。 More below. 下面更多。

So what's the difference to new ? 那么与new什么区别?

When a method is overriden it will be decided at runtime whether the overridden or the original implementation will be called. 重写方法时,将在运行时确定是否调用重写的或原始的实现。

So when a method receives a Point as argument, that Point instance might actually be a RightTriangle or a Circle . 因此,当方法将Point用作参数时,该Point实例实际上可能是RightTriangleCircle

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

In the above example, both p1 and p2 are Point s from the perspective of the code that uses p1 and p2 . 在上面的示例中,从使用p1p2的代码的角度来看, p1p2都是Point However, "underneath" they are actually instances of the derived classes. 但是,它们“实际上”实际上是派生类的实例。

So with my solution, when you acess p1.sideA for example, the the runtime will look "underneath" and check what the Point really is: Is it actually a RightTriangle ? 因此,在我的解决方案中,例如,当您访问p1.sideA时, 运行时将看起来“在下面”并检查Point真正含义是:它实际上是RightTriangle吗? Then check if there is an overridden implmentation of sideA and call that one. 然后检查sideA是否有重写sideA并调用该实现。 Is it actually a Circle ? 它实际上是一个Circle吗? Then do the same check (which will fail) and call the original implmentation of sideA . 然后执行相同的检查(这将失败)并调用sideA原始 sideA

The new qualifier however does something else. 但是, new限定词还有其他作用。 It will not override a method, but create a completely new one (with the same name), that is handled differently at compile time . 它不会覆盖一种方法,而是创建一个全新的 (具有相同名称)方法,该方法在编译时将以不同的方式处理。

So with your solution, the compiler sees that you created a RightTriangle and stored it in a variable of type Point . 因此,在您的解决方案中, 编译器会看到您创建了RightTriangle并将其存储在Point类型的变量中。 When you now access p1.sideA for example, the compiler will compile this access in such a way that it reads sideA from the base class, since the instance variable your are dealing with has the base type . 例如,当您现在访问p1.sideA ,编译器将以某种方式编译该访问,使其从类中读取sideA ,因为您要处理的实例变量具有基类型

If you still want to access the new implementation, then your code using p1 has to cast it to the correct derived type RightTriangle : 如果仍然要访问new实现,则使用p1的代码必须将其RightTriangle转换为正确的派生类型RightTriangle

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

So what's the problem? 所以有什么问题?

As you may have already noticed now, using new is not a good solution. 您可能已经注意到,使用new并不是一个好的解决方案。 The code that uses all kinds of Point s has to know whether a specific derivate of Point implements a field with new or not. 使用各种代码Point S有知道的特定衍生物是否Point实现了现场与new或没有。 Moreover it has to know, when it receives a Point instance what kind of instance it acutally is underneath. 而且,它必须知道,当它收到一个Point实例时,它实际位于哪种实例之下。 This will lead to lots of very elaborate if - else statements that check what kind of point p1 is being dealt with and run the appropriate behvaiour. 这将导致大量的非常复杂if - else ,入住什么点的语句p1被处理,并运行相应的behvaiour。

Using virtual and override alleviates the last problem, but only if the base class implements all methods and properties any derived class implements. 使用virtualoverride缓解最后一个问题,但前提是基类必须实现所有派生类实现的所有方法和属性。 Which is basically kind of insane. 这基本上是一种疯狂。 You tried that and I'm sure you noticed on the way that it doesn't make much sense to give a Point a sideA and sideB and a radius . 您已经尝试过了,而且我敢肯定,您注意到在给Point一个sideAsideB 以及一个radius并没有多大意义。 How could Point ever implement those properties meaningfully? Point如何才能有效地实现这些属性? And you cannot make them abstract either, because then a Circle also had to implement a sideA and so on. 而且您也不能使它们abstract ,因为那时Circle还必须实现sideA等等。

So how to solve it in a better way? 那么如何更好地解决呢?

Think about what you want to do whith this differnet point instances. 考虑使用此不同网点实例时要执行的操作。 You collect them together in a list for a reason: they have something in common. 您将它们收集在一个列表中是有原因的:它们有一些共同点。 Also you iterate over this list doing something with each one of them for a specific reason as well. 同样,您也出于特定原因遍历此列表,对每个列表进行处理。 But what is it you are trying to do exactly? 但是,您到底想做什么? Can you describe it in an abstract way? 您可以用抽象的方式描述它吗?

Maybe you are getting side lengths and radiuses to calculate the area? 也许您正在获取边长和半径来计算面积? Then give Point a virtual method GetArea() and override it in each derived class. 然后为Point一个virtual方法GetArea()并在每个派生类中重写它。 A method GetArea() makes sense on all derived types, although it is implemented differently in each one of them. 尽管在每种派生类型中实现方法都不同,但GetArea()方法对所有派生类型有意义。

You could also make GetArea() an abstract method instead of a virtual one. 您也可以使GetArea()成为abstract方法,而不是virtual方法。 This means it is not implemented in the base class at all and all derived types are forced to implement it on their own. 这意味着根本没有在基类中实现它,并且所有派生类型都被迫自己实现。

Maybe you don't want to handle all Point s in the list but the RightTriangle s only? 也许您不想只处理RightTriangle列表中的所有Point Then the code doing this should only receive the RightTriangle s: 然后,执行此操作的代码应仅接收RightTriangle

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

Call it with: 调用它:

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

This doesn't compile as you are trying to get a sideA property from the class Shape , which doesn't exist. 当您尝试从Shape类中获取sideA属性时,该属性不会编译,该属性不存在。 Since not all classes have a sideA , I wouldn't add that property to the Shape class either, since not all classes use the property of sideA , it would be bad OO design to put properties inside classes that wouldn't need them. 由于并非所有类都具有sideA ,所以我也不会将该属性添加到Shape类中,因为并非所有类都使用sideA的属性,因此将属性放在不需要它们的类中是很糟糕的OO设计。 Instead you need to check the type of class and cast it back: 相反,您需要检查类的类型并将其回退:

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

To see more about checking the type of an object remember to refer to this MSDN article 要查看有关检查对象类型的更多信息,请记住参考此MSDN文章。

EDIT 编辑

So your full class structure could be: 因此,您的完整课程结构可能是:

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 will inherit radius from the Circle. 圆锥将从圆继承半径。

Then you need some checks to see what to do with them, a method like this could be a possibility: 然后,您需要进行一些检查以查看如何处理它们,可能会出现如下所示的方法:

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 这是一个工作的dotnetfiddle

Do remember though, using as keyword, a Cone can be considered a Circle , but a Circle object cannot be considered a Cone 不过请记住,使用Cone as关键字可以视为Circle ,但不能将Circle对象视为Cone

There is no point on redifining the methods on the derived classes. 重新定义派生类上的方法没有意义。 This should work fine for you: 这应该适合您:

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

NOTE: renamed "side" to axis names. 注意:将“侧”重命名为轴名称。

The collection that you have is aa List of Point s. 您拥有的集合是PointList

When you add a RightTriangle or a Circle or a Cone these are being added as their base class a Point . 当您添加RightTriangleCircleCone它们将被添加为Point的基类。 So when you index into your collection you are getting back an object of type Point , which as you well know has no fields. 因此,当您对集合进行索引时,您将获得一个Point类型的对象,众所周知,该对象没有字段。

After you've gotten your object out of the collection you'll have to cast back from a Point to it's concrete class such as: 将对象从集合中移出后,您必须将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;

I'd also look into the is keyword which will come in handy when checking which type the object is when getting it back from the collection. 我还要研究is关键字,当从集合中取回对象时,它在检查对象的类型时会派上用场。 This can be used like: 可以这样使用:

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

You weren't too far away. 你离得不太远。 But I'd recommend sticking with your first implementation as the second has Point implement all the different fields which are unnecessary for it's type. 但是,我建议您坚持使用第一个实现,因为第二个实现将Point实现所有不同的字段,而这对于它的类型来说是不必要的。

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

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