C# 泛型编程

为什么使用泛型

为什么使用泛型?那我们先来说说不使用泛型会怎样。这里就会涉及到装箱拆箱,首先了解一下装箱,装箱拆箱
    装箱分为三个步骤:将值类型转换为引用类型
  1. 内存的分配:在堆中分配内存空间来存放复制的实际数据
  2. 完成实际数据的赋值:将值类型实例的实际数据复制到新分配的内存中。
  3. 地址返回:将堆中的独享地址返回给引用类型变量

拆箱将引用类型转换成值类型

  1. 检查实例:首先检查进行拆箱操作的引用类型是否为null,如果为null就抛出异常,如果不为null则继续检查变量是否和拆箱后的类型是同一类型。 注意:被装箱过的数据才可以拆箱
  2. 地址返回:返回已装箱变量的实际数据部分的地址

  3. 数据复制:将堆中实际数据复制到栈中

这里说一下ref,out关键字的使用
ref关键字  和c语言中的指针类似。我觉得就是相当于把实参自身传了过去,在方法里用实参进行操作
class Program
    {
        public static void Main()
        {
            string a="hello";
            fun(ref a);//按引用进行传递,传递的是a的地址,在函数中通过形参str直接修改实参a;
            Console.WriteLine(a);
        }
        public static void fun(ref string str)
        {
            str="world";//如果不按引用传递,改变的是引用的指向,并没有影响实参
        }
    }
out 关键字  在使用时out声明的形参必须赋初值,out就是讲方法中的值带回到主函数中,将值赋值给形参
public static void Main()
        {
            int a=10;
            fun(out a);//out 关键字,必须要通过形参给实参做赋值操作
            Console.WriteLine(a);
        }
        public static void fun(out int n)
        {
            //Console.WriteLine(n);
            n = 100;//out关键字声明的形参必须要先初始化才能操作
            Console.WriteLine(n);
        }

      这是简单的变量的装箱拆箱操作,下面说一下方法

普通的方法中,定义的参数是什么类型就只能传递什么类型的参数。

首先 定义的是什么类型的数组就存什么类型

public void add(int v)
{
      array[size++]=v; //这样就只能添加int类型的数据
}

然后你会想到用一个通用的类型数据object

public void add(object v)
{
	array[size++]=v;
}

使用object可以解决数据类型的问题,但是在会出现装箱、拆箱操作,这将在堆上分配和回收大量的变量,若数据量大,性能会

损失非常严重,在处理引用类型时虽然没有装箱和拆箱操作,但是将数据类型的强制转换操作,增加处理器的负担。

这里就可以使用一段代码测试一下统计一下两种操作的时间。(直接复制下来在你的机器上运行一下)

using System;
using System.Diagnostics;//统计时间的命名空间
namespace aa
{
    class Program
    {
        public static void Main()
        {
            int max = 10000000;
            Vector1 v1 = new Vector1(max);
            Stopwatch watch1 = new Stopwatch();
            watch1.Start();
            for (int i = 0; i < max; i++)
            {
                v1.add(i);
            }
            watch1.Stop();
            TimeSpan span1 = watch1.Elapsed;
            Console.WriteLine("{0}:{1}",span1.Seconds,span1.Milliseconds);
            Vector2 v2 = new Vector2(max);
            Stopwatch watch2 = new Stopwatch();
            watch2.Start();
            for (int i = 0; i < max; i++)
            {
                v2.add(i);
            }
            watch2.Stop();
            TimeSpan span2 = watch2.Elapsed;
            Console.WriteLine("{0}:{1}",span2.Seconds,span2.Milliseconds);
        }
    }
    public class Vector1
    {
        private object []arrary;
        private int size;
        public Vector1(int n)
        {
            arrary = new object[n];
            size = 0;
        }
        public object this[int index]
        {
            get{ return arrary[index];}
            set{ arrary[index] = value;}
        }
        public void add(object obj)
        {
            arrary[size++] = obj;
        }
    }
    public class Vector2
    {
        private T[]arr;
        private int size;
        public Vector2(int n)
        {
            arr = new T[n];
            size = 0;
        }
        public T this[int index]
        {
            get{ return arr[index];}
            set{ arr[index] = value;}
        }
        public void add(T x)
        {
            arr[size++] = x;
        }
    }
}

使用泛型的优点

使用泛型就可以有传递多种类型的参数,减少了相同方法的重复定义。不仅可以解决类型问题还可以很大程度的优化性能

