(八)CSharp-泛型类和参数约束(1)

一、C# 中的泛型

泛型(generic)特性可以让多个类型共享一组代码。

泛型类型不是类型,而是类型的模板。

(八)CSharp-泛型类和参数约束(1)_第1张图片

C# 提供了5种类型:类、结构、接口、委托和方法。

(八)CSharp-泛型类和参数约束(1)_第2张图片

泛型类

(八)CSharp-泛型类和参数约束(1)_第3张图片

泛型的主要优点:

  • 性能
    类型转换时,非泛型的类型进行装箱和拆箱时,会使得性能损失比较大。(需要转换运算符以及拷贝内存)

  • 类型安全
    如果在泛型类中,定义了具体类型,编译器就不会编译代码。

  • 二进制代码重用
    泛型可以定义一次,就可以用于不同类型的实例化。

  • 代码的扩展
    1)泛型类的定义会放在程序集中,所以用特定类型实例化泛型类不会在 IL 代码中复制这些类。(即类型实例化的代码在使用泛型时,不会进行拷贝这些实例化的代码)。
    2)在 JIT 编译器把泛型类编译为本地代码时,会给每个值类型创建一个新类引用类型共享同一个本地类的所有相同的实现代码。(比如 List《int》会对其类型进行复制,而 List《MyClass》会对其类型进行引用。)

  • 命名约定

命名规则:

泛型类型的名称用字母 T 作为前缀。
泛型类型允许用任意类替代,且只使用了一个泛型类型。

 public class List { }
    public class LinkedList { }
  • 如果泛型类型有特定的要求(例如,它必须实现一个接口或派生自基类),或者使用了两个或多个泛型类型,就应该泛型类型使用描述性的名称:
    //泛型委托
    public delegate void EventHandler(object sender, TEventArgs e);
    public delegate TOutput Converter(TInput from);
    //泛型类
    public class SortedList { }

二、声明泛型类

泛型声明定义的代码:

    //T1,T2 为类型参数
    class SomeClass
    {
        public T1 SomeVar;
        public T2 OtherVar;
    }

    class Program
    {
        static void Main(string[] args)
        {
            //构造的类型
            var first = new SomeClass();
            var second = new SomeClass();

            Console.ReadKey();
        }
    }

执行测试代码的作用:

   //SomeClass()
    class SomeClass
    {
        public short SomeVar;
        public int OtherVar;
    }

   //SomeClass()
    class SomeClass
    {
        public int SomeVar;
        public short OtherVar;
    }
    

三、比较泛型和非泛型栈

实现泛型栈的代码例子:

    class MyStack
    {
        T[] StackArray;
        int StackPointer = 0;

        public void Push(T x)
        {
            if (!IsStackFull)
                StackArray[StackPointer++] = x;
        }

        public T Pop()
        {
            return (!IsStackEmpty) ? StackArray[--StackPointer] : StackArray[0];
        }


        const int MaxStack = 10;
        bool IsStackFull { get { return StackPointer >= MaxStack; } }
        bool IsStackEmpty { get { return StackPointer <= 0; } }

        public MyStack()
        {
            StackArray = new T[MaxStack];
        }

        public void Print()
        {
            for (int i = StackPointer - 1; i >= 0; i--)
                Console.WriteLine($"  Value: { StackArray[i] }");

        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyStack StackInt = new MyStack();
            MyStack StackString = new MyStack();

            StackInt.Push(3);
            StackInt.Push(5);
            StackInt.Push(7);
            StackInt.Push(9);
            StackInt.Print();

            StackString.Push("This is fun");
            StackString.Push("Hi there!  ");
            StackString.Print();
            Console.ReadKey();
        }
    }

表-非泛型栈和泛型栈之间的区别

非泛型 泛型
源代码大小 更大:需要为每一种类型编写一个新的实现(有重复的代码逻辑) 更小:不管构造类型的数量有多少,只需要一个实现
可执行文件大小 无论每一个版本的栈是否会被使用,都会在编译的版本中出现 可执行文件中只会出现有构造类型的类型 (疑问:设计泛型类型的代码逻辑不在可执行文件里吗?似乎有些明白了,泛型只会放在程序集中,而不会拷贝这些实例化的代码,所以编译器不会编译设计泛型的代码。)
写的难易度 易于书写,因为它更具体 比较难写,因为它更抽象 (我觉得因为想要通用任何可支持的类型,需要考虑点多,越是抽象(类型特征越多),设计的代码结构就越复杂。)
维护的难易度 更容易出问题,因为所有修改需要应用到每一个可用的类型上 (因为类型已固定了,如果更改类型或者代码结构,就会把所有应用到的地方都修改一遍,容易出错。) 易于维护,因为只需要修改一个地方

四、类型参数的约束

1、类型参数的约束

由于泛型不知道它们保存的项的类型是什么,也就不会知道这些类型实现的成员,就不会对这些成员进行运算符的事情。

所以,如果泛型类需要调用泛型类型中的方法,就必须添加约束。

未绑定的类型参数: 符合约束的未绑定的类型参数。如果泛型里有 Object 成员,那么这个Object 成员是已知的类型,可以对它做一些如 ToSting、Equals 以及 GetType 方法的处理。而如果其他未知类型的成员不能像 Object 成员那样可以直接处理的,是未绑定的类型参数。

class Simple
{
static public bool LessThan(T i1,T i2)
{
//错误,因为i1 和 i2 属于未绑定的类型参数,
//不能使用 < 运算符进行处理
retrun i1 < i2;
}
}

2、Where 子句

约束使用 where 子句列出。

  • 每一个有约束的类型参数都有自己的 where 子句。
  • 如果形参有多个约束,它们在 where 子句中使用逗号分隔。

where 子句的语法如下:

where TypeParam : constraint,constraint,...
//where:关键字
//TypeParam:类型参数
//constraint,constraint,...:约束列表

关于 where 子句的要点:

  • 它们在类型参数列表的关闭尖括号之后列出。
  • 它们不使用逗号或其他符号分隔。
  • 它们可以以任何次序列出。
  • where 是上下文关键字,所以可以在其他上下文中使用。
//T1 未绑定,T2 和 T3 具有约束
class MyClass < T1,T2,T3 >
		where T2:Customer //T2的约束
		where T3:IComparable //T3的约束
{
...
}

3、约束类型和次序

公有5种类型的约束:

约束类型 描述
类名 只有这个类型或从它派生的类才能用作类型实参
class 任何引用类型,包括类、数组、委托和接口都可以用作类型实参
struct 任何值类型都可以用作类型实参(包括枚举? 好吧,所有值类型,那就是包括枚举)
接口名 只有这个接口或实现这个接口的类型才能用作类型实参
new() 任何带有无参公共构造函数的类型都可以用作类型实参。这叫作构造函数约束

where 子句可以以任何次序列出。但是,where 子句中的约束必须有特定的顺序

  • 最多只能有一个主约束,而且必须放在第一位。
  • 可以有任意多的接口名称约束
  • 如果存在构造函数约束,则必须放在最后
约束 约束类型 个数
主约束 ClassName class struct 0或1个
次约束 InterfaceName 0或多个
构造函数约束 new() 0或1个
class SortedList
		where S:IComparable{...}

calss LinkedList
		where M : IComparable
		where M : ICloneable{...}

class MyDictonary
		where KeyType:IEnumerable,
		new()               {...}

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