簡體   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