我是普通的C#编码人员,在泛型方面遇到一些困难。当我按顺序学习该主题时,没有问题。但是,当我看到内部风格的通用类或方法时,就会遇到一些概念上的理解问题。 ;
例如,如果我看到类似List<string>
的代码,则假定存在一个列表类,这意味着您可以使用字符串进行一些操作。这没有问题。如果我看到像这样的课程,我该怎么想
Catalog<Book>
等。这是否意味着有一个Catalog类,而您只能对book类进行操作?当我看到复杂的泛型字时,我该如何思考这些单词?
答案 0 :(得分:4)
如果我们按字面意思取的名称,泛型将更容易理解。
一类或一组事物的特征或与之有关的特征;不具体。
我可以编写一个对某个对象执行操作的特定类,如下所示:
public class BookStorage
{
private Book _item;
public void StoreItem(Book item)
{
_item = item;
}
public Book RetrieveItem()
{
return _item;
}
}
这是一个非常简单的类,但是如果我想处理其他事情(而不仅仅是书籍),我最终会得到很多与此类非常相似的类。
public class BookStorage
{
private Book _item;
public void StoreItem(Book item)
{
_item = item;
}
public Book RetrieveItem()
{
return _item;
}
}
public class StringStorage
{
private string _item;
public void StoreItem(string item)
{
_item = item;
}
public string RetrieveItem()
{
return _item;
}
}
事实上,我的班级并不真正在乎项目是书还是字符串,因为它不依赖于Book
或string
的任何成员。
因此,如果我们使类更加通用(不太具体),则可以允许使用者稍后提供type参数。我们通常将未知类型称为T
,但这只是一个约定。
我们现在有了一个通用的存储类,该类将在以后指定类型T
。它与使用者相同,并获得与它在特定类中时相同的类型检查,即,如果我创建new Storage<Book>()
,它的行为将与BookStorage
类相同。但是,我现在可以创建新型的存储而无需添加更多的类,例如new Storage<string>()
或new Storage<Customer>()
。
public class Storage<T>
{
private T _item;
public void StoreItem(T item)
{
_item = item;
}
public T RetrieveItem()
{
return _item;
}
}
我也可以将其带入一个新的水平,并说我将依赖于我的通用类中的某些行为。例如,如果我只想存储有效的项目,则可以向T
添加一个约束,该约束说它必须实现IValidatable
接口。这意味着我不能再创建new Storage<string>()
,因为它不满足约束条件,但是只要Book
,Customer
或Building
满足我可以使用的约束条件所有这些。
public interface IValidatable
{
public bool IsValid();
}
public class Storage<T> where T : IValidatable
{
private T _item;
public void StoreItem(T item)
{
if (item.IsValid())
{
_item = item;
}
}
public T RetrieveItem()
{
return _item;
}
}
总而言之,泛型使您的类与它所操作的类型的绑定不再那么紧密,从而允许使用者将类重新用于不同的类型。
答案 1 :(得分:1)
您提到List<string>
的意思是“有一个列表类,意味着您可以使用字符串进行某些操作”。那是真的。但不是真的。 List<T>
意味着您可以对对象列表进行操作,其中T
是对象类型的占位符。 List<string>
除了列表所保存的类型string
之外,没有任何字符串特定的功能; List<T>
类提供的所有操作都与字符串无关,但与列表无关。
如果您有一个从List<string>
派生的类,则它可能具有string
特定的功能,因为在这种情况下,类定义明确包含字符串类型;例如
void Main()
{
var demo = new StringList(){"one","two","three","four"};
Console.WriteLine(demo.MaxLength());
}
class StringList: List<string>
{
public int MaxLength()
{
return this.Max(s => s?.Length ?? 0); //handles nulls
}
}
我们在MaxLength
上没有List<T>
函数,因为如果我们将int
作为类型,则此方法将没有任何意义(即,int
不会) t具有Length
属性)。但是,由于StringList
是从List<string>
派生的,所以我们有一个具体的类型,在该类型中,我们知道该类型的所有项目都将具有Length
属性,因此,有一种方法可以找到最大长度。
关于Catalog<Book>
之类的名称(这就是我们要做的所有事情)将表示存在Catalog<T>
类,其中对T可能有一些限制,但是Book符合这些限制(例如,可能要求T
实现某个接口,例如ICatalogItem
,该接口声明项目具有属性Title
,Creator
(例如{{ 1}} / Book
,Movie
,Publisher
等的导演,然后PublishDate
类将提供一种保存这些类型的多个项目的方法,并提供允许您搜索这些属性的方法。
还请注意,泛型不必一定是集合;您可以有一个采用给定类型的单个实例的泛型类,或者甚至不具有任何“内部”值而只提供对该类型的操作的泛型类。我们经常看到在依赖项注入中使用泛型的“单个实例”,其中泛型用于容纳单例,例如:
Catalog<T>
即允许系统中具有功能的任何代码与builder.RegisterInstance(new SqlServerDatabase(myDefaultConnectionString))
.As<IDatabase>()
集成在一起,以使用与我们的IDatabase
实例的连接;尽管只需将下面这行代码(和连接字符串的值)更改为以下内容,我们就可以将其切换为SqlServerDatabase
实例。
MySqlDatabase
我们没有任何实例的泛型示例可能像builder.RegisterInstance(new MySqlDatabase(myDefaultConnectionString))
.As<IDatabase>()
,如此处记录的https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.icomparer-1?view=netframework-4.7.2。通过创建派生自此类的类,我们可以在某些类型的对象之间创建不同的比较,例如我们可能希望比较字符串的长度而不是值(或者如果变得更复杂/更真实,则可能比较其拼字单词得分),例如:
IComparer<T>
答案 2 :(得分:0)
例如,如果我看到类似
List<string>
的代码,则假定存在一个 列表类,这意味着您可以使用字符串进行一些操作。
错了。如果看到Foo<T>
,则Foo
和T
不一定是class
。它们也可以是interface
。 System.Collections.Generic.IEnumerable<T>
是通用interface
的示例。 List
实现了此interface
。看到Foo<T>
时,您唯一知道的是Foo
是某种概念,无论是class
还是interface
,并且它正在与{{1 }}。在您的T
示例中,Catalog<Book>
是Catalog
或class
,并且正在使用interface
做某事。既然这里的名字说明了一切,那么我们可以推断出我们拥有一本书的目录。