'为什么'和'哪里'实际使用泛型?

时间:2010-03-19 06:42:24

标签: c# .net generics

我知道泛型用于实现类型安全,我经常读到它们主要用于自定义集合。但为什么我们确实需要让它们通用?

例如,

为什么我不能使用string[]代替List<string>

我们假设我声明了一个泛型类,它有一个泛型类型参数X。

T x;

如果我为类提供了一个

的方法
x = x + 1;

实际上是什么意思?我不知道T实际上会是什么,我不知道x = x + 1实际上会发生什么。

如果我无法在我的方法中进行自己的操作,那么泛型如何帮助我呢?

我已经研究过很多书呆子的答案。如果任何人能够在此提供一些明确的见解,将不胜感激。

此致 NLV

10 个答案:

答案 0 :(得分:17)

你的问题回答了自己。 数组类型本身就是一种通用类型。如果我们在CLR v1.0中放置泛型类型系统,我愿意打赌没有特殊的数组类型,只会有Array<T>以及List<T>等等。由于CLR的第一个版本没有泛型类型,因此我们将最重要和最明显的泛型类型(数组类型)放入其中。因此,在那里只有一大堆专用代码来处理数组,这是v1.0中支持的CLR的一种通用类型。

由于数组本质上是泛型类型,因此您的问题会自行解决:通用类型的原因通常与激发数组类型模式创建的原因相同。即:数组类型以特定方式放大其基础类型的能力

int表示数字。 int []表示数字的集合;我们已经将数字概念放大数字集合的概念。客户代表客户。 Customer []代表了一组客户的概念。我们将客户的概念扩展到了客户收集的概念。 从类型T到类型T []的映射表示将类型实例一般放大到该类型实例集合的抽象概念。

同样的理由激励所有泛型类型。 Nullable<T>类型将类型放大为“此事可能是该类型的实例”的概念。 IComparable<T>类型将类型放大为“此类型的实例可以相对于另一个实例进行排序”的概念。等等。每个泛型类型都与数组模式类似:它表示将类型放大为新类型,为该类型提供新操作。

简而言之:通用类型系统的目的是使您能够发明自己的类型扩增,并使用类型系统来操纵这些扩增。

答案 1 :(得分:9)

当我们需要“高阶”多态时,我们使用泛型,类似于编译时duck typing。当我们说Foo<T>时,我们的意思是Foo<>取决于任何可能的T可能具有的某些属性,并且给定的T具有此属性,或者代码无法编译

当我们说x = x + 1时,我们暗示T能够添加1,并且返回类型为T

答案 2 :(得分:3)

  

但为什么我们确实需要让它具有通用性?

如果您愿意,还可以制作大量自定义课程:StringListIntListDoubleList等。但泛型的一点是您可以定义一次课程它适用于所有对象。

其中x = x + 1属于通用类型x的{​​{1}}要求实际的真实类型要么自然地支持添加,要么拥有{ {1}}重载了int作为第二种类型。

答案 3 :(得分:3)

实际上,我正在一个网站上工作,仿制药让我的工作变得更轻松。我需要从数据库访问大量信息,因为整个站点都是数据库驱动的,所以我真的想要一个单独的类来实现数据库功能。这个类由更多知道如何处理各自数据的类继承。

例如,我有一个处理产品数据的Products类和一个处理主题等的Themes类。好吧,所有这些类都需要一个通用的读写格式。所以,我创建了一个处理这个的记录类。

现在,这里是泛型乐趣开始的地方。我创建了一个Product类,Theme类等。使用强类型成员(如Name,Manufacturer,ThemeId),但Records类不知道如何处理它们。因此,Database类的实例化类型与特定类,Product,Theme等相关。并且Record类也使用Generic类型,因此我可以编写诸如......之类的代码。

Product.Name = "Cool Product";

然后将其存储为......

Products.InsertRecord(Product coolProduct);

它不仅节省了大量的输入,而且使我能够拥有一个处理所有脏工作的类,而这些小的“存根”类为我提供了一个可读的强类型接口。

无论如何,对不起啰嗦的帖子感到抱歉。希望这有助于您了解至少在这种情况下泛型的强大功能。

答案 4 :(得分:0)

对于您的第一个问题,如果您想在列表中动态添加字符串,则可以选择List<string>而不是string[] - 数组是固定大小。

答案 5 :(得分:0)

因为虽然支持string [](即扣除了自定义数组类型),但数组具有: *功能不如列表甚至是IList *是一个数组,因此这对其他类型的集合不起作用。字典怎么样?

  

如果我不能做自己的事   在我的方法中操纵如何   仿制药会帮助我   呢?

