带有泛型的C#类型转换自定义对象

时间:2018-08-02 19:27:09

标签: c# generics

我正在关注:

subObject,superObject,其中subObject是superObject的子类。

我总是可以将子对象上传到超对象,但是我不能执行以下操作:

wrapperObject<superObject> instance = (wrapperObject<superObject>) wrraperObject<subObject> instance2;

这是带有通用列表的示例:

            List<Object> l1 = null;
            List<Boolean> l2 = null;
            l1 = (Object)l2; <<< This will not work

            Object o1 = null;
            Boolean o2 = false;
            o1 = (Object)o2; <<< This works

我了解在使用列表的情况下,我可以遍历列表中的所有对象并分别进行类型转换。 但这在我的自定义类“ wrapperObject”的情况下不起作用

        wrapperObject<superObject> l1;
        wrapperObject<subObject> l2;
        l1 = (wrapperObject<superObject>)l2;  <<< this doesnt work


        superObject a = null;
        subObject n = null;
        a = (superObject)n;  <<< this works

3 个答案:

答案 0 :(得分:4)

让我们先假设这是可能的

List<bool> lb = new List<bool>();
List<object> lo = (List<object>)lb;

现在,我们可以做到

lo.Add(123); // Since lo is typed as List<object>

但是lo只是指向List<Boolean> lb的引用。砰!

对此类型问题的一种解决方法是拥有一个非通用的基本类型(类或接口),并从中派生一个通用类型。例如,List<T>实现ICollection<T>,而IEnumerable<T>实现IEnumerable。也就是说,此分配有效:

IEnumerable e = new List<bool>();

请注意,您可以使用数组进行这种类型的转换。也就是说,您可以分配

object[] obj = new Person[10];

我们必须为此付出的代价是效率,因为分配数组元素时会执行类型测试。如果我们分配了不兼容的值,则此类型测试可能会引发异常。请参阅:Array Covariance

上的Eric Lippert的博客

答案 1 :(得分:1)

Oliver的答案是完全正确的,它解释了为什么您根本无法做到这一点。但是我可以想到一个指导性的解决方法,该方法除了可以帮助您实现所需的目标外,还可以帮助您更好地了解.Net中的协方差和逆方差。考虑以下类:

class SuperObject { }

class SubObject : SuperObject { }

class WrapperObject<T>:IContravariantInterface<T>,ICovariantInterface<T> where T : SuperObject
{
    public void DoSomeWork(T obj)
    {
        //todo
    }

    public T GetSomeData()
    {
        //todo
        return default;
    }
}

我们使Wrapper实现两个接口:IContravariantInterface<T>ICovariantInterface<T>。他们是这些人

interface IContravariantInterface<in T> where T : SuperObject
{
    void DoSomeWork(T obj);
}

interface ICovariantInterface<out T> where T : SuperObject
{
    T GetSomeData();
}

通过执行此操作,我们将包装器功能分为两部分:协变部分和逆变部分。为什么要这样做?因为这样做,我们可以安全地将大多数派生类转换为较少的类,或者在我们使用正确接口的条件下安全地进行强制转换:

    var superObjectWrapper = new WrapperObject<SuperObject>();
    var subObjectWrapper = new WrapperObject<SubObject>();

    ICovariantInterface<SuperObject> covariantSuperObjWrapper = subObjectWrapper;
    IContravariantInterface<SuperObject> contravariantSuperObjWrapper = subObjectWrapper; //does not compile

    ICovariantInterface<SubObject> covariantSubObjWrapper = superObjectWrapper; //does not compile
    IContravariantInterface<SubObject> contravariantSubObjWrapper = superObjectWrapper; 

通过强制转换到这些接口,您可以确保只能访问那些可安全用于强制转换的方法

编辑

基于以下OP的评论,请考虑在Wrapper类中编写转换器逻辑。看一下以下重构的WrapperObject:

class WrapperObject<T> where T : SuperObject
{
    private T _justATestField;

    public void Copy<TType>(WrapperObject<TType> wrapper) where TType : SuperObject
    {
        if (wrapper._justATestField is T tField)
        {
            _justATestField = tField;
        }
    }

    public WrapperObject<SuperObject> GetBaseWrapper()
    {
        var baseWrapper = new WrapperObject<SuperObject>();
        baseWrapper.Copy(this);
        return baseWrapper;
    }
}

现在您可以这样做:

    var subObjectWrapper = new WrapperObject<SubObject>();
    WrapperObject<SuperObject> superObjectWrapper = subObjectWrapper.GetBaseWrapper();

答案 2 :(得分:0)

您要询问的是Variant Generics。目前,C#仅允许接口上的Variant Generics,并且只能在一个方向上进行。泛型的两种类型是协方差,其中函数的输出可能比声明的变量类型更精确;相反的协方差,函数的输入可能比声明的变量类型更精确。

如果您的界面需要同时对同一个变量执行操作,那么您就不走运了。