2017-02-27 115 views
2

問題:我有2種類型的是結果集的2個不同的程序DB: Proc1Result,Proc2Result(我們不得不分開他們 - 但它們基本上是相同自帶輸入/輸出)明確投基類派生類

然後我決定,我可以利用在運行時需要的程序之間切換的界面 - 但這只是意味着我需要1種常見的類型,而我可以轉換Proc1Result和Proc2Result

所以我不需要維護這個新類(創建所有的屬性,添加/刪除,如果一個在DB結果nything變化) - 我得到的這個類從結果之一:

public class DerivedClassForInterface : Proc1Result {} 

然後我實現了從第二PROC工作正常明確的轉換,但是當我要實現從基類顯式的推導類 - 它不會允許我(因爲它已經有點「不」 - 但它在運行時失敗):

public class DerivedClassForInterface : Proc1Result 
{ 
    //ok - and works as expected 
    public static explicit operator DerivedClassForInterface(Proc2Result v) 
    { 
     return new DerivedClassForInterface 
     { 
      ... 
     }; 
    } 

    //fail: 'user-defined' conversations to or from a base class are not allowed 
    public static explicit operator DerivedClassForInterface(Proc1Result v) 
    { 
     return new DerivedClassForInterface 
     { 
      ... 
     }; 
    } 
} 

所以此工程:

//result2 is of type Proc1Result 
DerivedClassForInterface castedResult = (DerivedClassForInterface)result2; 
//compiles - works as expected at runtime 

但這並不:

//result1 is of type Proc1Result 
DerivedClassForInterface castedResult = (DerivedClassForInterface)result1; 
//compiles - conversation fails at runtime 

那麼,爲什麼我不能寫自己的顯式操作符,如果你不能從基類轉換爲派生類?

有趣的是,編譯器允許我從基類轉換爲派生類,但它在運行時不起作用。

也許我會去爲簡單的功能,這將爲我「鑄造」。任何人都可以提出一個更好的解決方案(記住,我想保持「DerivedClassForInterface」服從於「Proc1Result」改變(或「Proc2Result」 - 無所謂))

編輯
@Peter Duniho - 這裏的類型「Proc1Result」和「Proc2Result」是作爲存儲過程(linq2sql)的結果生成的。我希望有一個代碼,當那些過程的輸出發生變化時,我不需要觸及代碼(因爲我們需要分割一大堆過程 - 並且實現新模塊可以並且通常會增加更多輸出)。

Proc1和Proc2基本上是相同的存儲過程(它們需要完全相同的輸入並提供相同的輸出(按類型而非數據方式))。他們都與不同的數據段一起工作,並且需要分開。

對不起,讓這個混亂的(在我的工作一天結束...),而不是澄清 - 這裏的問題居然是:

爲什麼編譯器讓我從基礎轉換爲派生類中運行時會導致異常?爲什麼我不能自己實現這個工作(...因爲它已經有了 - 但它在運行時不起作用?)

從我的立場

所以 - 它看起來如下:
- 我不能因爲它已經存在
實現這個轉換 - 然而,這注定是行不通

這裏是「最小的,完整的和可驗證的代碼示例」(感謝鏈接):

//results from stored procedures in database which got splitted appart (linq 2 sql) 
class Proc1Result { } 
class Proc2Result { } 
// 

class DerivedClassForInterface : Proc1Result 
{ 
    public static explicit operator DerivedClassForInterface(Proc2Result v) 
    { 
     //this part would be exported in generic function 
     var derivedClassInstance = new DerivedClassForInterface(); 
     var properties = v.GetType().GetProperties(); 
     foreach (var property in properties) 
     { 
      var propToSet = derivedClassInstance.GetType().GetProperty(property.Name); 
      if (propToSet.SetMethod != null) propToSet.SetValue(derivedClassInstance, property.GetValue(v)); 
     } 
     return derivedClassInstance; 
    } 
} 

interface IProcLauncher 
{ 
    DerivedClassForInterface GetNeededData(); 
} 

class ProcLauncher1 : IProcLauncher 
{ 
    public DerivedClassForInterface GetNeededData() 
    { 
     var dataFromDb = new Proc1Result();/*just ilustrative*/ 
     return (DerivedClassForInterface)dataFromDb; 
    } 
} 

class ProcLauncher2 : IProcLauncher 
{ 
    public DerivedClassForInterface GetNeededData() 
    { 
     var dataFromDb = new Proc2Result();/*just ilustrative*/ 
     return (DerivedClassForInterface)dataFromDb; 
    } 
} 


class Program 
{ 
    static void Main(string[] args) 
    { 
     bool causeInvalidCastException = true; 

     IProcLauncher procedureLauncher; 
     if (causeInvalidCastException) procedureLauncher = new ProcLauncher1(); 
     else procedureLauncher = new ProcLauncher2(); 

     var result = procedureLauncher.GetNeededData(); 
     Console.WriteLine("I got here!"); 
    } 
} 

