2009-09-21 125 views
5

我遇到了方法返回類型的問題。如何在方法中返回動態返回類型? C#

該方法返回一個linq對象,它目前返回類型tblAppointment。此方法如下所示:

public tblAppointment GetAppointment(int id) 
{ 
    var singleAppointment = (from a in dc.tblAppointments 
                where a.appID == id 
                select a).SingleOrDefault(); 
    return singleAppointment; 

} 

問題是tblAppointment是抽象的,並且有很多子類型會繼承它。當我嘗試返回類型爲「appointmentTypeA」的對象並調用它的.GetType()方法時,它給了我正確的子類型,但是當我嘗試訪問屬性時,它只允許我訪問父屬性。如果我把這個對象轉換成一個子類型的新對象,那麼它就可以工作,並讓我訪問我需要的所有東西,但看起來很亂。

var viewSingleAppointment = appointmentRepos.GetAppointment(appointmentId); 

Debug.Write(viewSingleAppointment.GetType()); //returns type i want 

if (viewSingleAppointment is tblSingleBirthAppointment) 
{ 
    tblSingleBirthAppointment myApp = (tblSingleBirthAppointment)viewSingleAppointment; //need to do this to access TypeA properties for some reason 

} 

編輯:我有這個工作,但我需要使用select語句每次約會(約20),並將其轉換爲相應的類型和retreive屬性和林不知道如何重構這個,因爲它將在我們正在做的幾頁上使​​用。

+0

定義'protected abstract void tblAppointment.Process()'並在每個類中覆蓋它,然後使用'appointmentRepos.GetAppointment(id).Process()'。 – 2009-10-01 10:39:17

回答

7

你解決錯誤的問題。如果你有一個超A,與子類BC等,所有具有類似的功能,要做到以下幾點:

  1. ABC等實現一個接口。使用BC實例的代碼通過A提供的接口工作。如果你可以定義一套適用於所有類型的通用操作,那麼這就是你所需要做的。

  2. 如果您無法定義一組通用操作,例如你有類似的代碼:

    A foo = GetA(); 
    if(foo is B) { 
        B bFoo = (B) foo; 
        // Do something with foo as a B 
    } else if(foo is C) { 
        C cFoo = (C) foo; 
        // Do something with foo as a C 
    } ... 
    

    甚至這個(這基本上是同樣的事情,只是用額外的信息來模擬什麼類型的系統已爲您提供):

    A foo = GetA(); 
    MyEnum enumeratedValue = foo.GetEnumeratedValue(); 
    switch(enumeratedValue) { 
        case MyEnum.B: 
         B bFoo = (B) foo; 
         // Do something with foo as a B 
         break; 
        case MyEnum.C: 
         C cFoo = (C) foo; 
         // Do something with foo as a C 
         break; 
    } 
    

    那麼你真正想要的是做這樣的事情:

    A foo = GetA(); 
    foo.DoSomething(); 
    

    其中每個子類將實現switch聲明的相應的分支。實際上這在幾個方面更好:

    • 它使用較少的整體代碼。
    • 由於案例的實現存在於各種實現類中,因此不需要投射;他們可以直接訪問所有的成員變量。
    • 既然你不是建設一個大switch/case單獨從實際BC實現,你不運行的不小心忘記添加相應的case如果添加一個新的子類的任何風險。如果您將DoSomething()方法從A的子類中取出,您將收到編譯時錯誤。

編輯:針對您的評論:

如果您DoSomething()例程需要一個Form或其他GUI元素上的操作,只是通過這個元素到方法。例如:

public class B : A { 
    public void DoSomething(MyForm form) { 
     form.MyLabel.Text = "I'm a B object!"; 
    } 
} 

public class C : A { 
    public void DoSomething(MyForm form) { 
     form.MyLabel.Text = "I'm a C object!"; 
    } 
} 

// elsewhere, in a method of MyForm: 

A foo = GetA(); 
foo.DoSomething(this); 

或者,甚至更好的想法可能是把你的BC類成自定義控件,因爲他們似乎封裝顯示邏輯。

+0

但是,如果當前的switch語句在表單上設置標籤和輸入值,這將如何工作?對不起,我知道這可能是基本的,但.NET花了一點時間去適應比我想象的:) – Andrew 2009-09-23 10:35:20

0

如果您要返回對父類型的子類型的引用,則該引用將是該類型的,並且編譯器將不允許您訪問任何子類型的成員,直至轉換爲該類型。這是行爲中的多態性:)

