《Visual C#从入门到精通》个人学习整理

该文章是本人自己阅读并学习了《Visual C#从入门到精通》(第九版)之后进行的整理和总结

对个人认为重点的知识点进行了概括总结,帮助自己学习C#,分享出来希望能对读者有所帮助,也让自己进步

一定会存在各种漏洞欢迎指出!

目录:

第七章:类和对象

第八章:值和引用

第九章:枚举和结构

第十章:数组

第十一章:理解参数数组

第十二章:继承

第十三章:接口和抽象类

第十四章:垃圾回收和资源管理

第十五章:属性

第十六章:索引器

第十七章:泛型

未完待续。。。

类和对象

7.3定义和使用类

定义:

Eg.class A

{

………;

}

实例化:

A a=new A();

相同类的实例的赋值:

A a1=new A();

a1=a;

7.4控制可访问类型

private 私有的

public 公共的

eg.private int a;

构造器

构造器是在创建类的实例时自动运行的方法;

可以获取参数,但不能有返回值;

每个类都有一个构造器,不写会自动添加默认构造器,如果写了非默认构造器就不会自动生成默认构造器了,如果还需要默认构造器就需要手动写一个;

        默认private,只能在类内创建该类实例,需要手动加public;

定义构造器:

class A

{

    private int a;

    public A()

    {

        a = 0;

    }

}

        可以写构造器的重载,来获取参数以初始化类的数据:

class Circle

{

    private int radius;

    public Circle(int r)

    {

        radius = r;

    }

}

调用:Circle c = new Circle(45);

分部类:一个类分成几个部分写,用关键词partial

partial class Circle

{

    private int radius;

    public Circle(int r)

    {

        radius = r;

    }

}

......

partial class Circle

{

    private int b;

}

注意:类的字段的private修饰也可以在同一个类的不同实例中相互访问,字段的private指的是级别上的私有,而非对象级的私有;

解构器:

作用:检查对象并提取字段的值

class Point

{

    private int x, y;

    public void Deconstruct(out int x, out int y)

    {

        x = this.x;

        y = this.y;

    }

}

注意:

1.必须命名为Decontruct

2. 必须是void

3.必须获取一个或多个参数来填充

4.用out标记

5.方法主体代码向参数赋值

调用:用元组的方式获取

Point origin = new Point();

(int x, int y) = origin;

7.5理解静态方法和数据

const 常量字段,是特殊的静态字段,值不会变,不用写static

静态类 只能包含静态成员 作为工具方法和字段容器使用 不能包含任何实例数据或方法 初始化可以写静态构造器,必须是默认构造器

public static class Math

{

    public static double Sin(double x) {…}

    public static double Cos(double x) {…}

    public static double Sqrt(double x) {…}

}

匿名类——没有名字的类  

定义方法:var myAnonymousObject = new {Name ="John ",Age = 54 } ;

Var:对变量初始化的表达式为什么类型,就用这个类型创建变量

C#根据匿名类定义时里面的字段名,值类型,顺序是否相同自动归类

只能包含public字段且必须都初始化 不能是static 不能定义方法

值和引用

值类型和引用类型的区别:

值类型: 包含int,long,double,char,结构等,除string外

    赋值过程:开辟空间(int i=42)->将值赋进去

引用类型: 包含string,类,等

    赋值过程:开辟地址空间->将引用对象(类的实例)的地址存入

如何复制一个引用类型:

Circle c = new Circle();

Circle refc = c;->若出现了引用,只复制了引用,没有复制引用的对象(指向了同一个对象)(浅拷贝)

解决浅拷贝办法:使用Clone方法返回自己的新实例,并填充相同的数据(拆分成两个对象)(深拷贝)

定义Clone方法:

class Circle

    {

        private int radius;

        //省略构造器和其他方法

        public Circle Clone()

        {

            Circle clone = new Circle();

            clone.radius = this.radius;

            return clone;

        }

}

Clone方法可以访问私有数据;

注意:如果Circle类里的引用类型要Clone,则引用类型也需要有Clone方法

