虽然我们可以从基类/接口继承,但为什么我们不能声明List<>
使用相同的类/接口?
interface A
{ }
class B : A
{ }
class C : B
{ }
class Test
{
static void Main(string[] args)
{
A a = new C(); // OK
List<A> listOfA = new List<C>(); // compiler Error
}
}
有办法吗?
答案 0 :(得分:204)
使这项工作的方法是迭代列表并转换元素。这可以使用ConvertAll来完成:
List<A> listOfA = new List<C>().ConvertAll(x => (A)x);
你也可以使用Linq:
List<A> listOfA = new List<C>().Cast<A>().ToList();
答案 1 :(得分:150)
首先,停止使用不可理解的类名,如A,B,C。使用动物,哺乳动物,长颈鹿或食物,水果,橙色或其他关系清晰的东西。
你的问题是“为什么我不能将长颈鹿列表分配给动物类型变量的变量,因为我可以将长颈鹿分配给动物类型的变量?”
答案是:假设你可以。什么可能会出错?
好吧,您可以将Tiger添加到动物列表中。假设我们允许您将长颈鹿列表放在一个包含动物列表的变量中。然后你尝试将老虎添加到该列表中。怎么了?你想要长颈鹿的名单包含一只老虎吗?你想要崩溃吗?或者您是否希望编译器通过首先使分配非法来保护您免受崩溃?
我们选择后者。
这种转换称为“协变”转换。在C#4中,我们将允许您在接口上进行协变转换,并在知道转换始终是安全的时委托。有关详细信息,请参阅我关于协方差和逆变的博客文章。 (本周一和周四的这个主题将会有一个新的。)
答案 2 :(得分:50)
引用Eric的精彩解释
会发生什么?你想要长颈鹿的名单包含一只老虎吗?你想要崩溃吗?或者您是否希望编译器通过首先使分配非法来保护您免受崩溃? 我们选择后者。
但是如果你想选择运行时崩溃而不是编译错误怎么办?您通常会使用Cast&lt;&gt;或ConvertAll&lt;&gt;但是你会遇到两个问题:它会创建一个列表副本。如果在新列表中添加或删除某些内容,则不会在原始列表中反映出来。其次,由于它使用现有对象创建了一个新列表,因此会有很大的性能和内存损失。
我遇到了同样的问题,因此我创建了一个包装类,可以在不创建全新列表的情况下转换通用列表。
在原始问题中,您可以使用:
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);
}
}
答案 3 :(得分:24)
至于为什么它不起作用,理解covariance and contravariance可能会有所帮助。
为了说明为什么不应该工作,这里是对您提供的代码的更改:
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>());
}
答案 4 :(得分:22)
如果您使用IEnumerable
,它将起作用(至少在C#4.0中,我没有尝试过以前的版本)。这只是演员,当然,它仍然是一个列表。
而不是 -
List<A> listOfA = new List<C>(); // compiler Error
在问题的原始代码中,使用 -
IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
答案 5 :(得分:13)
您只能投射到只读列表。例如:
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现在具有对象元素 - 它应该是不可能的,而不是。
答案 6 :(得分:1)
我个人喜欢创建带有类扩展的库
public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
where TFrom : class
where TTo : class
{
return fromlist.ConvertAll(x => x as TTo);
}
答案 7 :(得分:0)
因为C#不允许这种类型的继承转换at the moment。
答案 8 :(得分:0)
这是BigJim辉煌answer的扩展。
在我的情况下,我有一个NodeBase
类,带有Children
字典,我需要一种方法来一般地从孩子那里进行O(1)查找。我试图在Children
的getter中返回一个私有字典字段,所以显然我想避免昂贵的复制/迭代。因此,我使用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<>
的问题):
&#34;我是否真的需要整个集合,或者我是否可以使用抽象方法来保存执行任务所需的特殊知识,从而避免直接访问集合?&#34;
有时最简单的解决方案是最好的。
答案 9 :(得分:0)
您还可以使用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
相同。
答案 10 :(得分:0)
我已经阅读了整个主题,我只想指出对我来说似乎不一致的地方。
编译器阻止您使用列表进行分配:
Tiger[] myTigersArray = new Tiger[3] { new Tiger(), new Tiger(), new Tiger() };
Animal[] myAnimalsArray = myTigersArray; // No problem
但是编译器可以很好地使用数组:
myAnimalsArray[1] = new Giraffe();
关于分配是否为已知是安全的的争论在这里分开了。我对数组所做的分配不安全。为了证明这一点,如果我继续进行以下操作:
{{1}}
我得到一个运行时异常“ ArrayTypeMismatchException”。如何解释这一点?如果编译器真的想阻止我做一些愚蠢的事情,它应该阻止了我执行数组分配。
答案 11 :(得分:0)
dynamic
Array
IReadOnlyList
, IEnumerable
正确使用 List<>
。
它们都运行良好!不需要任何棘手的编程!
以下是每个示例的示例:
dynamic
:最通用的解决方案只需写 dynamic listOfA = new List<C>();
而不是 List<A> listOfA = new List<C>();
首先是所有示例的接口和类定义:
using System;
using System.Collections.Generic;
using System.Linq;
interface IAnimal
{
public string Name { get; }
}
class Bear : IAnimal
{
public string BearName = "aBear";
public string Name => BearName;
}
class Cat : IAnimal
{
public string CatName = "aCat";
public string Name => CatName;
}
// Dog has no base class/interface; it isn't related to the other classes
class Dog
{
public string DogName = "aDog";
public string Name => DogName;
}
这是使用 dynamic
public class AssignDerivedClass
{
public static void TestDynamicListAndArray()
{
dynamic any = new List<Bear>() // List of derived
{
new Bear() { BearName = "Bear-1" },
new Bear() { BearName = "Bear-2" }
};
//any[0].CatName = "NewCat"; // => Microsoft.CSharp.RuntimeBinder.RuntimeBinderException
Console.WriteLine($"Bear names: {any[0].BearName}, {Name(any[1])}");
any = new Cat[] // Array of derived
{
new Cat() { CatName = "Cat-3" },
new Cat() { CatName = "Cat-4" }
};
Console.WriteLine($"Cat names: {any[0].CatName}, {any[1].Name}");
any = new List<Dog>() // List of non-related class
{
new Dog() { DogName = "Dog-5" },
new Dog() { DogName = "Dog-6" }
};
Console.WriteLine($"Dog names: {any[0].DogName}, {Name(any[1])}");
any = new List<IAnimal>() // List of interface
// any = new IAnimal[] // Array of interface works the same
{
new Bear() { BearName = "Bear-7" },
new Cat() { CatName = "Cat-8" }
};
Console.WriteLine($"Animal names: {any[0].BearName}, {any[1].CatName}");
any[0].BearName = "NewBear";
Console.WriteLine($"Animal names: {Name(any[0])}, {any[1].Name}");
}
private static string Name(dynamic anymal)
{
return anymal switch
{
Bear bear => bear.BearName,
Cat cat => cat.CatName,
Dog dog => dog.DogName,
_ => "No known Animal"
};
}
// Bear names: Bear-1, Bear-2
// Cat names: Cat-3, Cat-4
// Dog names: Dog-5, Dog-6
// Animal names: Bear-7, Cat-8
// Animal names: NewBear, Cat-8
}
Array
:创建Bear[]
数组,保证所有数组元素引用Bear
的实例。您可以交换元素,但不能删除或添加新元素。
尝试设置错误的类型会产生运行时错误。
public static void TestArray()
{
Bear[] bears = { new Bear(), null };
IAnimal[] bearAnimals = bears;
//bearAnimals[1] = new Cat(); // System.ArrayTypeMismatchException
bearAnimals[1] = new Bear() { BearName = "Bear-1" };
Console.WriteLine($"Bear names: {bearAnimals[0].Name}, {bears[1].BearName}");
}
// Result => Bear names: aBear, Bear-1
IReadOnlyList
, IEnumerable
:将您的 List<C>
分配给 IEnumerable<A>
或 IReadOnlyList<A>
它们都不能在运行时更改,即您不能添加或删除元素。
为什么编译器应该允许将您的 List<C>
分配给 List<A>
而不是 IReadOnlyList<A>
添加元素无论如何都会导致错误?
public static void TestIEnumerableAndIReadonlyList()
{
var cats = new List<Cat>()
{
new Cat() { CatName = "Cat-3" },
new Cat() { CatName = "Cat-4" }
};
IEnumerable<IAnimal> iEnumerable = cats;
Console.WriteLine($"Cat names: {(iEnumerable.ElementAt(0) as Cat).CatName}, "
+ Name(iEnumerable.Last()));
IReadOnlyList<IAnimal> iROList = cats;
Console.WriteLine($"Cat names: {iROList[0].Name}, {Name(iROList[1])}");
//iROList.Add(new Cat()); // compiler error CS61: no definition for 'Add'
}
// Result:
// Cat names: Cat-3, Cat-4
// Cat names: Cat-3, Cat-4
List<>
: List<A> listOfA = new List<A>()
定义界面列表
只分配一个派生类的实例 - 您无论如何都不想存储其他类,是吗?
public static void TestListOfInterface()
{
var bears = new List<IAnimal>()
{
new Bear() { BearName = "Bear-1" },
new Cat() { CatName = "Cat-3" },
};
bears.Add(new Bear() { BearName = "Bear-2" });
string bearNames = string.Join(", ", bears.Select(animal => animal.Name));
Console.WriteLine($"Bear names: {bearNames}");
string bearInfo0 = VerifyBear(bears[0]);
string bearInfo1 = VerifyBear(bears[1]);
Console.WriteLine($"One animal is {bearInfo0}, the other one is {bearInfo1}");
string VerifyBear(IAnimal bear)
=> (bear as Bear)?.BearName ?? "disguised as a bear!!!";
}
// Bear names: Bear-1, Cat-3, Bear-2
// One animal is Bear-1, the other one is disguised as a bear!!!