2013-06-05 23 views
0

爲什麼我會爲下面的一段代碼獲得類型轉換編譯錯誤?使用通用助手類隱式強制類

我已經在我的項目衍生防守/視圖類的好幾件事。他們都有一些代碼庫,比如說持久性,檢索等。我認爲通過編寫一個使用泛型的助手類可以實現這個通用代碼庫的可維護性。

但是我得到了對分配以高清行DoSomeStuff法「類型轉換」編譯錯誤。我已經小心地爲所有基類和派生類編寫隱式強制轉換。

注意防守&視圖類故意不從一些常見的類派生。此外,我總是希望只從View轉換爲Def,而不是其他方式,因此只有我的View類具有在其上定義的隱式轉換。

我的確嘗試遵循Eric Lipert關於協變性和逆變性的討論,但隨着他的例子的進展,我的腦海裏變得相當混亂。任何有關這個問題的幫助,不勝感激。

public class BaseDef 
{ 
    public int Id { get; set; } 
} 

public class DerivedDef : BaseDef 
{ 
    public string Name { get; set; } 

    public DerivedDef() 
     : base() 
    { 

    } 

    public DerivedDef(BaseDef bd) 
    { 
     this.Id = bd.Id; 
    } 
} 

public class BaseView 
{ 
    public int Id { get; set; } 

    public BaseView() 
    { 

    } 

    public BaseView(BaseDef bd) 
    { 
     Id = bd.Id; 
    } 

    public BaseDef ToBaseDef() 
    { 
     return new BaseDef { Id = this.Id }; 
    } 

    public static implicit operator BaseView(BaseDef bd) 
    { 
     return new BaseView(bd); 
    } 

    public static implicit operator BaseDef(BaseView bv) 
    { 
     return bv.ToBaseDef(); 
    } 
} 

public class DerivedView : BaseView 
{ 
    public string Name { get; set; } 

    public DerivedView() 
     : base() 
    { 

    } 

    public DerivedView(DerivedDef dd) 
     : base(dd) 
    { 
     Name = this.Name; 
    } 

    public DerivedDef ToDerivedDef() 
    { 
     return new DerivedDef(this) 
     { 
      Name = this.Name, 
     }; 
    } 
} 

public class SomeHelper<Tdef, Tview> 
    where Tdef : BaseDef 
    where Tview : BaseView 
{ 
    public void DoSomeStuff(Tview vm) 
    { 
     Tdef df = vm; // this line give a compile error 'Cannot convert type 'Tview' to 'Tdef' 
     // work with df from here on 
    } 
} 
+2

你給的代碼編譯罰款。你的意思是將'df'聲明爲'Tdef'類型而不是'var'嗎? (順便提一下,我懷疑你可以大大簡化你的例子。) –

+0

你確定這是你得到的確切線和錯誤?我不明白這是如何可能的,因爲「df」沒有明確輸入。你使用了「var」,所以它的類型只是右手邊的暗示。那裏不會有任何轉換。 –

+0

@Jon,你說得對。我的錯誤,我糾正了這條線。我會非常感興趣的聽你的想法... – kapil

回答

2

有沒有保證,有轉換到Tdef。肯定有一個轉換BaseDef,編譯器將使用轉換:

BaseDef df = vm; // This is fine 

...但是這不是一回事。

在這種情況下,轉換實際上是會返回一個BaseDef反正 - 有無轉換操作符從DerivedViewDerivedDef ...有一個方法(ToDerivedDef),但有沒有在你的代碼,會調用它。即使轉換在這種情況下存在,編譯器也不能保證它存在。

你可以使用:

Tdef df = (Tdef) (BaseDef) vm; 

...這將執行用戶定義的轉換到BaseDef,然後正常投地Tdef - 這將無法在你的執行時間,但可以工作如果轉換稱爲合適的虛擬方法。不過,在編譯時不能保證

+0

謝謝喬恩。如果我在DerivedView上添加一個隱式轉換,那會起作用嗎? – kapil

+0