當時的想法是:
- 無需更改任何代碼,如果程序改變輸出。
- 在運行時決定使用哪個proc。
- 將轉換部分導出爲通用功能。
- 被注射。

我可以解決這個問題 - 比如說 - 只需要一個泛型函數來處理所有情況下的對話,但問題以粗體顯示。

+0

'Proc2Result'定義在哪裏?我只看到'Proc1Result'。 –

+0

我對你的問題有點遺憾,但是如果我正確理解你的問題,那你爲什麼不把Proc1Result和Proc2Result之間的所有公共領域複製到一個通用接口。你能告訴你哪些類可以修改,哪些不能。你也可以用一些代碼來演示你的問題(不是帶有演員操作符的解決方案) – Vikhram

+0

@ rory.ap - Proc1Result和Proc2Result是由存儲過程產生的Link2Sql分類生成的。最初的要求是將他們在兩個不同的過程中進行分割(不在1個過程中)。最初我想通過強制轉換(出於某種原因,編譯器允許我將基類的實例obj轉換爲派生 - 但在運行時失敗)。 我可以在2,3 ..等更多的方式解決問題 - 但我想知道爲什麼編譯器讓我這樣做 - 但運行時沒有。 – RUKAclMortality

回答

0

我實現了轉換方式如下:

class BaseConverter 
{ 
    protected T Convert<T, X>(X result) 
    { 
     var derivedClassInstance = Activator.CreateInstance<T>(); 
     var derivedType = derivedClassInstance.GetType(); 

     var properties = result.GetType().GetProperties(); 
     foreach (var property in properties) 
     { 
      var propToSet = derivedType.GetProperty(property.Name); 
      if (propToSet.SetMethod != null) 
      { 
       propToSet.SetValue(derivedClassInstance, property.GetValue(result)); 
      } 
     } 
     return derivedClassInstance; 
    } 

    protected List<T> Convert<T, X>(List<X> listResult) 
    { 
     var derivedList = new List<T>(); 
     foreach (var r in listResult) 
     { 
      //can cope with this - since there will not ever be many iterations 
      derivedList.Add(Convert<T, X>(r)); 
     } 
     return derivedList; 
    } 
} 

所以接口實現類將繼承它:

class ProcLauncher2 : BaseConverter, IProcLauncher 
{ 
    public DerivedClassForInterface GetNeededData() 
    { 
     var dataFromDb = new Proc2Result();/*just ilustrative*/ 
     //usage (works for single result or list if I need a list returned): 
     return Convert<DerivedClassForInterface, Proc2Result>(dataFromDb); 
    } 

    //other methods... 
} 

然而 - 目前尚不清楚,我 - 爲什麼已經從投基類派生 - 如果這不起作用。伊莫 - 它不應該在那裏,並在編譯時拋出錯誤。

1

我不明白你的問題。您似乎認爲編譯器允許您編寫您發佈的代碼,但它在運行時會失敗。這不是我的經歷。我得到基類的顯式轉換操作的編譯時間錯誤:

錯誤CS0553:「Derived.explicit操作者衍生(基礎1)」:用戶定義的轉換或從基類是不允許

似乎對我很清楚。至於你爲什麼不被允許編寫這樣的代碼,你必須要求語言設計師確定地知道,但這對我來說似乎是一個合理的限制。只要基類實例實際上是派生類的實例,就已經有了從基類到該基類的派生類的安全的內置轉換。如果程序員被允許進行額外的轉換,這可能會引起混淆,並可能導致錯誤,而不必考慮使鑄造/轉換操作符的語言規範的規則複雜化。

至於更廣泛的問題,我不明白你選擇的方法。你正在設計的課程完全顛倒了人們通常會這樣做的方式。如果您有許多類都具有共享成員,那麼您希望能夠在某些上下文中將所有這些類視爲相同,並且您希望能夠僅將這些共享成員實現一次並在其他類中共享它們,你會把所有這些成員放在一個基類中,然後派生出這個類的所有不同類型。

我甚至不看這種擔心是如何你目前的做法地址:

只是讓我不需要保持這個新類(創建所有屬性,添加/刪除,如果在DB的結果有什麼變化)

由於Proc2Result不繼承Proc1Result,如果Proc1Result變化,你就必須去改變Proc2Result反正匹配。和其他類似的類型。和DerivedClassForInterface類。你必須改變所有的顯式操作符。如何更好?

我想你會喜歡的東西,如:

class BaseClassForInterface 
{ 
    // declare all shared members here 
} 

class Proc1Result : BaseClassForInterface { ... } 
class Proc2Result : BaseClassForInterface { ... } 

然後,對於每個新Proc...Result類,只需繼承的基類,無需重新編寫成員,並從每個Proc...Result轉換課是微不足道的。你甚至不需要使用轉換/轉換操作符;該語言已知道如何從派生類隱式轉換爲基類,因爲派生類是基類的

這實際上是使用OOP的標準方式。這是任何OOP語言的最基本特徵之一。

