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

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

但事实并非如此。编译器说没有sideAPoint .但是,这也不起作用:
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实例实际上可能是 RightTriangleCircle .
Point p1 = new RightTriangle();
Point p2 = new Circle();

在上面的例子中,p1p2Point 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正在处理并运行适当的行为。

使用 virtualoverride缓解了最后一个问题,但前提是基类实现了任何派生类实现的所有方法和属性。这基本上有点疯狂。你试过了,我相信你已经注意到,给出 Point 没有多大意义。 sideAsideB和一个 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/

相关文章:

c# - C# 可以并将对象写入页面文件吗?

c# - 从具有共同值的字典中获取 TKey,其中 TValue 是 List<string>

php - 数组中的空值作为第一项 (PHP)

javascript - 通过另一个预定义的数组元素作为键来重构 JavaScript 对象数组

c++ - 在 C++ 中传递给函数的数组给出不同的长度

php - PDO : Getting Error : Call to a member function prepare() on a non-object

java - 改进对象格式化程序 API 的类层次结构设计

oop - 使用过程式代码重用 Java 类?

c# - MessageContract - 无效的方法生成

c# - 我应该为参数异常编写测试吗?