这有点像问“如果我经营素食餐厅,我可以在供应商那里购买牛排有什么用呢?”。

基本上,当您的项目变得更复杂时,您将在方法中编写自己的操作。你会做所有有意义的事情。

答案 6 :(得分:0)

public foobar<T>{
    public Action<string,T> SomeAction;
    public string SomeString;
    public foobar(Action<string,T> s,string m){
         SomeAction=s;
         SomeString=m;
    }


    public void Run(T foo){
         SomeAction(SomeString,foo);
    }
}

如果没有泛型,您将不得不为每种可能性编写不同的类,或者其他一些精心设计的操作来获得所需的结果。泛型使事情变得更加优雅。

答案 7 :(得分:0)

string[]为我们提供了一个固定大小的数组。要获得动态大小列表,我们需要一个类。因此,如果我们想要一个字符串列表,我们可能必须创建一个名为StringList的类。对于双打,DoubleList。等等。但是这些类是什么?这些类型是独一无二的?没有。因此,我们创建了一个可以接受任何类型的泛型类List&lt;&gt;。无需一遍又一遍地为不同类型重新创建相同的类,泛型可以为我们节省一些工作。

对于x = x + 1问题,您必须期望T有一个+运算符,它接受右侧的int。如果没有,则会抛出运行时错误。如果是,则该代码将x分配给+运算符的输出,其中x1作为参数。

还有很多其他情况没有执行任何独特的操作,并且类/方法必须使用多种类型。这些也是泛型是重要且有用的情况。

由您自己决定在代码中何时何地使用它们。如果你遇到问题,他们是最好的答案,那很好。使用它们。否则,不要。只是不要取消他们作为解决方案的资格,因为他们从来都不是你特定问题的答案。

答案 8 :(得分:0)

我自己的工作经验中的一个很好的例子是管理Linq-To-SQL类型。创建新数据库对象时我必须执行的一项基本操作是:

  1. 检查本地缓存中是否存在请求的对象。
  2. 否则,检查数据库中是否存在请求的对象。
  3. 否则,使用提供的参数实例化一个新对象。
  4. 在这个特定的项目中,大约有100种不同的对象类型,因此为每个对象编写这些代码最好是麻烦的,即使是使用脚本,如果我们必须改变一些方法,我们该怎么做? / p>

    使用泛型和约束,我可以这样做:

    public class SQLObjectManager<TEntity> where TEntity : class, new()
    {
        public TEntity GetObject(int year, int stateID)
        {
            if( /* entity found in cache */)
                return GetFromCache(year,stateID);
            if( /* entity found in db */)
                return GetFromDB(year,stateID);
            TEntity entity = new TEntity();
            entity.Year = year;
            entity.StateID = stateID;
            return entity;
        }
    }
    

    除了编译时类型检查的好处之外,我现在只有一个函数来实现和维护我的数据库中每个表的新版本。

答案 9 :(得分:0)

请注意,如果您来自C ++背景,那么C#泛型和C ++模板是类似的概念。虽然,我相信他们的工作方式不同。

存在泛型,用于编写可跨不同类型重用的代码。仿制药,增加了类型安全性,减少了铸造和装箱。

假设您需要一堆整数;一个解决方案是提出一个整数堆栈类。然后,对于每个必需的数据类型(float,string等),您将编写该类的单独版本;在这里你会意识到这会导致代码重复。

另一种解决方案是编写一个使用object作为元素类型进行泛化的堆栈:

如何关注?

MyGenericStack<string> stringStack = new MyGenericStack<string>();
stringStack.Push("One");
stringStack.Push("Two");
stringStack.Push("Three");

MyGenericStack<int> intStack = new MyGenericStack<int>();
intStack.Push(1);
intStack.Push(2);
intStack.Push(3);

MyGenericStack<CustomObject> customObjectStack = new MyGenericStack<CustomObject>();
customObjectStack.Push(new CustomObject(1));
customObjectStack.Push(new CustomObject(2));
customObjectStack.Push(new CustomObject(3));

以下是通用类

public class MyGenericStack<T>
{
    int position;
    T[] data = new T[10];
    public void Push(T obj) { data[position++] = obj; }
    public T Pop() { return data[--position]; }

    public void Show()
    {

        for (int i = 0; i < data.Length; i++)
        {
            //Console.WriteLine("Item: {0}", data[i]);
            //MessageBox.Show(String.Format("Item: {0}", data[i]));
        }
    }
}

是的,我知道,现在你too爱上了Generics!

顺便说一句,如果您计划比较继承和泛型,那么请记住,继承具有基本类型的可重用性,而泛型使用模板类型表示可重用性。