关于泛型思维方式的概念性问题

时间:2018-12-03 10:30:08

标签: c# generics

我是普通的C#编码人员,在泛型方面遇到一些困难。当我按顺序学习该主题时,没有问题。但是,当我看到内部风格的通用类或方法时,就会遇到一些概念上的理解问题。 ;

例如,如果我看到类似List<string>的代码,则假定存在一个列表类,这意味着您可以使用字符串进行一些操作。这没有问题。如果我看到像这样的课程,我该怎么想 Catalog<Book>等。这是否意味着有一个Catalog类,而您只能对book类进行操作?当我看到复杂的泛型字时,我该如何思考这些单词?

3 个答案:

答案 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;
    }
}

事实上,我的班级并不真正在乎项目是书还是字符串,因为它不依赖于Bookstring的任何成员。

因此,如果我们使类更加通用(不太具体),则可以允许使用者稍后提供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>(),因为它不满足约束条件,但是只要BookCustomerBuilding满足我可以使用的约束条件所有这些。

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,该接口声明项目具有属性TitleCreator(例如{{ 1}} / BookMoviePublisher等的导演,然后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>,则FooT不一定是class。它们也可以是interfaceSystem.Collections.Generic.IEnumerable<T>是通用interface的示例。 List实现了此interface。看到Foo<T>时,您唯一知道的是Foo是某种概念,无论是class还是interface,并且它正在与{{1 }}。在您的T示例中,Catalog<Book>Catalogclass,并且正在使用interface做某事。既然这里的名字说明了一切,那么我们可以推断出我们拥有一本书的目录。