Vector v1 = new Vector(5);
            for (int i = 0; i < 5; i++)
            {
                v1.add(i);
            }
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(v1[i]);
            }
            Vector v2 = new Vector(5);
            for (int i = 0; i < 5; i++)
            {
                v2.add(i * 1.1d);
                Console.WriteLine(v2[i]);
            }

1.他是类型安全的,实例化了int 类型的栈,就不能处理string类型,其他数据类型也是一样

2.无需装箱和拆箱。这个类在实例化时,按照所传入的数据类型生成本地代码,本地代码类型已经确定,所以无需装箱和拆箱。

3.无需类型转换。

4.泛型将方法实现行为与方法操作的数据类型分离,实现了代码重用。

泛型中的两个容器

  1. List<>容器

List<>容器中每个元素都对应着一个整型下标。自己创建容器时都要自己定义一个长度,但是list容器不需要我们自己传递参数

系统会自动为我们定义,并且会自动扩展容量(List中包含多个属性和方法自己在编辑器中查看就行)

2.Dictionary 容器 以键值对的形式存储数据

每个元素(值)都对应着一个下标(键)。包含两个类型 键的类型和值的类型

foreach(KeyValuePairi in d) 使用这样的方式遍历字典中的数据

 Dictionary d = new Dictionary();
 //通过键来访问对应的值
            for (int i = 0; i < 5; i++)
            {
                d.Add("hel" + i, i);
            }
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(d["hel"+i]);
            }
public class Channel
    {
        //这个是Channel的标题
        private string tittle;
        //创建了一个存放News 的字典
        private DictionarynewsList;
        //创建一个索引,就可以通过channel对象直接访问 channel c1["aaa"]直接访问
        //不用再使用c1.newslist["aaa"];
        public News this[string index]
        {
            get{ return newsList[index];}
            set{ newsList[index] = value;}
        }
        public Channel(string _tittle)
        {
            tittle = _tittle;
            newsList = new Dictionary();
        }
        //调用系统的add方法将新闻添加到newslist中,然后每一个channel中都有一个词典,
        public void addNews(News n)
        {
            newsList.Add(n.Tittle, n);
        }
    }

泛型方法,缩小了泛型的定义,只在方法中使用泛型

另外T只是一个表示符,可以使用其他的字母,不是非得使用T ,只是T 已经成了一种公认的习惯

泛型约束

首先要知道,泛型在没有约束的情况下可以指向任何数据类型

泛型约束

   通过where关键字给T添加约束
    1.基类约束

基类约束,指定是某个类或该类的子类,约束后,T的类型必须是该类型或者该类型的子类
public class Vector where T:Person
{}

2.接口约束

给T 指定了类型必须继承接口中所有的方法

只有继承了接口了的实体类才能作为泛型参数

3.new()构造函数约束

允许开发人员实例化一个泛型类型的对象new()约束要求类型实参必须提供一个无参数的公有构造函数。使用new()约束时,

可以通过调用该无参数的构造函数来创建对象。

(1)  new()约束可以与其他约束一起使用,但必须位于约束列表的末端。

(2)  仅允许使用无参的构造函数创建对象,即使同时存在其他的构造函数也是如此

(3)  不可以同时使用new()约束和值类型约束。因为值类型都隐式的提供了一个无参公共构造函数。

4.引用类型约束

where T:class  这时T 必须是引用类型

5.值类型约束

where T :struct 这是T必须是值类型。

不能同时出现的约束类型
不能同时出现的约束 基类约束 值类型约束 引用类型约束 构造函数和值类型不能一起用
多个约束时的顺序

顺序 第一位基类型约束值类型约束引用类型约束第二位 接口约束 第三位 构造函数约束  
泛型接口

泛型接口在继承接口时就要确定接口的类型

public interface IAdd
    {
        void add(T x);
    }
 public class Vector1:IAdd//接口继承前要指定泛型参数
    {
    }

泛型接口中的泛型参数可以使用泛型类中的参数

public class Vector2:IAdd
{
     
}

泛型委托

public delegate T Mydelegate (T x,T y);

泛型委托的优点在于,他允许开发人员以类型安全的方式定义一种通用形式,可用于匹配任意兼容的方法


泛型方法重载参数类型一样时就会出现问题,因为方法名一样,参数列表也一样

泛型方法中的泛型参数和类中的泛型参数是没有关系的可以不一致

你可能感兴趣的:(c#)