2016-06-28 24 views
2

我有這樣的代碼:轉換包裝<Derived>爲包裝<Base>

class Base { } 

class Derived : Base { } 

class Wrapper<T> { 

    public T Value { get; } 

    public Wrapper (T value) { Value = value; } 
} 

我想用Wrapper這樣的:

Wrapper<Base> wrapper = new Wrapper<Derived> (new Derived()); 

但與此錯誤結束:

Error CS0029 Cannot implicitly convert type 'Wrapper<Derived>' to 'Wrapper<Base>'

我試過創建方法Wrapperclass將作爲轉換器

public Wrapper<TResult> To<TResult>() /* Constraints needed here. */ => 
    new Wrapper<TResult> (Value); 

但我錯過了一些有效的約束。當前代碼結束了錯誤:

S1503 Argument 1: cannot convert from 'T' to 'TResult'

我會想象在To方法約束可能看起來像where T : TResult,但這不是有效的約束。

任何方式實現轉換器從Wrapper<Derived>Wrapper<Base>容易嗎?

+1

您是否嘗試過'包裝包裝=新的包裝(新派生的());'? – Eli

+0

這會在你預先創建對象的情況下工作,但是在它看起來像這樣的情況下:'var wrapper = new Wrapper (new Derived());/*其他代碼/ */var newWrapper = wrapper;'不會。 –

回答

4

你可以使用協方差像這樣:

class Base { } 

class Derived : Base { } 

interface IWrapper<out T> 
{ 
    T Value { get; } 
} 

class Wrapper<T> : IWrapper<T> 
{ 
    public T Value { get; private set; } 

    public Wrapper(T value) { Value = value; } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     IWrapper<Base> wrapper = new Wrapper<Derived>(new Derived()); 
    } 
} 
+0

這看起來像要走的路。當然,我想避免引入新的'interface',但如果沒有其他建議,我會這樣做。 –

+0

不幸的是,協變和逆變只適用於接口和委託,所以如果你想使用這種方法,你必須使用接口方法。 – Tsef

+0

是的,已經在'class'上嘗試過協變了,但那不起作用。 –

3

起初我會約束添加到類,要求T必須Base類型:

class Base { } 
class Derived : Base { } 

class Wrapper<T> where T : Base // T must be (derived from) Base 
{ 
    public T Value { get; } 

    public Wrapper (T value) { Value = value; } 
} 

其次,通用轉換器將危險。如果有人試圖將Wrapper<Gnu>轉換爲Wrapper<Lion>,該怎麼辦?

所以我退後一步,讓非通用轉換器,簡單地轉換爲Wrapper<Base>

public Wrapper<Base> ToBase() 
{ 
    return new Wrapper<Base>(Value);  
} 

而且這個工作,因爲在一流水平約束的T的。


C#實際上是一種以高級別安全性着稱的語言。但你可以避開它,你在評論問由ommitting任何約束,只是想投什麼進來:

public Wrapper<TResult> To<TResult>() where TResult : class 
{ 
    return new Wrapper<TResult>(Value as TResult);  
} 

您需要class約束和as運營商,因爲兩者之間有直接投泛型參數是不可編譯的(因爲IL太依​​賴於特定的類型)。

但是,如果類型不匹配,這將返回Wrapper個實例,其中Value設置爲null。它也可以用派生類型而不是基類型。所以保重。你可以添加一些額外的檢查。並採取了角馬:)照顧


UPDATE:

一個更安全的方式:

public Wrapper<TResult> To<TResult>() where TResult : class// TResult must also be (derived from) Base 
{ 
    if (!typeof(TResult).IsAssignableFrom(typeof(T))) 
     throw new InvalidCastException(); 
    return new Wrapper<TResult>(Value as TResult); 
} 

這就驗證了TTResult派生並引發InvalidCastException如果不是。您可以根據自己的需求進行改進。

+0

如果我想'Wrapper'保持通用並能夠採取任何類型?我想從'Wrapper '轉換成'Wrapper '。 –

+0

@DovydasSopa更新我的回答 –

+0

正如你指出的那樣,在使用'as'之前需要進行一些類型檢查。 –

0

您遇到的問題是,泛型類型Wrapper<Base>Wrapper<Derived>是兩個完全不同的類爲.NET Framework

你可以做什麼正在創建Base類型的新Wrapper

Wrapper<Base> wrapper = new Wrapper<Base>(new Derived()); 

還是要COMPLE TE你到方法的方法:

public Wrapper<TResult> To<TResult>() where TResult : T 
    => new Wrapper<TResult>((TResult)Value); // This could throw an error 

public bool TryCastTo<TResult>(out Wrapper<TResult> derivedWrapper) where TResult : T 
{ 
    derivedWrapper = null; 

    // EDIT: changed to the version from René Vogt since this is much cleaner and mine had a little error 
    if (!typeof(T).IsAssignableFrom(typeof(TResult))) 
    { 
     return false; 
    } 

    derivedWrapper = new Wrapper<TResult>((TResult)Value); 
    return true; 
} 

的用法是:

Wrapper<Derived> derivedWrapper1 = wrapper.To<Derived>(); 
Wrapper<Derived> derivedWrapper2; 
bool success = wrapper.TryCastTo<Derived>(out derivedWrapper2); 
相關問題