使用列表的多态性
本文关键字:多态性 列表 | 更新日期: 2023-09-27 18:03:44
我有一个对象的继承结构,有点像下面:
public class A { }
public class B : A { }
public class C : B { }
理想情况下,我希望能够将A
, B
或C
的List
传递给这样的单一方法:
private void Method(List<A> foos) { /* Method Implementation */ }
B toBee = new B();
B notToBee = new B();
List<B> hive = new List<B> { toBee, notToBee };
// Call Method() with a inherited type. This throws a COMPILER ERROR
// because although B is an A, a List<B> is NOT a List<A>.
Method(hive);
我想想出一种方法来获得相同的功能,尽可能少的代码重复。
我能想到的最好的方法是创建包装器方法,接受各种类型的列表,然后循环通过传递的列表调用相同的方法;最后使用多态性对我有利:
private void Method(List<A> foos) { foreach (var foo in foos) Bar(foo); }
private void Method(List<B> foos) { foreach (var foo in foos) Bar(foo); }
private void Method(List<C> foos) { foreach (var foo in foos) Bar(foo); }
// An A, B or C object can be passed to this method thanks to polymorphism
private void Bar(A ayy) { /* Method Implementation */ }
Bus如您所见,我已经将该方法复制粘贴了三次,只更改了列表泛型中包含的类型。我已经开始相信,任何时候你开始复制和粘贴代码,有一个更好的方法去做……但我似乎想不出一个。
我怎样才能完成这样的壮举而不需要复制和粘贴?
创建一个泛型方法:
private void Method<T>(List<T> foos)
,所以你将能够使用它的各种List<T>
。您还可以使用泛型约束缩小方法接受的参数列表,以便仅处理A
子类:
private void Method<T>(List<T> foos)
where T : A
那么您确定foos
的每个元素都可以用作A
的实例:
private void Method<T>(List<T> foos)
where T : A
{
foreach (var foo in foos)
{
var fooA = foo as A;
// fooA != null always (if foo wasn't null already)
Bar(fooA);
}
}
正如Lucas Trzesniewski在他的回答中指出的那样,它甚至更好如果不需要修改集合,则使用IEnumerable<T>
。它是协变的所以你不会遇到你刚才描述的问题
你需要协方差。
如果你可以这样写:
private void Method(List<A> foos) { foreach (var foo in foos) Bar(foo); }
private void Method(List<B> foos) { foreach (var foo in foos) Bar(foo); }
private void Method(List<C> foos) { foreach (var foo in foos) Bar(foo); }
// An A, B or C object can be passed to this method thanks to polymorphism
private void Bar(A ayy) { /* Method Implementation */ }
那么它只意味着 List<T>
首先是错误的参数类型。
最好使用IEnumerable<out T>
,有两个原因:
- 它是协变,所以
IEnumerable<C>
是IEnumerable<A>
- 这是方法中唯一需要的东西,因为你只做了一个迭代。你可以传入
HashSet<B>
,LinkedList<A>
或C[]
,它仍然会工作得很好。
private void Method(IEnumerable<A> foos)
{
foreach (var foo in foos)
Whatever(foo);
}
List<T>
是一个机制 -它将T
引用(或值类型的值)存储在数组的连续内存中。不需要需要这个属性。
IEnumerable<out T>
是一个合约。它只说你可以枚举一个序列,这就是你在这个例子中所需要的。
你可以使用其他协变接口类型,如IReadOnlyCollection<out T>
(如果你需要预先知道项目计数或如果你必须枚举多次)或IReadOnlyList<out T>
(如果你需要项目索引)。
请记住,在大多数情况下,最好将接口类型作为参数,因为它允许您在需要时切换底层实现。
但是如果你真的要修改列表,那么协方差是不够的。在这种情况下,使用BartoszKP的解决方案,但是,嘿,您仍然可以使用IList<T>
而不是List<T>
:-)