我正在关注:
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
答案 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,并且只能在一个方向上进行。泛型的两种类型是协方差,其中函数的输出可能比声明的变量类型更精确;相反的协方差,函数的输入可能比声明的变量类型更精确。
如果您的界面需要同时对同一个变量执行操作,那么您就不走运了。