好消息是,當您投射引用類型時,您並未創建新對象 - 您只是簡單地更改指向您已擁有的對象的引用類型,訪問其成員。

+1

這是一個真實的陳述,但它並沒有幫助他解決手頭上的問題...... – 2009-09-21 16:04:36

1

您可以創建另一種方法來封裝演員:

public tblSingleBirthAppointment GetBirthAppointment(int id) 
{ 
    var singleAppointment = GetAppointment(id); 

    if (singleAppointment != null) 
    { 
     return (tblSingleBirthAppointment)singleAppointment; 
    } 

    return null; 
} 

也就是說,如果你想用的ID實際上不是BirthAppointment雖然使用方法會打破,所以你可能會考慮檢查。

var viewSingleBirthAppointment = appointmentRepos.GetBirthAppointment(appointmentId); 
2

你可以創建一個通用的方法:

public T GetAppointment<T>(int id) where T : tblAppointment 
{ 
    var singleAppointment = dc.tblAppointments.SingleOrDefault(a => a.appID == id); 
    return (T)singleAppointment; 
} 

但那麼你就需要調用它之前知道對象的實際類型...

7

好吧,如果你正在使用C# 4你可能使用動態輸入...但如果你想堅持靜態輸入,我懷疑你可以做的最好的是提供預期的類型作爲泛型類型的參數,並獲得執行c AST爲您提供:

public T GetAppointment<T>(int id) where T : tblAppointment 
{ 
    var singleAppointment = (from a in dc.tblAppointments 
                where a.appID == id 
                select a).SingleOrDefault(); 
    return (T) singleAppointment; 

} 

調用此方法:

SpecificAppointment app = GetAppointment<SpecificAppointment>(10); 

,或者使用隱式類型:

var app = GetAppointment<SpecificAppointment>(10); 

它會在執行時,如果轉換失敗拋出異常。

這假定來電者知道約會類型(儘管他們可以指定tblAppointment,如果他們不知道)。在編譯時不知道適當的約會類型,很難看出靜態類型如何更有利於你,真的......

+0

var app = GetAppointment (10); 我可以這樣做: var viewSingleAppointment = appointmentRepos.GetAppointment(appointmentId); var app = GetSingleAppointment (appointmentId); – Andrew 2009-09-22 09:09:55

+0

@SocialAddict:不,因爲必須在編譯時知道泛型類型參數。無論如何,它會有什麼好處?如果您在編譯時不知道約會類型,您將如何知道要使用哪些屬性? – 2009-09-22 09:11:09

+0

上述代碼的問題是,我不知道預約類型,直到我有從getAppointment返回的typeID。我可以通過在每個中使用類似這樣的大選擇語句使其工作: switch(viewSingleAppointment.tblAppointmentType.appTypeID) { case 1: {tblSingleBirthAppointment myApp =(tblSingleBirthAppointment)viewSingleAppointment; //獲取相關屬性並設置爲查看 } 就像我最終會在多個頁面上使用非常長的select語句並且不確定如何重構? – Andrew 2009-09-22 11:23:20

2

當您調用.GetType()時,您將獲得該對象的運行時類型。 C#編譯器不知道你的對象會有什麼樣的運行時類型。它只知道你的對象是從tblAppointment派生的類型,因爲你在方法聲明中這樣說,所以返回值的靜態類型是tblAppointment。因此tblAppointment是你可以訪問的所有東西,除非你使用轉換來告訴編譯器«我知道在運行時這個引用將引用這種類型的對象,插入一個運行時檢查並給這個靜態類型引用» 。

靜態類型是編譯時已知的類型和運行時類型之間的區別。如果您來自動態類型的語言,如Smalltalk或Javascript,則必須對您的編程習慣和思維過程進行相當多的調整。例如,如果你必須對依賴於它的運行時類型的對象做些什麼,那麼解決方案通常是使用虛函數 - 它們在對象的運行時類型上進行分派。

更新:你的具體情況,使用虛擬功能,這也正是他們爲製作:

class tblAppointment 
{ 
    protected abstract void ProcessAppointment() ; 
} 

sealed class tblBirthAppointment 
{ 
    protected override void ProcessAppointment() 
    { 
     // `this` is guaranteed to be tblBirthAppointment 
     // do whatever you need 
    } 
} 

... 

然後使用

// will dispatch on runtime type 
appointmentsRepo.GetAppointment (id).ProcessAppointment() ;