如果這樣不能使您回到正軌,您需要改進問題,以便更清楚您正在做什麼以及爲什麼。您還需要提供一個很好的Minimal, Complete, and Verifiable code example,它清楚地說明了您的問題,並精確地解釋了代碼的作用以及您希望它執行的操作。

附錄:

感謝您的編輯。你的問題現在更具體和明確。我仍然有疑問,但至少我瞭解真實的情況。

在我看來,你已經瞭解最基礎的回答你的問題:

爲什麼編譯器讓我從基礎轉換爲派生類中運行時產生的異常?爲什麼我無法實現這個鑄造自己(...因爲它還挺已經沒有 - 但它只是在運行時不工作?)

從我站的地方

所以 - 它看起來如下:
- 我可以由於它已經存在,所以沒有執行此演職員表
- 但它註定不能工作

iee是的,我相信這種語言不允許這樣做,因爲已經有一個內置的演員陣容,是的,你尋求的確切方法註定是行不通的。

至於這部分雲:

當時的想法是:
- 無需更改任何代碼,如果程序改變輸出。
- 在運行時決定使用哪個proc。
- 將轉換部分導出爲通用功能。
- 被注射。

如果我沒有理解第一點,這就是爲什麼你繼承了存儲過程類型一個。這樣你就可以免費獲得財產聲明。對我來說似乎有點冒險,但我承認我確實瞭解動機。

正如我所理解的上述第三點和您在發佈後的聲明,您已經知道如何編寫通用方法來完成轉換。例如。例如:

DerivedClassForInterface ConvertToClassForInterface<T>(T t) 
{ 
    DerivedClassForInterface result = new DerivedClassForInterface(); 
    Type resultType = typeof(DerivedClassForInterface); 
    PropertyInfo[] properties = typeof(T).GetProperties(); 

    foreach (var property in properties) 
    { 
     var propToSet = resultType.GetProperty(property.Name); 
     if (propToSet.SetMethod != null) 
     { 
      propToSet.SetValue(result, property.GetValue(t)); 
     } 
    } 
    return result; 
} 

即,基本上是你在顯式操作符中顯示的代碼(有一些小的清理/優化)。或者,也許你沒有直接使用「通用」一詞,而只是指「通用」。很顯然,上述方法很少從通用方法中獲益,您可以像使用顯式運算符一樣輕鬆地在參數上使用GetType()

不幸的是,我不知道標準「是注射」適合在這裏。如何注射?你的意思是你想要在其他地方注入代碼嗎?或者你的意思是代碼需要與AOP系統兼容,否則其他形式的代碼注入應用呢?我實際上只是利用編譯器和運行時爲我做了所有繁重的工作(包括緩存反射的東西,在你的代碼中它會很慢),但是我不明白這個部分, 。你可以寫這樣的一類:

class Wrapper 
{ 
    private dynamic _data; 

    public string Value { get { return _data.Value; } } 

    public Wrapper(dynamic data) 
    { 
     _data = data; 
    } 
} 

鑑於這樣的幾個其他類:

class Result1 
{ 
    public string Value { get; set; } 
} 

class Result2 
{ 
    public string Value { get; set; } 
} 

然後你可以使用它像這樣:

Result1 r1 = new Result1 { Value = "result 1" }; 
Result2 r2 = new Result2 { Value = "result 2" }; 
Wrapper w1 = new Wrapper(r1), w2 = new Wrapper(r2); 

Console.WriteLine("w1 result: " + w1.Value); 
Console.WriteLine("w2 result: " + w2.Value); 

即只需創建一個Wrapper的實例,傳遞相關對象(對於您的情況,這將是存儲過程中生成的類型)。當然,不利的一面是,您必須將屬性添加到Wrapper類型才能與存儲過程相匹配。但我不相信這是一件壞事。即使你以某種方式安排它,以便其餘的代碼都不必更改,但這是一項相對較小的維護任務。

我懷疑改變存儲過程需要改變代碼中的其他地方,以明確地引用屬性。因爲畢竟,如果代碼的其餘部分對於特定的類成員完全不可知(即一直使用反射),那麼你可以傳遞結果對象作爲object類型,而不用擔心包裝器。

+0

感謝您的重播。這就是你寫的所有非常好的東西,通常這就是我所做的,但這裏Proc1Result和Proc2Result是從存儲過程生成的類。我用更多的解釋和可測試的代碼編輯了原文。 – RUKAclMortality

+0

@RUKAclMortality:好的,我試圖根據您的附加信息擴展我的答案。請參閱上面的修改。 –

+0

@ Peter Duniho - 感謝您的提示。 1)對於「注入」,我的意思是你需要能夠注入實現的接口類的實例,無論你需要它(通過函數作爲參數fe)2)是 - 與「通用」我的意思是實際上「通用」我已經添加了我的實現作爲答案3)事情是 - 這個代碼是在一個圖書館,然後「分佈」,並在其他程序中使用。 – RUKAclMortality