@ kapil:不,因爲編譯器不知道它在那裏。通用方法是編譯的(從源代碼到IL)*一次*,而不是每個類型參數組合一次。你可以考慮製作'BaseDef'和'BaseView'通用的,這樣你就可以擁有'class DerivedDef:BaseDef ',反之亦然,'BaseDef'中的抽象'ToView'方法(返回'TView')反之亦然。然後你可以使用'Tdef df = vm.ToDefinition()'或者任何你想要的東西......約束爲Tview:BaseView '和'Tdef:BaseDef '。 –

+0

您對關於泛型方法編譯的評論清除了我的困惑。我還有一些其他的限制,使你的建議basedef 作爲一個選項不可行。我會嘗試一下你的建議,只是爲了更好地理解它。 – kapil

0

我真的無法使用Jon的方法,因爲我有分層約束。 def模型是在數據庫層中定義的,而UI層中的視圖模型則是定義的。

但是,從Jon的評論中得到靈感,我如何解決手頭的問題是在所有視圖模型上添加隱式轉換,並在處理轉換爲&的輔助類中暴露了兩個屬性。這是最後的代碼看起來...

public class BaseDef 
{ 
    public int Id { get; set; } 

    public override string ToString() 
    { 
     return Id.ToString(); 
    } 
} 

public class BaseView 
{ 
    public int Id { get; set; } 

    public BaseView() 
    { 

    } 

    public BaseView(BaseDef bd) 
    { 
     Id = bd.Id; 
    } 

    public BaseDef ToBaseDef() 
    { 
     return new BaseDef { Id = this.Id }; 
    } 

    public static implicit operator BaseView(BaseDef bd) 
    { 
     return new BaseView(bd); 
    } 

    public static implicit operator BaseDef(BaseView bv) 
    { 
     return bv.ToBaseDef(); 
    } 
} 

public class DerivedDef : BaseDef 
{ 
    public string Name { get; set; } 

    public DerivedDef() 
     : base() 
    { 

    } 

    public DerivedDef(BaseDef bd) 
    { 
     this.Id = bd.Id; 
    } 
} 

public class DerivedView : BaseView 
{ 
    public string Name { get; set; } 

    public DerivedView() 
     : base() 
    { 

    } 

    public DerivedView(DerivedDef dd) 
     : base(dd) 
    { 
     Name = this.Name; 
    } 

    public DerivedDef ToDerivedDef() 
    { 
     return new DerivedDef((BaseView)this) 
     { 
      Name = this.Name, 
     }; 
    } 

    public static implicit operator DerivedView(DerivedDef dd) 
    { 
     return new DerivedView(dd); 
    } 

    public static implicit operator DerivedDef(DerivedView dv) 
    { 
     return dv.ToDerivedDef(); 
    } 
} 

public class SomeHelper<Tdef, Tview> 
    where Tdef : BaseDef 
    where Tview : BaseView 
{ 
    public Func<Tview, Tdef> ConvertToDef { get; set; } 
    public Func<Tdef, Tview> ConvertToView { get; set; } 
    public Tdef Convert(Tview vm) 
    { 
     if (ConvertToDef == null) 
     { 
      throw new ArgumentNullException("ConvertToDef uninitialized"); 
     } 
     return ConvertToDef(vm); 
    } 

    public Tview Convert(Tdef dm) 
    { 
     if (ConvertToView == null) 
     { 
      throw new ArgumentNullException("ConvertToView uninitialized"); 
     } 
     return ConvertToView(dm); 
    } 
} 

消費代碼看起來像這樣...

private static void TestCastWithGenerics() 
    { 
     BaseDef bd = new BaseDef() 
     { 
      Id = 1 
     }; 

     DerivedView dv = new DerivedView() 
     { 
      Id = 1, 
      Name = "DV", 
     }; 
     var aClassD = new SomeHelper<DerivedDef, DerivedView>(); 
     aClassD.ConvertToDef = dv1 => dv1; // Behind scenes the implicit cast is being invoked... 
     DerivedDef dd = aClassD.Convert(dv); 

     var aClassB = new SomeHelper<BaseDef, BaseView>(); 
     aClassB.ConvertToView = bd1 => bd1; // Behind scenes the implicit cast is being invoked... 
     BaseView bv = aClassB.Convert(bd); 

     Console.WriteLine(dd); 
     Console.WriteLine(bv); 
    }