面向对象 Object Oriented

  • 反射
  • 泛型
  • ==C#中表达抽象的语义==
  • 概述

    面向过程的程序 = 算法 + 数据结构; 关心解决问题的步骤。
    面向对象的程序 = 对象 + 交互; 关心谁在解决问题,在做什么。
    类:一个抽象的概念,即为生活中的“类别”。
    对象:类的具体实例,即归属于某个类别的“个体”。
    同类型的多个对象,行为相同,数据不同。

    主要思想

    分而治之 — 将一个大的需求分解为许多类,每个类处理一个独立的模块。
    拆分好处:独立模块便于分工,每个模块便于复用,可扩展性强。

    封装变化 — 变化的地方独立封装,避免影响其他模块。

    高 内 聚 — 类中各个方法都在完成同一项任务(类的职责单一;只做同一件事)。
    列:一个类只进行攻击的实现并不属于高内聚:因为攻击的方式不同;如果对每种攻击方式为单独的一种类则可以称之为高内聚。
    复杂的实现封装在内部,对外提供简单的调用。

    低 耦 合 — 类与类的关联性依赖度要低(类与类之间相互独立;之间没有过多的联系)。
    让一个模块的改变,尽少影响其他模块。

    [例如:硬件高度集成化,又要可插拔]
    最高的内聚莫过于类中仅包含1个方法,将会导致高内聚高耦合。
    最低的耦合莫过于类中包含所有方法,将会导致低耦合低内聚。

    优势

    高复用;(同一个类或类中方法对于多种需求都适用;当修改时只修改那一个类或类中 方法)
    高扩展;(当有新需求时,添加新类时对之前所编写的类无影响)
    高维护;(方便进行维护)
    高移植性。(方便移植:以类为单元)
    (复用,灵活,简单。)

    面向对象的三大特性

    封装:
    数据角度讲,将一些基本数据类型复合成一个自定义类型---->类。
    方法角度讲,向类外提供功能调用,隐藏实现的细节。
    设计角度讲,分而治之,高内聚低耦合,封装变化。
    作用:松散耦合;简化编程;增强安全性
    继承:
    统一概念。
    重写现有类的功能,在此基础上进行扩展(功能;概念)。
    当需要一个类去代表多个类时
    多态:
    父类的同一种动作或者行为,在不同的子类上有不同的实现。
    (父类调用同一方法,在不同的子类上有不同的执行效果)
    实现手段:虚方法、抽象方法、接口。(接口优于虚方法与抽象方法因为多实现)

    类的四大关系

    泛化(继承):子类与父类的关系,概念的复用,耦合度最高;
    B类泛化A类,意味B类是A类的一种;
    做法:B类继承A类。

    实现:抽象行为的具体实现,两者体现功能的关系,变化只影响行为;
    A类实现B类,意味A类必须具体实现B类中所有抽象成员。
    做法:实现抽象类、接口中的抽象成员。

    关联(聚合/组合):部分与整体的关系,功能的复用,变化影响一个类;
    A与B关联,意味着B是A的一部分;
    做法:在A类中包含B类型成员。

    依赖:合作关系,一种相对松散的协作,变化影响一个方法;
    A类依赖B类,意味A类的某些功能靠B类实现;
    做法:B类型作为A类中方法的参数,并不是A的成员。

    设计的八大原则

    开-闭原则(目标、总的指导思想)

    Open Closed Principle
    对扩展开放,对修改关闭。
    增加新功能,不改变原有代码。

    类的单一职责(一个类的定义,只做一类事)

    Single Responsibility Principle
    一个类有且只有一个改变它的原因。
    适用于基础类,不适用基于基础类构建复杂的聚合类。

    依赖倒置(依赖抽象)

    Dependency Inversion Principle
    客户端代码(调用的类)尽量依赖(使用/调用)抽象的组件。
    隔离用与做
    抽象的(“父类”)是稳定的。实现(“子类”)是多变的。

    组合(关联)复用原则(复用的最佳实践)

    Composite Reuse Principle
    如果仅仅为了代码复用优先选择组合(关联)复用,而非继承复用。
    组合:在A类中包含B类的成员
    组合的耦合性相对继承低。

    里氏替换(继承后的重写,指导继承的设计)


    Liskov Substitution Principle
    父类出现的地方都可以被子类替换,并且在替换后依然保持原功能。
    子类可以扩展父类的功能,但不能改变父类原有的功能。
    子类拥有父类的所有功能。
    子类在重写父类方法时,尽量选择扩展重写,以防止改变父类拥有的功能功能。
    扩展重写:例:base.“父类方法”+子类成员;


    接口隔离(功能拆分)

    Interface Segregation Principle
    ==尽量定义小而精的接口interface,少定义大而全的接口。==本质与单一职责相同。
    小接口之间功能隔离,实现类需要多个功能时可以选择多实现.或接口之间做继承。

    面向接口编程而非面向实现(切换、并行开发)

    客户端通过一系列抽象操作实例,而无需关注具体类型。
    便于灵活切换一系列功能。
    实现软件的并行开发。

    迪米特法则(类与类交互的原则)

    Law of Demeter
    不要和陌生人说话。
    ==类与类交互时,在满足功能要求的基础上,传递的数据量越少越好。==因为这样可能降低耦合度。

    封装

    定义

    1. 数据角度讲,将一些基本数据类型复合成一个自定义类型。(符合人类思考方式,便于操作数据)
    2. 方法角度讲,向类外提供功能,隐藏实现的复杂细节。
    3. 设计角度讲,分而治之,高内聚低耦合,封装变化。
      ----封装的应用:类;属性;构造函数;----

    作用

    1. 松散耦合,降低了程序各部分之间的依赖性。
    2. 简化编程,使用者不必了解具体的实现细节,只需要调用对外提供的功能。
    3. 增强安全性,以特定的访问权限来使用类成员,保护成员不被意外修改。

    访问修饰符

    1. private:私有的,类成员默认级别,仅在类内部可见。
    2. internal:内部的,类默认级别,仅在程序集(.cs文件编译后为.dll)内可见。
    3. protected:受保护的,类内部与派生类中可见;有继承关系的类之间;跨程序集也可以只需要有继承关系。
    4. protected internal:意为 internal 或 protected; 程序集内或者派生类中可见。
    5. public:公有的,类内类外都可见。

    案例

    需求

    1. 玩家可以通过摇杆控制主角运动。
    2. 运动过程中播放相应的动画。
    3. 玩家控制主角打怪,怪受伤,可能死亡。

    分析

    角色系统,成长系统,技能系统,动画系统,运动系统,任务系统,背包系统,结算系统。

    1. 识别对象:主角,小怪,输入控制,动画。
    2. 分配职责:
      主角:存储状态(攻击力,攻击速度,生命,魔法),受伤,死亡。
      小怪:存储状态(攻击力,攻击速度,生命,魔法),受伤,死亡。
      马达:移动, 旋转。
      输入控制:控制移动,控制攻击。
      动画系统:管理动画片段,提供动画事件。,
    3. 建立交互:
      移动: 检测到玩家开始移动–>调用动画系统播动画–>调用马达的移动方法–>检测到玩家松开按钮结束触摸–>调用动画系统取消动画。
      打怪: 按下技能按钮–>调用技能系统释放技能–>调用动画系统播动画–>处理动画事件
      –>调用小怪的受伤–>可能调用死亡方法。

    设计

    1. 运动系统
      角色马达:CharacterMotor
      数据:移动速度 moveSpeed,转向速度 rotationSpeed,角色控制器(chController)
      行为:移动(Movement),转向(LookAtTarget)
    2. 动画系统
      角色动画参数类:CharacterAnimationParameter
      数据:动画片段
      *动画事件行为:AnimationEventBehaviour
      数据:动画组件(anim)
      行为: 攻击时使用(OnAttack),撤销动画播放(OnCancelAnim)
    3. 角色系统
      主角状态:PlayerStatus
      数据:生命(HP,maxHP),魔法(SP,maxSP),基础攻击力(baseATK),防御(defence),攻击间隔 (attackInterval),攻击距离(attackDistance),动画参数(animParams)
      行为:受击(Damage) 死亡(Dead)
      小怪状态:MonsterStatus
      数据:生命(HP,maxHP),魔法(SP,maxSP),基础攻击力(baseATK),防御(defence),攻击间隔 (attackInterval),攻击距离(attackDistance),动画参数(animParams)
      行为:受击(Damage),死亡(Dead)
      摇杆输入控制:CharacterInputController
      数据:马达(chMotor), (EasyTouch插件)
      行为:摇杆移动执行的方法,摇杆停止时执行的方法

    继承 extends

    定义

    重用现有类的功能,在此基础上进行扩展(功能、概念)。[提取共性]

    优点

    1. 复用代码的一种方式。
    2. 统一概念,以层次化的方式管理类。

    缺点

    耦合度高:父类的改变直接影响到所有的子类,而不需要通知子类。

    适用性

    1. 多个类具有相同的数据或行为。
    2. 多个类从概念上是一致的,且需要进行统一处理。

    语法

    class A: B
    {
    }
    表示A类继承B类,A类称为子类(派生类),B类称为父类(基类,超类)
    通过this关键字访问本类成员、通过base关键字访问父类成员。
    一个类最多只能继承另一个类。

    继承中的构造方法

    构造方法不会继承给子类,但是在创建子类对象时,自动调用父类的构造方法,且父类构造方法先执行,子类构造方法后执行.
    当子类采用无参构造方法创建对象时,默认调用父类的无参构造方法,如果父类没有无参构造方法,则报编译错误,解决方法有两个: 1.为父类添加无参构造方法, 2.在子类的构造方法中用base关键字指明要调用父类的哪一个有参构造方法

    抽象类

    语法

    用abstract修饰类即为抽象类.
    抽象类不能创建对象
    抽象类中可能包含抽象成员(方法,属性);可以包含普通方法,字段

    语义

    抽象类表示一个概念的抽象(可以存储子类直接使用的成员).
    抽象只表示做什么,拥有什么数据,但往往不表达具体做法,不表达数据具体取值

    适用性

    1. 当不希望类创建对象的时候。
    2. 当有行为,但是不需要实现的时候。
    3. 当有一些行为,在做法上有多种可能时,但又不希望客户(调用者)了解具体做法。

    抽象类与普通类区别

    相同:都可以有静态、实例成员(数据、方法、构造函数)
    不同:抽象类使用abstract修饰,可能有抽象方法,属性等,不能直接创建对象

    抽象方法

    语法

    用abstract修饰并且没有实现的方法.只有方法声明,没有实现。
    抽象方法只能出现在抽象类中。
    抽象方法在本类中不实现,实现推迟到子类中,子类必须重写override实现。

    语义

    抽象方法表达抽象行为,只关注本身,不关注行为实现。
    抽象方法描述做什么,不描述怎么做。
    抽象方法一个行为的抽象。

    多态

    定义

    父类同一种动作或者行为(父类的引用调用同一方法),在不同的子类上有不同的实现。
    继承将相关概念的共性进行抽象,并提供了一种复用的方式;多态在共性的基础上,体现类型及行为的个性化,即一个行为有多个不同的实现。

    实现手段

    1. 虚方法: 父类型的引用 指向 子类的对象,调用虚方法,执行子类中的重写方法。
    2. 抽象方法:抽象类的引用 指向 实现类的对象,调用抽象方法,执行实现类中重写方法。
    3. 接口:接口的引用 指向 实现类的对象,调用接口方法,执行实现类中重写方法。
      接口优于虚方法与抽象方法

    方法隐藏

    定义:在子类中使用new关键字修饰的与父类同签名(方法名称与参数“数量,数据类型和顺序,修饰符”)的方法并且没有声明为virtual(虚方法)。
    作用:父类的方法在子类中不适用,且通过子类型引用调用时,隐藏掉从父类继承的旧方法,好像该方法不存在。但是可以使用base关键字调用父类中方法

    解决脚本生命周期冲突:1.方法隐藏;2.在子类方法体内通过base关键字调用父类中的方法。
    例子:
    //基类
    class MyBase
    {
        public int MyMethod(){}
    }
    //子类:
    class MyDerived
    {
        public new int MyMethod(){}
    }



    隐藏原理

    子类在方法表中增加一个新地址。
    所以通过子类引用调用时使用新纪录,执行子类中新方法;
    通过父类引用调用时使用旧纪录,执行父类中方法。

    虚方法

    定义:用vritual关键修饰的已实现方法。
    作用:可以在子类中重写的方法;不用必须实现。
    语法:public virtual void EnterState() { } 语句结束不用添加分号

    方法重写

    语法:在子类中使用override关键字修饰的方法。
    作用:父类的方法在子类中不适用(虚方法),或父类没有实现(抽象方法)。子类重写可以满足对该方法的不同需求。方法重写时必须在方法前加override关键字。

    目的:为实现调用父类引用执行子类对象,若想调用父类的方法使用base.父类方法

    解决脚本生命周期冲突:1.方法隐藏;2.在子类方法体内通过base关键字调用父类中的方法。


    三种方法可以重写
    abstract 方法在子类必须重写,除非子类也是抽象类。
    virtual 方法在子类可以重写,使父类方法的做法与子类有所不同。
    override方法,已经重写过的方法,在子类还可以继续重写,除非被标识为sealed。

    重写原理

    父类在方法表中修改对应的地址。
    所以不管通过父类还是子类的引用,调用方法时,都执行对象真实类型中定义的方法。

    动态绑定(晚期绑定)与静态绑定(早期绑定)

    绑定:类型与关联的方法的调用关系,通俗讲就是一个类型能够调用哪些方法。
    静态绑定:是指调用关系是在运行之前确定的,即编译期间。
    动态绑定:是指调用关系是在运行期间确定的。
    静态绑定因为在编译期确定,不占用运行时间,所以调用速度比动态绑定要快。
    动态绑定因为在运行期确定,占用运行时间,性能更低些,但是更灵活。
    方法重写是动态绑定。(需要更改父级方法表中的方法地址)
    方法隐藏是静态绑定。

    密封 Sealed

    1. 用在类的定义上,指示当前类不能做父类,也就是任何类都不可继承当前类
    2. 用在重写的成员,指示当前类的子类,不能再次重写该成员

    接口

    定义

    接口定义一组对外的行为规范,要求它的实现类必须遵循。
    接口只关注行为,不关注数据,且不关注行为的实现,实现由实现类(子类)完成。
    接口自身表达“能够做”,不表达“如何做”。
    接口是一组行为的抽象。

    一组对外的行为规范:
    ==一组:==接口中可以包含多种方法(抽象)
    ==对外:==要求实现类(子类)去实现;自身不用去实现,仅做抽象方法的声明
    ==行为:==接口自身只包含抽象的方法成员(属性,方法)
    ==规范:==接口中的抽象成员要求实现类(子类)必须自行实现

    作用

    规范不同类型的行为,达到了不同类型在行为上是一致的。
    扩展一个已有类的行为。

    语法

    使用interface关键定义。接口名建议用”I”开头,其后单词首字母大写。
    例:public interface IPoint {};
    接口中不能包含字段,可以包含:方法(不包括方法体),属性,索引器,事件。
    接口中的成员一定是public abstract的,但是不能写。
    接口中的所有成员不能有实现,全部默认抽象的。
    实现类实现接口用“:”与继承相同。
    实现类实现可以实现多个接口,且每个接口中所有的成员必须都实现。所实现的成员不用使用abstract修饰
    接口中的成员在实现类中以public的方式实现(除显式实现)。
    接口的引用可以指向实现类的对象。
    接口与接口之间可继承,且可以多继承。
    类与类是单继承,类与接口是多实现,接口与接口是多继承。
    结构(struct)可以实现接口,但不能被继承。

    抽象类与接口的选择策略

    抽象类与子类之间关系:is a [是一种]
    接口与实现类之间关系:can do [能够做(功能)]

    接口的显式实现

    作用:

    1. 解决多接口实现时的二义性:不同接口中的成员名称相同,并被同一实现类继承

    2. 解决接口中的成员对实现类不适用的问题:当不想使用接口中的某一成员时
      做法:
      在实现的成员前加接口名,并且不能加任何访问修饰符,默认为private
      显式实现成员只能通过接口类型的引用调用。




    Framework常用接口

    IComparable 可比较,使类型支持比大小的功能;适用性:常用的排序依据
    IComparer 比较器,提供比较的方法,常用于排序比较;适用性:不太常用的排序依据
    IEnumerable 可枚举(可迭代),使类型支持简单迭代(foreach)
    IEnumerator 枚举器(迭代器),支持MoveNext ,自己可以控制迭代的节奏;迭代器接口类型

    面向接口编程

    例:当对对象进行排序操作时,调用IComparable接口,通过接口对具体的类进行操作(依赖倒置;隔离用于做)

    Unity 协同程序(Coroutine)

    定义

    具有多个返回点(yield);将方法分为多个部分,可以在特定时机分部执行的函数。

    原理

    Unity每帧处理GameObject(游戏队象)中的协同程序,直到函数执行完毕。
    当一个协程函数启动时,本质创建迭代器对象;调用MoveNext方法,执行到 yield 暂时退出;待满足条件后再次调用MoveNext方法,执行后续代码,直至遇到下一个yield为止。如此循环至整个函数结束。

    语法

    通过MonoBehaviour中的StartCoroutine启动,StopCoroutine停止。
    协程函数返回值类型为IEnumerator,方法体中通过yield关键字定义返回点,通过return xx对象定义继续执行的条件。
    可以被yield return 的对象:

    1. null或者 数字 --> 等待一个渲染帧;在Update后执行,适合分解耗时的物理逻辑
    2. new WaitForSeconds() --> 等待指定时间;适合延迟调用
    3. new WaitForSecondsRealtime() --> 等待指定时间(不受时间缩放影响)
    4. new WaitForFixedUpdate() --> 等待一个物理帧;在FixedUpdate后执行,适合分解物理操作
    5. new WaitForEndOfFrame() --> 等待一帧结束;在每帧结束后执行,适合相机的跟随操作
    6. new WaitUntil(委托)–>在委托返回true时执行,适合等待某一操作
    7. new WaitWhile (委托) --> 在委托返回false时执行,适合等待某一操作
    8. Coroutine --> 在另一个协程函数执行完毕后再执行。
    9. WWW --> 在请求结束后执行,适合加载数据,如文件,贴图,材质等

    作用

    1. 延时调用。
    2. 分解操作。

    代码案例:

    委托

    定义

    将方法包装成对象,通过调用委托对象间接调用方法,实现回调机制。
    委托是引用类型,使用方法创建委托对象,方法必须符合委托的原型。
    委托是一类行为的抽象。

    使用

    1. 定义委托数据类型 delegate 返回值 方法签名
      delegate void Handler();
    2. 声明委托变量 Handler handler;
    3. 创建委托类型对象
      handler = new Handler(Fun);//Fun符合委托类型的方法
    4. 调用委托handler();
    另一种方法:
    1. 自定义一个委托
    2. 声明一个委托变量
    3. 对委托绑定方法
    4. 调用委托:委托变量();
    例:

    作用

    1. 将方法做为参数进行传递,可以将一个方法的执行代码注入到另一个方法中。
    2. 实现回调,且比接口更加灵活。
    3. 实现任意方法的异步调用。
    4. 事件实现的基础。

    原理

    1. 自定义的委托类型本质就是个类,包含Invoke、BeginInvoke、EndInvoke方法。
    2. 该类继承MulticastDelegate类,包含委托链Object _invocationList,实质就是Delegate[ ]类型。
    3. MulticastDelegate 类又继承Delegate类,包含实例方法的对象引用Object _target,方法句柄IntPtr _methodPtr。
    4. 创建委托对象(绑定方法),就是设置_target与_methodPtr字段。
    5. 调用委托对象,本质就是调用Invoke方法,内部找到_methodPtr字段,从而调用方法。

    多播委托

    1. 本质上是委托的一种特殊形式,以委托链的形式存在。
    2. 多播委托绑定的方法有返回值,调用委托对象时默认只会接收到最后一个方法的值。
    3. 获取每个方法返回值:调用委托GetInvocationList()方法,获取委托链,然后循环调用DynamicInvoke方法,即动态调用每个方法,可获取返回值。

    原理

    1. 委托对象绑定(+=)多个方法时,本质调用Delegate.Combine(原对象,新对象)方法。

    2. Combine方法内部创建新委托链(Delegate[ ]),再创建新委托对象,存入_invocationList字段中。

    3. 委托对象移除(-=)某个方法时,本质调用Delegate.Remove(原对象,删除对象)方法。

    4. Remove方法内部删除匹配的委托(判断_target与_methodPtr)。

    5. 如果删除后委托链中只剩下一个委托,则返回该委托;否则再新创建一个委托对象,初始化新委托链。

    委托实现方法的异步调用

    同步调用方法:排队调用,前一个方法执行时,后一个方法等待它的结束后才能启动。
    异步调用方法:不排队调用,后一个方法不必等待它的结束就可启动,异步调用的方法是创建了一个新线程来工作的,与后一个方法所在不同的线程,各自独立,互不影响。
    Framework中的任何方法都可以通过委托异步调用。(BeginInvoke)
    步骤:

    1. 为需要异步调用的方法定义一个相应的委托。
    2. 创建该委托的引用指向需要异步调用的方法。
    3. 使用委托类型的BeginInvoke方法开始异步调用。
      a) BeginInvoke中的参数IAsyncCallback 表示异步调用的方法结束时执行的回调方法,往往用回调方法来处理异步的结果。
      b) BeginInvoke中的参数object 表示在回调方法中需要用到的数据,该数据被封装在IAsyncResult的AsyncState属性中。
    4. 如方法有返回值,则需要调用EndInvoke取得返回值。

    事件

    定义

    当某一对象达到某种条件或发生某种改变时,将消息及时的通知到其它对象。事件基于委托,为委托提供了一个发布/订阅的机制。

    使用

    1. 定义事件参数类 XXXEventArgs:封装引发事件时需要由事件源传递给响应者的信息。
    2. 定义委托 :public delegate void XXXEventHandler(XXXEventArgs e);
    3. 定义事件源类
      – 基于委托声明事件 public event XXXEventHandler XXX;
      – 在某个方法内部引发事件。
    4. 响应者
      – 实例化事件源对象。
      – 注册方法:对象名.事件名+=方法名;
      温馨提示:即时第一次注册事件,也使用+=操作符。

    作用

    仅仅允许在声明类的外部注册、注销事件、其余操作包括事件的声明和调用在声明类的内部进行,事件的注册与注销也可以在声明类中进行,防止了例如清空委托链、直接引发事件等不正当操作,体现了封装思想。

    原理

    在事件源中声明事件,会编译为一个私有委托字段,一个公开add_XXX方法用于注册事件,一个移除remove_XXX方法用于注销事件。

    反射

    定义

    编译时无法创建对象;需要在运行时创建对象;依赖注入
    动态获取类型信息,动态创建对象,动态访问成员的过程。(使用字符串进行操作)

    作用

    在编译时无法了解类型信息,无法创建对象,在运行时获取类型信息,创建对象,访问成员。
    性能较差

    流程

    1. 得到数据类型
    2. 动态创建对象
    3. 查看类型信息(了解本身信息,成员信息)

    流程具体实现

    得到数据类型Type

    表示类型声明:类类型、接口类型、数组类型、值类型、枚举类型、类型参数、泛型类型定义,以及开放或封闭构造的泛型类型。

    方式一:根据类型名称–Type.GetType(“类型全名(命名空间.类名)”);
    适合于类型的名称已知
    方式二:根据对象–obj(以实例出的对象).GetType();
    适合于类型名未知,类型未知,存在已有对象
    方式三:根据数据类型–typeof(数据类型)
    适合于已知类型
    方式四:Assembly.Load(“XXX”).GetType(“名字”);
    适合于类型在另一个程序集中

    动态创建对象

    1.Activator.CreateInstance(string 程序集名称,string 类型全名)
    Activator.CreateInstance(Type type);

    2.Assembly assembly = Assembly.Load(程序集);
    assembly.CreateInstance(Type);

    //找到有参构造方法,动态调用构造方法
    3.type.GetConstructor(typeof(string)).Invoke()

    查看类型信息

    Type类常用:
    Get系列方法:GetProperty();GetMethod();
    Is系列属性:

    1. MethodInfo(方法)
      重要方法: Invoke(调用)
    2. PropertyInfo(属性)
      重要方法:SetValue GetValue
    3. FieldInfo(字段)
      重要方法:SetValue GetValue
    4. ConstructInfo(构造方法)
      重要方法:Invoke(调用)

    泛型

    相关类:ArrayHelper

    泛型的定义

    普通类:public class Generic{ public String Name;}
    泛型类:public class Generic{public T Name;}
    使用了;T表示type(类型)

    泛型的使用

    泛型类跟普通类的使用方式一样,都需要实例化对象,再由对象来调用内部的属性或方法。

    泛型的默认值

    泛型的默认值,如下面代码所示。需要使用default(T)来赋值。
    public class Generic{public T Name = default(T); }

    泛型的约束

    语法:where T:具体约束

    例:where T : Base

    T:结构

    类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息,请参见使用可空类型(C# 编程指南)。

    T:类

    类型参数必须是引用类型,包括任何类、接口、委托或数组类型。

    T:new()


    指定泛型类或方法声明中的类型实参必须有公共的无参构造函数;若使用new约束,则该类型不能为抽象类。
    new()约束可以与其他约束一起使用,但必须位于约束列表的末端。
    不可以同时使用new()约束和值类型约束。因为值类型和new()都隐式的提供了一个无参公共构造函数。


    T:<基类名>

    类型参数必须是指定的基类或派生自指定的基类。

    T:<接口名称>

    类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。

    T:U

    为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。


    C#中表达抽象的语义

    1. 抽象类:一个概念的抽象(包含普通成员,抽象成员)
    2. 接口:一组行为上的抽象(多个抽象成员)
    3. 委托:一种类型上(签名相同)的抽象(同一类型有多种方法)
      接口优于抽象类