与列表的多态性

时间:2015-07-20 19:48:49

标签: c# generics polymorphism

我有一个对象的继承结构,有点像:

public class A { }
public class B : A { }
public class C : B { }

理想情况下,我希望能够将List ABC传递给这样的单一方法:

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 */ }

总线如您所见,我已经三次复制并粘贴该方法,只更改列表通用中包含的类型。我开始相信,无论何时开始复制和粘贴代码,都有更好的方法来做到这一点......但我似乎无法想出一个。

如果没有不受欢迎的复制和粘贴,我怎么能完成这样的壮举?

2 个答案:

答案 0 :(得分:11)

创建generic method

private void Method<T>(List<T> foos) 

因此您可以将其用于各种List<T>。您还可以使用generic constraints缩小方法的接受参数列表,以便仅处理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>。这是协变的,所以你不会遇到你所描述的问题。

答案 1 :(得分:8)

您需要covariance

如果您可以写下以下内容:

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>(如果您需要预先了解项目计数或必须多次枚举)或IList<T>(如果您需要项目)索引)。

请记住,大多数情况下,尽可能将接口类型作为参数,因为它允许您在需要时切换底层实现。

但如果你真的需要修改列表,那么协方差就不够了。在这种情况下,请使用BartoszKP的解决方案,但是,嘿,您仍然可以使用List<T>代替 Private Sub Enable_Proxy(ByVal Proxyset As String) Dim regkeyProxy_Enable As RegistryKey 'Registry Key Location regkeyProxy_Enable = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", True) 'Change The Proxy Enable value regkeyProxy_Enable.SetValue("ProxyEnable", 1, RegistryValueKind.DWord) 'Set the proxy server value regkeyProxy_Enable.SetValue("ProxyServer", Proxyset, RegistryValueKind.String) End Sub Private Sub Disable_Proxy() Dim regkeyProxy_Enable As RegistryKey regkeyProxy_Enable = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", True) 'Disable The proxy regkeyProxy_Enable.SetValue("ProxyEnable", 0, RegistryValueKind.DWord) End Sub : - )