c#泛型

泛型

泛型是什么?

即通过参数化类型来实现在同一份代码上操作多种数据类型。
泛型可以定义类型安全类,而不会损害类型安全、性能或工作效率。

泛型怎么写?

泛型类

class MyClass
{
    //.............
}

泛型方法

public void MyMethod()
{
    //.............
}

泛型接口

 interface MyInterface
{
    //.............
}

泛型结构

 struct MyStruct
{
    //.............
}

相对于C#中的类的声明等语法,其实泛型只是多加了一个类型参数用来表示不同类型而已。添加的方式也就是在类、方法等名称后面加

泛型的使用

对于泛型的使用,总结成一句话就是,跟普通类的使用是几乎一样的,只是在使用过的时候需要将类型参数确定为明确的类型。
泛型就是一个 ,在使用时将 T 换成确定的类型。

泛型的优点

性能

泛型 的一个优点是性能。
对值类型使用 非泛型集合类 在把值类型转换为引用类型,和把引用类型转换为值类型时需要进行装箱和拆箱操作。
装箱拆箱链接。
值存储在栈上,引用存储在堆上。
泛型在使用时定义具体的类型,避免装箱拆箱操作。

类型安全

泛型类型需要在使用时指定,只能使用泛型指定的类型,编译器会在编译前检测,提前发现错误。

二级制代码重用

泛型可以更好的重用二级制代码。泛型类可以定义一次,并且享有许多不同的类型实例化。

代码扩展

泛型类的定义会放在程序集中,所以使用特定类型实例化泛型类不会在IL代码中重复这些类。
编译器将泛型类编译为本地代码后会给每一个值创建一个新类,引用类型共享一个本地类的所有实现代码。
因为引用类型的泛型类实例化只需要4个字节内存地址,就可以引用一个引用类型。
而对于值类型的存在于泛型类实例化内存中,每个值类型对内存的要求不同,所以要为每一个类创建一个新类。
引用类型大小确定,有限,所以引用类型共享代码。值类型大小不固定,每个值类型的实例化都会创建新类。

命名约定

  • 泛型类型使用字母T作为前缀。
  • 如果没有特殊要求,泛型类型允许使用任意类型代替,且只使用了一个泛型类型,就可以用字符T作为泛型类型的名称。
  • 如果泛型类型有特定的要求,或者使用了两个或多个泛型类型,就应该给泛型类型使用描述性名称。
using System;
using static System.Console;

namespace ConsoleApp22
{
    class Program
    {
        public class MyClass
        {
            public T1 X1;
            public T2 X2;
        }
        static void Main(string[] args)
        {
            MyClass s = new MyClass();
            s.X1 = 100;
            s.X2 = "ASD";
            WriteLine(s.X1);
            WriteLine(s.X2);
            ReadKey();
        }
    }
}

描述性名称并没有具体含义,但是要尽量表明其期望的含义,正常应该写为Tint,Tstring

泛型(Generic) 允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许编写一个可以与任何数据类型一起工作的类或方法。

可以通过数据类型的替代参数编写类或方法的规范。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理指定的数据类型。

泛型 可以创建独立于被包含类型的类和方法,我们不必给不同的类型编写许多相同的方法或类,只创建一个方法或类即可。
另一个减少代码的选项是使用object类,但使用派生自object类的类型进行传递不是类型安全的。
泛型类 使用 泛型类型 ,并且可以根据特定的类型替换泛型类型,保证了安全性。
如果这个 泛型 不支持 泛型类 ,编译器就会出现错误。
泛型 不局限于类,还有用于接口和方法的 泛型。以及用于委托的泛型
委托的泛型链接

泛型类

创建泛型类

首先介绍一个一般的,非泛型的简化链表类,它可以包含任意多的对象,以后再把这个类转化为泛型类。
在链表中,一个元素引用下一个元素。

image.png

先创建一个类,这部分可以直接看C#实现链表
类中包含链表需要的元素。

        public class LinkedListNode
        {
            public LinkedListNode(object value)
            {
                Value = value;
            }
            public object Value { get; set; }//值
            public LinkedListNode Next { get; internal set; }//指向下一个
            public LinkedListNode Prev { get; internal set; }//指向上一个
        }

然后实现具体的链表。