私有指“类级别上私有”而非“对象级别上私有”=>相同类的实例可以访问互相的private

8.2 理解null值和可空类型

    null

    null 用于初始化引用类型的实例

    空条件操作符 ? 作用:用于判断变量是否为空,如果为空,就不返回值

Eg.Console.WriteLine($"{c?.Area}");

可空类型:引用类型

定义: int? i = null; 值类型可以赋给可空类型,可空理性不可以赋给值类型

可空类型的两个属性:

    变量名.HasValue

    变量名.Value

8.3 使用ref和out参数

    ref—使实参传递给形参时传递实参的引用而非拷贝(可以直接改变实参)

    Eg. static void doIncrement(ref int param)

        {

            param++;

        }

        static void Main(string[] args)

        {

            int arg = 42;//必须要赋初值

            doIncrement(ref arg);

            Console.WriteLine(arg);//输出为43

        }

   

    out—ref的功能+可以不初始化变量,到方法内初始化(使用规则同ref)

    Eg. static void doIncrement(out int param)

        {

            param = 42;//方法内初始化

            param++;

        }

        static void Main(string[] args)

        {

            int arg;//未初始化

            doIncrement(out arg);

            Console.WriteLine(arg);//输出为43

        }

8.4 计算机内存的组织方式

    栈存放:参数,局部变量(即所有值类型)   生存期良好  

注意:对对象的引用(地址)在栈中

堆存放:可空类型,对象(即引用类型的实例)   

生存期差,最后一个引用消失时可被重用

堆内存有限

8.5 System.Object

    System.Object类->可直接写为object

  1. 所有类都是System.Object类的派生类
  2. System.Object可以引用任何对象

Eg. Circle c;

               c = new Circle(42);

                object o;

   o = c;

8.6 8.7 装箱和拆箱

    装箱:将数据项从栈自动复制到堆的行为称为装箱

         Eg. int i = 42;

              object o = i;//装箱

    拆箱:从装箱中提取出值得过程叫拆箱

          需要用到强制类型转换

          强制类型转换会检查已装箱的类型是否正确

          Eg. int i = 42;

               object o = i;//装箱

               i = (int)o;//拆箱

    装/拆箱会产生大量开销,因为有许多检查工作且需要分配额外的堆内存,不能滥用

8.8 数据的安全类型

    is操作符:判断对象类型是否时所期望的类型

        if(对象 is 类型)/if(对象 is 类型 对象名)

        Eg. if(o is Circle)/if(o is Circle myCircle)  确保转型安全

    as操作符:尝试将对象转换成类型

        成功->返回转换成功的结果   失败->返回null

        Eg. Circle temp = o as Circle;

   

    C#可以使用指针:需要用unsafe修饰,并生成项目时指定“允许不安全代码”

枚举和结构

9.1使用枚举

    定义:enum 枚举名 {元素名,}   使用:枚举名 枚举实例名;枚举名.元素名;

    Eg. enum Season { Spring,Summer,...}

        Season param;

       param=Season.Spring;

    枚举可以做参数,局部变量,字段

    可空枚举变量 Eg.Season?param=null; //赋初值为空

    枚举类型的字面值:默认从0开始对应

        可以设置字面值:enum Season {Spring=1,Summer,}(此时Summer字面值为2)

        可以设置两个元素相同:{Spring,Summer,Fall=Autumn}

        可以输出字面值:int i=(int)param(param=Season.Spring)

        选择枚举字面值基础类型:enum Season:short {Spring,}

    param++;=>指下一个元素(若溢出,则返回溢出字面值)(param=Season.Spring)

9.2 使用结构

    结构:值类型,在栈上存储    包含字段,方法和构造器    不能人为声明默认构造器

   

    ==或!=不能自动应用于结构变量,可以使用Equals()方法

    重点:若一个概念重点在于值而不是功能,就用结构实现

    结构和类的区别:

结构

值类型

引用类型

储存在栈上

储存在堆上

不能声明默认构造器

可以声明默认构造器

(已写自定义构造器后)能自动添加默认构造器

(已写自定义构造器后)不能自动添加默认构造器

