Answers:
进行此工作的方法是遍历列表并转换元素。这可以使用ConvertAll来完成:
List<A> listOfA = new List<C>().ConvertAll(x => (A)x);
您还可以使用Linq:
List<A> listOfA = new List<C>().Cast<A>().ToList();
ConvertAll
还是Cast
?
首先,停止使用无法理解的类名,例如A,B,C。使用Animal,哺乳动物,长颈鹿或Food,Fruit,Orange或关系明确的东西。
那么,您的问题是“为什么不能将长颈鹿列表分配给动物类型列表的变量,因为我可以将长颈鹿分配给动物类型的变量?”
答案是:假设可以。那会出什么问题呢?
好了,您可以将老虎添加到动物列表中。假设我们允许您将长颈鹿列表放入包含动物列表的变量中。然后,您尝试将老虎添加到该列表。怎么了?您是否希望长颈鹿清单中包含老虎?你想撞车吗?还是您想让编译器通过首先将分配设为非法来保护您免受崩溃的影响?
我们选择后者。
这种转换称为“协变”转换。在C#4中,当已知转换始终是安全的时,我们将允许您在接口和委托上进行协变转换。有关详细信息,请参见我的有关协方差和协方差的博客文章。(本周的星期一和星期四将有一个与此主题相关的新主题。)
IEnumerable
而不是进行此转换List
?即:List<Animal> listAnimals = listGiraffes as List<Animal>;
不可能,但是IEnumerable<Animal> eAnimals = listGiraffes as IEnumerable<Animal>
有效。
IEnumerable<T>
和IEnumerator<T>
都被标记为对协方差是安全的,并且编译器已对此进行了验证。
引用埃里克的出色解释
怎么了?您是否希望长颈鹿清单中包含老虎?你想撞车吗?还是您想让编译器通过首先将分配设为非法来保护您免受崩溃的侵害?我们选择后者。
但是,如果要选择运行时崩溃而不是编译错误怎么办?通常,您将使用Cast <>或ConvertAll <>,但是会遇到两个问题:它将创建列表的副本。如果您在新列表中添加或删除某些内容,则该内容不会反映在原始列表中。其次,由于它会使用现有对象创建一个新列表,因此会带来很大的性能和内存损失。
我遇到了同样的问题,因此我创建了一个包装器类,该类可以转换通用列表而无需创建全新的列表。
在原始问题中,您可以使用:
class Test
{
static void Main(string[] args)
{
A a = new C(); // OK
IList<A> listOfA = new List<C>().CastList<C,A>(); // now ok!
}
}
这里是包装器类(+一个易于使用的扩展方法CastList)
public class CastedList<TTo, TFrom> : IList<TTo>
{
public IList<TFrom> BaseList;
public CastedList(IList<TFrom> baseList)
{
BaseList = baseList;
}
// IEnumerable
IEnumerator IEnumerable.GetEnumerator() { return BaseList.GetEnumerator(); }
// IEnumerable<>
public IEnumerator<TTo> GetEnumerator() { return new CastedEnumerator<TTo, TFrom>(BaseList.GetEnumerator()); }
// ICollection
public int Count { get { return BaseList.Count; } }
public bool IsReadOnly { get { return BaseList.IsReadOnly; } }
public void Add(TTo item) { BaseList.Add((TFrom)(object)item); }
public void Clear() { BaseList.Clear(); }
public bool Contains(TTo item) { return BaseList.Contains((TFrom)(object)item); }
public void CopyTo(TTo[] array, int arrayIndex) { BaseList.CopyTo((TFrom[])(object)array, arrayIndex); }
public bool Remove(TTo item) { return BaseList.Remove((TFrom)(object)item); }
// IList
public TTo this[int index]
{
get { return (TTo)(object)BaseList[index]; }
set { BaseList[index] = (TFrom)(object)value; }
}
public int IndexOf(TTo item) { return BaseList.IndexOf((TFrom)(object)item); }
public void Insert(int index, TTo item) { BaseList.Insert(index, (TFrom)(object)item); }
public void RemoveAt(int index) { BaseList.RemoveAt(index); }
}
public class CastedEnumerator<TTo, TFrom> : IEnumerator<TTo>
{
public IEnumerator<TFrom> BaseEnumerator;
public CastedEnumerator(IEnumerator<TFrom> baseEnumerator)
{
BaseEnumerator = baseEnumerator;
}
// IDisposable
public void Dispose() { BaseEnumerator.Dispose(); }
// IEnumerator
object IEnumerator.Current { get { return BaseEnumerator.Current; } }
public bool MoveNext() { return BaseEnumerator.MoveNext(); }
public void Reset() { BaseEnumerator.Reset(); }
// IEnumerator<>
public TTo Current { get { return (TTo)(object)BaseEnumerator.Current; } }
}
public static class ListExtensions
{
public static IList<TTo> CastList<TFrom, TTo>(this IList<TFrom> list)
{
return new CastedList<TTo, TFrom>(list);
}
}
如果您使用 IEnumerable
改用它,它将起作用(至少在C#4.0中,我没有尝试过以前的版本)。这只是演员表,当然,它仍然是列表。
代替 -
List<A> listOfA = new List<C>(); // compiler Error
在问题的原始代码中,使用-
IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
List<A> listOfA = new List<C>(); // compiler Error
在问题的原始代码中,输入IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
至于为什么它不起作用,了解协方差和协方差可能会有所帮助。
只是为了说明为什么这不应该工作,这里是一个改变你所提供的代码:
void DoesThisWork()
{
List<C> DerivedList = new List<C>();
List<A> BaseList = DerivedList;
BaseList.Add(new B());
C FirstItem = DerivedList.First();
}
应该行吗?列表中的第一项的类型为“ B”,但DerivedList项的类型为C。
现在,假设我们真的只是想做一个通用函数,该函数在实现A的某种类型的列表上操作,但是我们不在乎是什么类型:
void ThisWorks<T>(List<T> GenericList) where T:A
{
}
void Test()
{
ThisWorks(new List<B>());
ThisWorks(new List<C>());
}
您只能转换为只读列表。例如:
IEnumerable<A> enumOfA = new List<C>();//This works
IReadOnlyCollection<A> ro_colOfA = new List<C>();//This works
IReadOnlyList<A> ro_listOfA = new List<C>();//This works
而且,对于支持保存元素的列表,您不能这样做。原因是:
List<string> listString=new List<string>();
List<object> listObject=(List<object>)listString;//Assume that this is possible
listObject.Add(new object());
现在怎么办?请记住,listObject和listString实际上是相同的列表,因此listString现在具有object元素-应该是不可能的,而不可能。
这是BigJim出色答案的扩展。
在我的情况下,我有一NodeBase
类带有Children
字典的类,并且我需要一种从子类进行O(1)查找的方法。我试图在的getter中返回私有字典字段Children
,因此显然我想避免昂贵的复制/迭代。因此,我使用Bigjim的代码将强制Dictionary<whatever specific type>
转换为泛型Dictionary<NodeBase>
:
// Abstract parent class
public abstract class NodeBase
{
public abstract IDictionary<string, NodeBase> Children { get; }
...
}
// Implementing child class
public class RealNode : NodeBase
{
private Dictionary<string, RealNode> containedNodes;
public override IDictionary<string, NodeBase> Children
{
// Using a modification of Bigjim's code to cast the Dictionary:
return new IDictionary<string, NodeBase>().CastDictionary<string, RealNode, NodeBase>();
}
...
}
这很好。但是,我最终遇到了不相关的限制,最终FindChild()
在基类中创建了一个抽象方法来代替查找。事实证明,这首先消除了对强制转换字典的需求。(IEnumerable
出于我的目的,我能够用一个简单的替换它。)
因此,您可能会问的问题(尤其是性能是否是禁止您使用.Cast<>
或的问题.ConvertAll<>
)是:
“我真的需要转换整个集合吗,还是可以使用抽象方法来保存执行任务所需的特殊知识,从而避免直接访问集合?”
有时最简单的解决方案是最好的。
您还可以使用System.Runtime.CompilerServices.Unsafe
NuGet包创建对相同对象的引用List
:
using System.Runtime.CompilerServices;
...
class Tool { }
class Hammer : Tool { }
...
var hammers = new List<Hammer>();
...
var tools = Unsafe.As<List<Tool>>(hammers);
给定上面的示例,您可以Hammer
使用tools
变量访问列表中的现有实例。将Tool
实例添加到列表会引发ArrayTypeMismatchException
异常,因为tools
引用的变量与相同hammers
。
我已经阅读了整个主题,我只想指出对我来说似乎是矛盾的。
编译器阻止您使用列表进行分配:
List<Tiger> myTigersList = new List<Tiger>() { new Tiger(), new Tiger(), new Tiger() };
List<Animal> myAnimalsList = myTigersList; // Compiler error
但是编译器可以很好地使用数组:
Tiger[] myTigersArray = new Tiger[3] { new Tiger(), new Tiger(), new Tiger() };
Animal[] myAnimalsArray = myTigersArray; // No problem
关于分配是否已知是安全的争论在这里分开了。我对数组所做的分配并不安全。为了证明这一点,如果我对此进行跟进:
myAnimalsArray[1] = new Giraffe();
我得到一个运行时异常 “ ArrayTypeMismatchException”。如何解释这一点?如果编译器确实希望阻止我执行某些愚蠢的操作,则它应该阻止了我执行数组分配。