让我们首先讨论纯参数多态性,然后再讨论有界多态性。
参数多态性是什么意思?好吧,这意味着类型或类型构造函数由类型进行参数化。由于类型是作为参数传递的,因此您无法事先知道它可能是什么。您不能基于此做出任何假设。现在,如果您不知道它可能是什么,那么有什么用?你能做什么呢?
好吧,例如,您可以存储和检索它。您已经提到过这种情况:集合。为了将一个项目存储在列表或数组中,我对该项目一无所知。列表或数组可以完全忽略该类型。
但是Maybe
类型呢?如果您不熟悉它,那Maybe
是一个可能有值但可能没有值的类型。您将在哪里使用它?好吧,例如,当从字典中取出一个项目时:一个项目可能不在字典中的事实并不是一种例外情况,因此,如果该项目不存在,您就不应该抛出异常。相反,您返回的子类型的实例,该实例Maybe<T>
恰好具有两个子类型:None
和Some<T>
。int.Parse
是真正应该返回a Maybe<int>
而不是抛出异常或整个int.TryParse(out bla)
舞步的东西的另一个候选者。
现在,您可能会认为这Maybe
有点像列表,只能包含零个或一个元素。因此有点收藏。
那又如何Task<T>
呢?它是一种有望在将来某个时刻返回值的类型,但现在不一定具有值。
还是呢Func<T, …>
?如果不能抽象类型,您将如何表示从一种类型到另一种类型的功能概念?
或更笼统地说:考虑到抽象和重用是软件工程的两个基本操作,为什么不希望能够对类型进行抽象呢?
因此,现在让我们谈谈有界多态性。有界多态性基本上是参数多态性和子类型多态性相遇的地方:您可以将类型绑定(或约束)为某个指定类型的子类型,而不是完全不理会其类型参数的类型构造函数。
让我们回到集合。拿一个哈希表。上面我们说过,列表不需要了解其元素。哈希表确实可以做到:它需要知道可以对它们进行哈希处理。(注意:在C#中,所有对象都是可哈希的,就像可以比较所有对象是否相等一样。不过,并非所有语言都适用,有时甚至在C#中也被视为设计错误。)
因此,您想将散列表中的键类型的类型参数约束为IHashable
:
class HashTable<K, V> where K : IHashable
{
Maybe<V> Get(K key);
bool Add(K key, V value);
}
想象一下,如果您有:
class HashTable
{
object Get(IHashable key);
bool Add(IHashable key, object value);
}
你会用什么做value
你的存在吗?您不能对它做任何事情,只能知道它是一个对象。而且,如果您对其进行迭代,那么得到的就是一对您知道的东西IHashable
(对它没有多大帮助,因为它只有一个属性Hash
),而您知道的东西对object
(这对您甚至没有多大帮助)。
或基于您的示例的内容:
class Repository<T> where T : ISerializable
{
T Get(int id);
void Save(T obj);
void Delete(T obj);
}
该项目需要可序列化,因为它将存储在磁盘上。但是如果您有这个呢?
class Repository
{
ISerializable Get(int id);
void Save(ISerializable obj);
void Delete(ISerializable obj);
}
与一般情况下,如果你把BankAccount
在,你会得到一个BankAccount
回来了,方法和属性一样Owner
,AccountNumber
,Balance
,Deposit
,Withdraw
,等你的东西可以工作。现在,另一种情况?您输入,BankAccount
但又返回Serializable
,其中只有一个属性:AsString
。你打算怎么办?
您还可以使用有限多态来完成一些巧妙的技巧:
F界量化基本上是类型变量再次出现在约束中的位置。在某些情况下这可能很有用。例如,您如何编写ICloneable
接口?您如何编写方法,其中返回类型是实现类的类型?在具有MyType功能的语言中,这很容易:
interface ICloneable
{
public this Clone(); // syntax I invented for a MyType feature
}
在具有有限多态性的语言中,您可以改为执行以下操作:
interface ICloneable<T> where T : ICloneable<T>
{
public T Clone();
}
class Foo : ICloneable<Foo>
{
public Foo Clone()
{
// …
}
}
请注意,这不如MyType版本安全,因为没有什么可以阻止某人简单地将“错误”类传递给类型构造函数的:
class EvilBar : ICloneable<SomethingTotallyUnrelatedToBar>
{
public SomethingTotallyUnrelatedToBar Clone()
{
// …
}
}
抽象类型成员
事实证明,如果您具有抽象类型成员和子类型,则实际上可以完全不用参数多态性,而仍然可以做所有相同的事情。Scala是在这个方向迈进,成为第一大语言,开始了与仿制药,然后试图删除它们,而这正是从如Java和C#的其他方式。
基本上,在Scala中,就像可以将字段,属性和方法作为成员一样,也可以具有类型。就像字段,属性和方法可以被抽象为稍后在子类中实现一样,类型成员也可以被抽象为抽象。让我们回到一个简单的集合,List
如果在C#中受支持,它将看起来像这样:
class List
{
T; // syntax I invented for an abstract type member
T Get(int index) { /* … */ }
void Add(T obj) { /* … */ }
}
class IntList : List
{
T = int;
}
// this is equivalent to saying `List<int>` with generics
interface IFoo<T> where T : IFoo<T>
也记得。那显然是现实生活中的应用。这个例子很棒。但是由于某种原因,我感到不满意。我宁愿在合适的时候动脑子,什么时候不合适。答案对这个过程有一定的帮助,但是我仍然觉得自己在这方面不适应。这很奇怪,因为语言级别的问题已经困扰我这么久了。