构造器参数字段不会自动初始化

构造器参数字段会自动初始化

字段声明时不能初始化

字段声明时可以初始化

声明结构变量:

Eg. Time parameter;(不会初始化)/Time parameter = new Time();(会初始化)

可空:Time? parameter = null;

    复制结构变量

    Eg. Time a = parameter;(parameter必须要初始化)

    托管代码: .NET框架下:C#->编译器->CIL->CLR->机器指令

   

    C++虽然支持结构,但不支持其中的成员函数(即成员方法),不能包含实例,不能有静态方法

   

数组

10.1声明和创建数组

    声明数组int[] pins = new int[4];//数组是引用类型,储存方法像类

              int[] pins;

              int[] pins = new int[4] { 1, 2, 3, 4 };

              int[] pins = { 1, 2, 3, 4 };

    隐式类型的数组var names = new[] { "a", "b" };

    遍历:for/foreach(首选foreach)

                     

    用for的情况:

  1. foreach只能遍历整个数组,不能遍历部分
  2. foreach不能反向遍历
  3. foreach稚嫩知道元素值,没有索引
  4. 不能修改数组值

foreach遍历长度为0的数组是安全的

数组作为参数和返回值

    Eg. public int[] ReadData(int[])

        {

            int[] data;

            return data;

}

   

    Main方法中的数组参数static void Main(string[] args)

可以在命令行(cmd)启动程序时,指定附加的命令行参数,将这些参数传给CLR,后者将他们作为实参转给Main,每个命令参数都以空格分隔

   

    复制数组

    Eg. int[] pins = { 1, 2, 3, 4 };

            int[] copy;

            //1

            pins.CopyTo(copy,0);//从索引0开始复制进去

            //2

            Array.Copy(pins, copy, copy.Length);

            //3

            int[] copy = (int[])pins.Clone();//Clone方法返回object类,因此要转型

        三个方法都是浅拷贝,要深拷贝需要for虚幻中增加合适的代码

    多维数组int[,] items = new int[4, 6];//方括号内代表数组大小

              items[2, 3]= 6;//方括号内代表元素位置

    交错数组—即不规则数组,其每列长度可以不同

    Eg. int[][] items = new int[4][];

         int[] col0 = new int[3];

         int[] col1 = new int[10];

         items[0] = col0;

         items[1] = col1;

         items[0][1] = 99;

         col0[1] = 99;//与上一行等价

每个类都有ToString方法,可以重写ToString方法使其输出的字符有意义(对于自定义类的数组的输出很重要)

    Eg. public override string ToString()

        {

            string result = "1";

            return result;

        }

    Console.Writeline()会自动调用需要显示的变量的ToString

    随机数:Random random = new Random;

            int a = random.Next(4);//返回随机数,范围0~3

    换行符:$"{Environment.NewLine}"

   

    从方法中返回引用数据:

    Eg.    Person[] family = new Person[4];

            ref Person FindYoungest()

            {

                int i = 2;

                return ref family[i];//返回的必须是方法结束时还存在的变量,不是局部变量

            }

   

理解参数数组

11.2使用参数数组

    参数数组—允许将数量可变的实参传给方法  使用params关键字

    Min(int[] a)和Min(params int[] a) 的区别:

前者需要先定义一个项数确定的数组int[] a={1,2},再Min(a)调用方法,后者可以直接Min(1,2)调用不用提前写数组

    定义:

Eg. public static int Min(params int[] paramlist)

        {

            //省略内容

        }

    调用方法时可以直接 int a=Min(b,c,d);

    注意:

  1. 只有一维数组能用params
  2. 不能只依靠params来重载方法

Eg. public static int Min(int[] a)

   public static int Min(params int[] a)//出错

  1. Params不能用ref out修饰
  2. Params只能是最后一个参数,只能有唯一的params
  3. 非params方法优先于params方法
  4. 为方法声明无params(无参数数组)的版本能优化性能

params object[]—让方法接收object类型的一个参数数组,从而接受任意数量,任意类型的参数

