2016-06-28 71 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); 
相关问题