public class LinkedList : IEnumerable//IEnumerable 是 .NET 的接口,用于实现迭代
        {
            public LinkedListNode First { get; set; }//链表头
            public LinkedListNode Last { get; set; }//链表尾

            public LinkedListNode AddLast(object node)//创建链表
            {
                var newNode = new LinkedListNode(node);//对链表数量化
                if (First == null)//如果头为空
                {
                    First = newNode;//让链表头为新链表
                    Last = First;//链表头和链表尾相等
                }
                else//如果链表头部位空
                {
                    Last.Next = newNode;//尾链表的下一个元素为新链表
                    Last = newNode;//尾链表尾新链表
                }
                return newNode;//返回链表
            }
            public IEnumerator GetEnumerator()//迭代方法获取链表
            {
                LinkedListNode current = First;//获取链表头
                while (current != null)//当前链表不为空
                {
                    yield return current.Value;//迭代返回链表的下一个元素
                    current = current.Next;//当前元素为下一个元素
                }
            }

实现链表头尾,扩展链表,链表遍历通过 枚举器 详情见 c#数组和元组
然后就可以对链表进行操作,

      var list1 = new LinkedList();
      list1.AddLast(2);
      list1.AddLast(4.78);
       list1.AddLast("6");

      foreach (var i in list1)
      {
        WriteLine(i);
      }

接下来我们创建泛型类版本的链表。

using System;
using System.Collections;
using System.Collections.Generic;
using static System.Console;

namespace ConsoleApp22
{
    class Program
    {
        public class LinkedListNode//定义一个链表泛型类
        {
            public LinkedListNode(T value)//泛型类的构造函数
            {
                Value = value;
            }

            public T Value { get; }//只读字段链表值
            public LinkedListNode Next { get; internal set; }//指向下一个元素指针
            public LinkedListNode Prev { get; internal set; }//指向上一个元素指针
        }

        public class LinkedList : IEnumerable//链表具体实现
        {
            public LinkedListNode First { get; private set; }//链表类型的链表头
            public LinkedListNode Last { get; private set; }//链表类型的链表尾

            public LinkedListNode AddLast(T node)//链表方法,返回一个链表
            {
                var newNode = new LinkedListNode(node);//对链表进行实例化
                if (First == null)//通过头结点为空
                {
                    First = newNode;//头结点为链表
                    Last = First;//尾结点就是头结点
                }
                else//如果头结点不为空
                {
                    Last.Next = newNode;//尾结点的下一个结点为新传入的结点
                    Last = newNode;//尾结点为这个新链表
                }
                return newNode;//返回新列表
            }

            public IEnumerator GetEnumerator()//对链表遍历方法,返回一个可迭代泛型集合
            {
                LinkedListNode current = First;//当前指针获取头结点

                while (current != null)//如果结点存在
                {
                    yield return current.Value;//迭代返回当前结点的值
                    current = current.Next;//当前结点转向下一个结点
                }
            }
            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        }
        static void Main()
        {
            var list2 = new LinkedList();//对泛型实例化,规定链表只能传入int
            list2.AddLast(1);//撰写链表
            list2.AddLast(3);
            list2.AddLast(5);

            foreach (int i in list2)//遍历链表的值
            {
                WriteLine(i);
            }

            var list3 = new LinkedList();//只能传入字符串
            list3.AddLast("2");
            list3.AddLast("four");
            list3.AddLast("foo");

            foreach (string s in list3)
            {
                WriteLine(s);
            }
            ReadKey();
        }
    }
}

泛型类的定义与一般类类似,只是要使用泛型声明。
之后,泛型类型就可以在类中用作一个字段成员,或者方法的参数类型,

        public class LinkedListNode//定义一个链表泛型类
        {
            public T Value;
        }

构造函数也可以变为接受T类型的对象,也可以返回和设置泛型类型。

            public LinkedListNode(T value)//泛型类的构造函数
            {
                Value = value;
            }

泛型类的功能

默认值

不能将null赋予泛型类型,因为泛型可以实例化为值类型,而null只能用于引用类型。
可以使用 default 关键字将 null在示例化为值类型时变成0.

        class Test
        {
            public T F()
            {
                T t = null; //有问题
                return t;
            }
        }

t在这里无法编译,无法将null转换为参数类型T,因为它可能是不可以为null的值类型。
所以需要使用 default

class Test
    {
        public T F()
        {
                return default(T);
        }
    }

default对应各种类型生成默认值列表如下

类型 默认值
任何引用类型 null
数值类型 0
bool false
enum 表达式(E)0生成的值,其中E是枚举标识符
struct 将所有值类型设为其默认值,将所有引用类型设为null
可以为null的类型 HasValue属性为false,且Value属性未定义的实例

约束

如果泛型类需要调用泛型类型的方法,就必须添加约束
泛型接受的约束类型

约束 说明
where T : sturct 对于结构约束,泛型必须是值类型
where T : class 类约束指定类型T必须是引用类型
where T : IFoo 指定类型T必须实现接口IFoo
where T : Foo 指定类型T必须派生自基类Foo
where T : new() 这个是一个构造函数约束,指定类型T必须有一个默认构造函数
where T1: T2 这个约束也可以指定,类型T1派生自泛型类型T2

**只能为默认构造函数定义构造函数约束,不能为其他构造函数定义构造函数约束。
使用泛型类型还可以合并多个约束。

class MyClassy
where T1: struct
where T2: class,new()
{
      //....
}

指定T1必须是值类型,T2必须是引用类型且必须有一个默认构造函数。

**在C#中,where子句的一个重要限制是不能定义必须由泛型类型实现运算符,运算符不能在接口定义 。在where子句中,只能定义基类,接口和默认构造函数,

继承

泛型类可以实现泛型接口,也可以派生自一个类,泛型类可以派生自泛型基类。

        public class MyClass1
        {
            //.......
        }
        public class MyClass2 : MyClass1
        {
            //.......
        }

派生类不必是泛型类

        public class MyClass1
        {
            //.......
        }
        public class MyClass2 : MyClass1
        {
            //.......
        }

泛型接口同样可以被继承

        interface IMethod
        {
            void f();
        }
        public class MyClass3 : IMethod
        {
            public void f()
            {
                WriteLine(777);
            }
        }

静态成员

泛型类的静态成员需要特别关注,泛型类型的静态成员只能在类中的一个实例中共享,

using System;
using static System.Console;

namespace ConsoleApp23
{
    class Program
    {
        public class MyClass
        {
            public static T x;
        }
        static void Main(string[] args)
        {
            MyClass.x = 100;
            MyClass.x = "asdw";
            ReadKey();
        }
    }
}

string,int是两个不同的类型,这样就出现两个不同的静态字段x。

泛型接口

使用泛型可以定义接口,在接口中定义的方法可以带泛型参数,

泛型接口的协变和抗变

参数类型是协变的。
泛型参数定义的类型只能作为方法的返回类型,不能作为方法的参数类型,且该类型直接或者间接地继承自接口方法的返回值类型;可以使用out关键字声明协变参数
如果泛型类型使用 out 关键字做标注,泛型接口就是协变的,这也意味着返回类型只能是T。
如:string->object (子类到父类的转换)

返回的方法是抗变的。
泛型参数定义的类型只能作为方法参数的类型,不能作为返回值类型,且该类型是接口方法的参数类型的基类型;可以使用in关键字声明抗变参数。
如果泛型类型使用 in 关键字做标注,泛型接口就是抗变的,这也意味接口只能将类型T用作其方法的输入。

如:object->string (父类到子类的转换)

泛型结构

与类类似,结构也可以是泛型的,非常类似于泛型类,但是没有继承。

using System;
using static System.Console;

namespace ConsoleApp23
{
    class Program
    {
        public struct MyStruct
        {
            public T A;
            public T B;
        }
        static void Main(string[] args)
        {
            MyStruct s;
            int a = s.A = 100;
            int b = s.B = 777;
            WriteLine(a);
            WriteLine(b);
            ReadKey();
        }
    }
}

泛型方法

在泛型方法中,泛型类型用方法声明来定义。
泛型方法可以在非泛型类中定义。

泛型方法示例

using System;
using static System.Console;

namespace ConsoleApp23
{
    class Program
    {
        public class MyClass
        {
            public static void swap(ref T a,ref T b)
            {
                T temp;
                temp = a;
                a = b;
                b = temp;
            }
        }
        static void Main(string[] args)
        {
            int a = 4;
            int b = 10;
            WriteLine("{0}   {1}",a,b);
            MyClass.swap(ref a, ref b);
            WriteLine("{0}   {1}",a,b);
            ReadKey();
        }
    }
}

这里为什么要用ref。ref关键字用于将方法内的变量改变后带出方法外。

带约束的泛型方法

泛型类可以用where子句限制,泛型方法也可以用where子句限制,

            public static void swap(ref T a,ref T b) where T: struct
            {
                T temp;
                temp = a;
                a = b;
                b = temp;
            }

带委托的泛型方法

https://www.jianshu.com/p/5a12f3f74c62

泛型方法规范

泛型方法可以重载,为特定类型定义规范,这也适用于带泛型的方法。

            public  void F(T x)
            {
                WriteLine(x);
            }
            public void F(int x)
            {
                WriteLine(x);
            }
            public void F( T1 x1,T2 x2)
            {
                WriteLine("{0}  {1}",x1,x2);
            }
            public void F(int x1,int x2)
            {
                WriteLine("{0}  {1}", x1, x2);
            }

上面完成F方法的四次重载。
在编译期间,编译器会使用最佳匹配。

泛型脑涂概览

脑图转载自 牧白 # C#-弄懂泛型和协变、逆变

o_190411-generic-mind.png

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