Eg. class Black

    {

        public static void Hole(params object[] paramlist)

        {

            //省略内容

        }

}

    可以不传参—Black.Hole()

    可以传null—Black.Hole(null)

    可以传递一个实际数组作为参数—Black.Hole(array)

    可以传递不同类型的实参—Black.Hole(new object[]{a,1})

   

可选参数:

        Eg. void optMethod(int a,double b=0.0,string c = "hi")

        {

            //省略内容

        }

        A必须要指定;b,c可以不指定值

        Eg.optMethod(1);/optMethod(1,1,1);

参数数组和可选参数分别由同一个方法的重载时,优先使用可选参数

继承

12.2使用继承

            Eg.class Derivedlass:Baseclass//C#只允许从一个类派生

               {

                 //省略内容

               }

    继承只适用于类,不适用于结构!

   

    调用基类构造器

    Eg. class Mammal

        {

            public Mammal(string name)//Mammal构造器必须public

            {

                //省略内容

            }

        }

        class Horse:Mammal

        {

            public Horse(string name):base(name)//调用基类构造器

            {

                //省略内容

            }

        }

    如果不在派生类里调用基类构造器,则会自动添加

    类的赋值

       Horse horse = new Horse(...);

        Mammal mammal=new Horse(...);//合法(子赋父)

        Horse horse = new Mammal(...);//非法(父赋子)

        Mammal.Breath();//合法(Breath是父类里定义的方法)

        Mammal.Trot();//非法(Trot是子类里定义的方法)

        可以使用is,as来检查是否可以赋值

方法签名:包括方法名,参数数量和参数类型,这三个相同就叫方法的签名相同(返回值不计入签名)

虚方法:故意设计成要被重写的方法称为虚方法

Eg.System.object下的ToString

        class Object

        {

            public virtual string ToString()

            {

                //省略内容

            }

        }

注意:virtual不能与static,abstract,override同时使用

重写(override):提供同一个方法的不同实现

    Eg. public override string ToString()

        {

string temp = base.ToString();//派生类中,base关键字用来调用方法的基类版本

            //省略内容(虚方法的内容和普通方法没有区别

}

    注意:

  1. 虚方法不能私有
  2. 虚方法和重写方法的签名必须一致
  3. 只能重写虚方法
  4. 派生类里面有和基类重名的方法并且没有用override,就不是重写基类方法,而是隐藏方法,需要用new消除警告
  5. 重写方法隐式成为虚方法,可在派生类中被重写,但不允许用virtual关键字将重写方法显示声明为虚方法(?不是很理解)
  6. override不能和static,new,virtual同时使用

虚方法和多态:

    多态:写法一样的语句,却能依据上下文调用不同的方法

    Eg. class Mammal

        {

            //省略内容

            public virtual string GetTypeName()

            {

                return "mammal";

            }

        }

        class Horse : Mammal

        {

            //省略内容

            public override string GetTypeName()

            {

                return "horse";

            }

        }

        class Whale:Mammal

        {

            //省略内容

            public override string GetTypeName()

            {

                return "whale";

            }

        }

        class Ardvark : Mammal

        {

            //省略内容

    }

//以下在Main方法内

           Mammal mammal = new Horse();

            Console.WriteLine(mammal.GetTypeName());//输出horse

            mammal = new Whale();

            Console.WriteLine(mammal.GetTypeName());//输出whale

            mammal = new Ardvark();

        Console.WriteLine(mammal.GetTypeName());//输出mammal,体现了多态

    Protected关键字:只有在类的派生类层级下可以访问protected

       

