我一直在读Albaharis的"Nutshell中的C#5.0",我在Generics部分遇到了这个问题,据说它是合法的:
class Bar<T> where T : Bar<T> { ... }
这对我来说毫无意义,尽管我已经仔细阅读了整章。我一点也听不懂。
有人能用一些可以理解的名字来解释吗,比如:
class Person<T> where T : Person<T> { ... }
在现实世界的应用场景中,这种用法是合适和有用的?
这意味着T
必须继承自Person<T>
。
这是在基类中创建特定于类型的方法、属性或参数的典型方法,特定于实际的子体。
例如:
public abstract class Base<T> where T : Base<T>, new()
{
public static T Create()
{
var instance = new T();
instance.Configure(42);
return instance;
}
protected abstract void Configure(int value);
}
public class Actual : Base<Actual>
{
protected override void Configure(int value) { ... }
}
...
Actual a = Actual.Create(); // Create is defined in Base, but returns Actual
当您使用某些外部库或框架(您不能或不想修改)时,它很有帮助。例如,您有这个库中的类User
,将使用它的开发人员肯定会定义从它继承的CustomUser
类(只是为了添加一些自定义字段)。还让我们想象一下,User
类对另一个用户有一些引用,例如:creator和deletor(它们显然是CustomUser
类型的实例)。在这种情况下,通用的自引用声明可以很好地提供帮助。我们将把后代类型(CustomUser
)作为参数传递给基(User
)类,因此在User
类声明中,我们可以将创建者和委托者的类型设置为未来的类型(CustomUser
),因此不需要强制转换:
public class User<TCustomUser> where TCustomUser : User<TCustomUser>
{
public TCustomUser creator {get;set;}
public TCustomUser deletor {get;set;}
//not convenient variant, without generic approach
//public User creator {get;set;}
//public User deletor {get;set;}
}
public class CustomUser : User<CustomUser>
{
//custom fields:
public string City {get;set;}
public int Age {get;set;}
}
用法:
CustomUser customUser = getUserFromSomeWhere();
//we can do this
var creatorsAge = customUser.creator.Age;
//without generic approach:
//var creatorsAge = ((CustomUser)customUser.creator).Age;
我可能会迟到一点,但我想分享一个不真实的世界应用程序场景来取乐:)
// self referencing list in c#
// we cant use generic type syntax: new List<List<List..
// but dynamic keyword comes to save us
var list = new List<dynamic>();
list.Add(list); // the "FBI! open up" part
Console.WriteLine(list[0][0][0][0][0][0][0].Count); // 1
当您有一系列的类要写,并且您意识到80%(选择一个数字)的代码基本上是相同的,只是类型不同,这也很有帮助。
编写泛型允许您在基类中捕获所有重复的代码并重用它
上面的特定模式很重要/很必要,因为您希望T是您要编写的类。
想象一个框架,其中crud对象基于crudBase,并且所有内容都继承自crudBase。进一步想象一下,您有一个基类来帮助查询这些对象(queryBase),并且crudBase和queryBase类将是1:1。
使queryBase成为泛型很简单,因为它相当明显地表明了您将如何声明它为
public abstract class queryBase<T> where T : crudBase
{
public list<T> FindMatches(string SearchCriteria){}
}
如果没有泛型,则必须在每个具体类中进行,因为返回类型会发生变化。泛型太棒了。
不太明显的是如何用crudBase实现同样级别的GENERIC涅盘。假设您有70%的锅炉板CRUD代码已经在一个子类中,但还有10%的逻辑需要引用类型
(选择一个数字,%数字不重要)
GENERIC解决方案在这里不那么明显。在第一种情况下,GENERIC类引用了一个具有T的不同类。在这种情况下,您希望用T引用同一个类。
使用上面描述的模式,你实际上可以实现这一点:
public class crudBaseGeneric<T> where T : crudBaseGeneric<T>
{
public <T> CopyMe(){}
}
在这里,您将把基类重新定义为泛型,并且您将能够捕获最后10%。
同样,如果没有泛型,我必须在每个具体类中复制粘贴我的CopyMe()函数。