将Wrapper <derived>转换为Wrapper <base />

时间:2016-06-28 06:17:34

标签: c# generics

我有这样的代码:

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 ());

但它最终会出现这个错误:

  

错误CS0029无法隐式转换类型&#39; Wrapper&lt; Derived&gt;&#39;到&#39; Wrapper&lt; Base&gt;&#39;

我尝试在Wrapper class中创建一个充当转换器的方法

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

但我错过了一些有效的约束。当前代码最终出现错误:

  

S1503参数1:无法转换为&#39; T&#39; to&#39; TResult&#39;

我认为To方法的约束可能看起来像where T : TResult,但这不是有效约束。

有哪些方法可以轻松地将转换器从Wrapper<Derived>实现到Wrapper<Base>

3 个答案:

答案 0 :(得分: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());
    }
}

答案 1 :(得分: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#实际上是一种高级别安全性的语言。但是你可以通过省略任何约束并试图抛出任何内容来绕过它并在评论中做你要求的事情:

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

您需要class约束和as运算符,因为两个通用参数之间的直接转换不可编译(因为IL过分依赖于特定类型)。

但是如果类型不匹配,这将返回WrapperValue个实例,设置为null。它也适用于派生类型而不是基类型。所以要小心。您可以为此添加一些额外的检查。并照顾gnus:)

更新:

更安全的方式:

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);
}

这会检查T是否来自TResult,如果没有,则会引发InvalidCastException。您可以根据自己的需要进行优化。

答案 2 :(得分:0)

您遇到的问题是泛型类型Wrapper<Base>Wrapper<Derived>是.NET Framework的两个完全不同的类

您可以做的是创建Wrapper类型的新Base

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

或完成您的To方法方法:

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);