我的任务很简单:我必须创建一个动态数组(这意味着它的大小可以在所有运行时改变)并用不同类型的对象填充它(取决于用户输入,也在运行时)。此外,我应该可以访问数组中每个对象的字段(和/或方法)。显然,每种类型的字段都不同。简化结构:
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
基类中的函数并在子类中覆盖它们,但这似乎不是美容解决方案。提前致谢,请忘记我的文盲(英语不是我的母语)。
最佳答案
你几乎拥有它(理论上):
public class Point { public virtual double sideA, sideB, radius, height; }
public class RightTriangle:Point { public override double sideA, sideB; }
要使派生类中的属性和方法可覆盖,必须声明它们
virtual
在基类中。覆盖类必须将覆盖的属性/方法声明为 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
实例实际上可能是 RightTriangle
或 Circle
.Point p1 = new RightTriangle();
Point p2 = new Circle();
在上面的例子中,
p1
和 p2
是 Point
s 从使用 p1
的代码的角度来看和 p2
.然而,“下面”它们实际上是派生类的实例。因此,使用我的解决方案,当您访问
p1.sideA
时例如,运行时将看起来“在下面”并检查 Point
真的是:它真的是 RightTriangle
?然后检查是否存在 sideA
的覆盖实现并调用那个。它真的是 Circle
?然后做同样的检查(会失败)并调用 sideA
的原始实现.new
然而,限定符会做其他事情。它不会覆盖一个方法,而是创建一个全新的方法(具有相同的名称),在编译时以不同的方式处理。因此,使用您的解决方案,编译器会看到您创建了一个
RightTriangle
并将其存储在 Point
类型的变量中.当您现在访问 p1.sideA
例如,编译器将以读取 sideA
的方式编译此访问。来自基类,因为您正在处理的实例变量具有基类型。如果您还想访问
new
实现,然后你的代码使用 p1
必须将其强制转换为正确的派生类型 RightTriangle
:var w = ((RightTriangle)p1).sideA; // Gets correct value.
所以有什么问题?
正如您现在可能已经注意到的,使用
new
不是一个好的解决方案。各种使用的代码Point
s 必须知道是否是 Point
的特定导数使用 new
实现一个字段或不。此外,它必须知道,当它收到 Point
时实例它实际上是在什么样的实例下面。这将导致很多非常精细的if
- else
语句检查什么样的点 p1
正在处理并运行适当的行为。使用
virtual
和 override
缓解了最后一个问题,但前提是基类实现了任何派生类实现的所有方法和属性。这基本上有点疯狂。你试过了,我相信你已经注意到,给出 Point
没有多大意义。 sideA
和 sideB
和一个 radius
.怎么可能Point
曾经有意义地实现这些属性吗?你不能制造它们 abstract
要么,因为然后是 Circle
还必须实现 sideA
等等。那么如何更好的解决呢?
想想你想用这个不同的点实例做什么。您将它们收集在一个列表中是有原因的:它们有一些共同点。此外,您也出于特定原因遍历此列表,对每个列表执行某些操作。但是你到底想要做什么?你能用抽象的方式描述它吗?
也许您正在获取边长和半径来计算面积?然后给
Point
virtual
方法 GetArea()
并在每个派生类中覆盖它。一种方法GetArea()
对所有派生类型都有意义,尽管它在每个派生类型中的实现方式不同。您也可以制作
GetArea()
abstract
方法而不是 virtual
一。这意味着它根本没有在基类中实现,所有派生类型都被迫自己实现它。也许你不想处理所有
Point
s 在列表中,但 RightTriangle
只?然后执行此操作的代码应该只收到 RightTriangle
s: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>());
关于c# - 在不同类型的数组上调用 item 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37743326/