12.3 创建扩展方法

    用于快速扩展已有类型,允许添加静态方法来扩展现有类型(无论是类还是结构

    引用被扩展类型的数据,即可调用扩展方法

    编译器会自动识别当前作用域的所有静态类,找出给定类型定义的所有扩展方法

扩展方法在一个静态类中定义,被扩展类型必须是方法的第一个参数,而且必须附加 this关键字

Eg.  namespace Extensions

    {

static class Util//定义扩展方法

              {

                 public static int ConvertToBase(this int i,int baseToConvertTo)

//被扩展类型必须是方法的第一个参数,this后面的类型就是扩展的类型,后面的都是方法的参数

{

                     //省略内容

                     return result;

                 }

        }

      }

   

using System;

using Extensions;//引入定义了扩展方法的命名空间

//省略固定结构

Console.WriteLine($"{x} in base {i} is {x.ConvertToBase(i)}");

    在调用扩展方法时需要将静态类进入当前作用域(使用using 命名空间)

   

接口和抽象类

13.1理解接口

    接口:描述了类提供的功能,不描述功能具体如何实现

    接口约等于协议=>实现了接口的类必然包含接口规定的所有方法

    接口能够定义方法和属性,不能有字段

    定义:  interface IComparable

           {

                 int CompareTo(object o);//接口的成员默认public,不用加访问修饰符

        }

实现:class Horse:IComparable

         {

            public int CompareTo(object o)//这种方法又叫隐式实现接口

            {

                //省略实现内容

            }

      }

注意:

  1. 方法名和返回类型完全匹配
  2. 所有参数(包括ref,out)都要完全匹配
  3. 实现接口方法需要public(但若使用显示接口实现【即实现时附加接口名前缀】,则不能加访问修饰符)

同时继承类,实现接口:(先写基类,再写接口

        class Horse:Mammal,IComparable

           {

              //省略内容

        }

接口扩展:interface IA:IB

通过接口引用类

            Eg.Horse horse = new Horse();

            ILandBound ihorse = horse;//合法,含有该接口的类可以直接赋给接口

接口作为参数:

    Eg. int FindLandSpeed(ILandBound landBoundMammal)

            {

                //省略内容

        }

多个接口:

        Eg. class Horse:Mammal,ILandBound,IGrazable

           {

            //省略内容

        }

显式实现接口:

    解决不同接口中同名的方法的区分

    Eg. interface IJourney

        {

            int LegsNumber();

        }

        interface ILandBound

        {

            int LegsNumber();

        }

        //定义两个有同名方法的接口

        class Horse:Mammal,IJourney,ILandBound

        {

            int IJourney.LegsNumber()

            {

                //省略内容

            }

            int ILandBound.LegsNumber()//能够区分不同接口的同名方法

            {

                //省略内容

            }

     }

    注意:显式实现接口不用写public

    调用同一个类中不同接口的同名方法:

               IJourney iJourney = new Horse();//注意这一步

            int i = iJourney.LegsNumber();

    注意:尽量显式实现接口

    接口的限制:

  1. 接口不能有字段
  2. 接口不能有构造器,析构器
  3. 接口方法不能有访问修饰符
  4. 不能嵌套任何类型
  5. 接口不能从类,结构继承

13.2 抽象类

    抽象类:为明确声明不允许创建某个类的实例(因为一般都为了提供通用的默认实现而创建出来,因此创建出来没有意义),必须将那个类显式声明为抽象类,用abstract关键字

    抽象方法:与虚方法类似,只是不含方法主体,不能私有

    Eg. abstract class GrazingMammal:Mammal,IGrazable

        {

            public abstract void DigestGrass();//抽象方法

        }

13.3密封类

    不能作为基类的类可以声明为密封类

    Eg. sealed class Horse

        {

            //省略内容

        }

    注意:不能包含虚方法     抽象类不能密封

    密封方法:

       是方法的最后一个实现,意味着派生类不能重写该方法

       方法要声明为sealed override(只有有override才能写成密封方法)

垃圾回收和资源管理

14.1对象生存期

垃圾回收:销毁对象并将内存归还给堆的过程

托管对象:类的对象,作用域内的变量等

非托管对象:文件,数据库等

析构器:在对象被垃圾回收时执行必要清理(大型的托管资源或者非托管资源需要析构器)

定义:先写一个~,再写类名

       

Eg. class FileProcessor

        {

            ~FileProcessor()

            {

                //省略内容

            }

      }

    注意:

  1. 析构器只适合引用类型
  2. 析构器不能有访问修饰符
  3. 析构器不能有参数

慎用析构器,以提升效率

析构器什么时候运行是无法精确预判的,所以不要让析构器相互依赖

14.2资源清理

    析构器只能等待垃圾回收器在某个不确定的时间来释放内存,因此需要资源清理来控制释放资源的时机

    使用using检查运行是否错误,且不管是否出错都执行Dispose()语句

    定义:using(TextReader reader=new StreamReader(filename))//异常安全的

            {

                //省略内容

            }

            //上述代码等价于

            TextReader reader = new StreamReader(filename);

            try

            {

                //省略内容

            }

            finally

            {

                if (reader != null)

                {

                    ((IDisposable)reader).Dispose();

                }

            }

    using语句声明的变量类型必须实现IDisposable接口,类中要具体化Dispose()方法

    在析构器中调用Dispose()方法

        保证Dispose()方法一定运行,多一层保障(可以看14.2.4示例代码)

        GC.SuppressFinalize(this);//告诉“运行时”不要调用this的析构器

属性

15.2 什么是属性

    属性是字段和方法的交集,既能够维持封装性,又能够使用字段风格的语法

    定义属性:

    Eg. class Class1

        {

             private int _x;

             public int X

             {

                 get { return this._x; }//get=>this._x;

                 set { this._x = value; }//set=>this._x=value;

             }

        }      

        value:隐藏的、内建的参数来传递要写入的数据

    使用属性:

       Class1.X=20

    只读:只有get    只写:只有set

    get,set可以设置访问性:private get=>……

    注意:

  1. 只能改变一个的可访问性
  2. 属性为private,访问器(get,set代码块)就不能public

15.3 属性的局限

    1.只有在结构或类初始化过后,才能用属性来赋值

    2.不能将属性作为ref,out参数传递给方法

Eg.MyMethod(ref location.X)//X为属性,编译出错

3.属性内只能包含get,set,不能有其他方法,字段等

    4.get,set不能获取参数,只能通过value传给set

    5.不能在属性内声明const属性

15.4接口中声明属性

   

Eg. interface IA

    {

        int X { get; set; }

}

要在类中实现属性

可以在类中实现属性时声明属性为virtual,允许派生类重写实现

通过属性就可以代替原来通过方法来取得值

15.5 自动生成属性

   C#编译器能够自动为属性生成代码,该技术适合用来创建不可变属性(即属性在对象构造好后就不更改)

    Eg. public int X { get; set; }

        //等价于以下代码

        private int _x;

        public int X

        {

            get { return this._x; }

            set { this._x = value; }

        }

    可以创建自动只读属性,但不能创建自动可写属性

    自动只读属性要么构造器初始化,要么声明时初始化

    构造器初始化:

    Eg. class Class1

    {

        public Class1()

        {

            X = 0;

        }

        public int X { get; }

}

声明时初始化:

Eg. class Class1

    {

        public int X { get; } = 0;

 }

15.6 属性初始化对象

   

    Eg. //Side1Length是Triangle类下的属性

        Triangle tri1 = new Triangle { Side1Length = 20 };//大括号内是对象初始化器

    使用构造器的情况:

    Eg. //Side1Length是Triangle类下的属性

        //Triangle类有一个参数为string的构造器

        Triangle tri1 = new Triangle ("Equal"){ Side1Length = 20 };

索引器

16.1 什么是索引器

    属性->智能字段       索引器->智能数组

    定义:

    Eg. class Class1

    {

        private int[] data;

        public int this[int i]//用this替代名称,[]内是下标类型

        {

            get => this.data[i];

            set => this.data[i] = value;

        }

}

注意:

  1. 每个类或结构只允许定义一个索引器,且总命令为this
  2. 索引器和数组的区别
    1. 数组只能整数下标
    2. 索引器可以重载【不是重写!!】(索引器参数类型,即[]内的参数类型必须不一样)

Eg. public int this[string i]

                     {

                         //省略内容

                     }

                     public string this[int i]

                     {

                         //省略内容

     }

    1. 索引器不能作为ref,out参数

数组作为属性和索引器的区别

    数组作为属性会直接修改该类/结构中数组属性的值,因此不管实例化几个值都相同

    索引器就不会产生这种问题,每一次修改都是修改实例化的对象内的数组值

16.2 接口中的索引器

    Eg. interface IA

    {

        int this [int i] { get;set; }//在类中具体实现

}

索引器加virtual->让派生类可以重写

Eg. public virtual int this[int i]

索引器的显式实现:

    Eg. class A:IA

    {

        private int a;

        int IA.this[int i]

//索引器的显示实现是非公共(不用加访问修饰符,默认private)和非虚的(不能重写)

        {

            get => this.a;

            set => this.a = value;

        }

 }

泛型

17.2 泛型解决方案

用于创建常规化的类和方法(可以适用于任意类)

   

类型参数:<T>

定义:

    Eg. class Queue<T>

    {

        private T[] data;

}

不同的T会生成不用的类

泛型类与常规类(object)的区别

object在使用时任何情况下都是同一类实例

使用泛型每次指定一个新的类型都会自动生成一个新的类

   

    泛型类的具体版本成为已构造类

    泛型的约束

确保泛型类使用的类型参数是提供了特定方法的类型

        利用接口约束:

        Eg. public class A<T> where T:IA//约束T必须要实现接口IA

       

17.3 创建泛型类

    类库:是已经编译好的多个类的集合,所有类型都存储在程序集(.dll文件)

    构造器名称不能包含<T>

    Eg. public class A<T>

        {

             int i;

             public A()//public A<T>()是错误的

             {

                 this.i = 0;

             }

        }

17.4 泛型方法

    Eg. public void MyMethod<T>(int i)

        {

            T a;

            //省略内容

        }

        //调用

        int i = 0;

        MyMethod<int>(i);

17.5 可变性和泛型接口

    协变性:泛型类型参数T可以从派生类隐式转换为基类,用<out T>标记,协变的接口的方法只能有返回值(结合例子理解)

    Eg. interface IRetrieveWrapper<T>

    {

        T GetData();

    }

    interface IStoreWrapper<T>

    {

        void SetData(T data);

    }

    class Wrapper<T> : IRetrieveWrapper<T>,IStoreWrapper<T>

    {

        private T Data;

        void IStoreWrapper<T>.SetData(T data)

        {

            this.Data = data;

        }

        T IRetrieveWrapper<T>.GetData()

        {

            return Data;

        }

    }

    //以上是类的定义,接下来的写在Main函数中

       Wrapper<string> wrapper = new Wrapper<string>();

         IStoreWrapper<string> storeWrapper = wrapper;

         storeWrapper.SetData("Hello");

         IRetrieveWrapper<string> retrieveWrapper = wrapper;

         Console.WriteLine($"{retrieveWrapper.GetData()}");

         //将类型参数转换为string的父类object

         IRetrieveWrapper<object> retrieveWrapper1 = wrapper;

//上述语句不合法,不能将子类接口对象赋给父类接口对象,编译器会报错,这种接口称为不变量

//将该接口修改为协变接口就能让子类接口对象赋给父类接口对象,接口的类型参数前加out

       //将接口定义修改为:interface IRetrieveWrapper<out T>

        IRetrieveWrapper<object> retrieveWrapper1 = wrapper;//合法了

    注意:

  1. 只有接口内的方法返回类型是类型参数才能使用out,如果类型参数作为接口内方法的传入参数,添加out就是非法的
  2. 协变性只适合引用类型,因为值类型没有继承
  3. 只有接口和委托类型才能使用out修饰符,泛型类不能使用out

逆变性:与协变性正好相反,泛型类型参数可以从基类隐式转换为派生类,用<in T>标记,逆变的接口的方法只能有传入的参数

Eg. public interface ICustomContravariant<in T>

    {

        void Get(T t);

    }

    public class CustomContravariant<T>:ICustomContravariant<T>

    {

        public void Get(T t)

        {

//省略实现内容

        }

    }

    //上面是类的定义,接下来的写在Main函数内

    ICustomContravariant<string> custom = new CustomContravariant<object>();//合法

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值