一、一切皆因“类型”

在学习C#之前,已经有了一定的python基础并自制了python入门手册。学C#后,发现C#python有较大的区别:很多设计、思路等不能理解,在不断的思索中最终找到了困扰我的最大因素:类型

python中,我们可以轻易的写出如下代码:

但是在C#中,我们需要写成这样

到此就可以引出我们的核心问题:类型

python属于动态类型语言,类似的有js、php等,而C#、Java等属于静态类型语言,二者有一个很大的区别在于:

声明一个对象/变量时需要明确的告诉编译器,这个对象/变量的类型

这一点看似很容易理解,但涉及的因素很多,要深入理解起来需要相当的思考,后续所有内容都围绕着“类型”来展开

二、类型的开始-多态

学习C#Java的朋友们一定听说过“多态”这个词,多态指的是同一种类型的不同表现状态,我们上代码:

代码里,我们先定义了一个 类型名叫brid,这个brid的类型里有一个shout方法

之后有两个子类:麻雀(maque)和喜鹊(xique)分别继承了brid,并重写了父类的shout方法

之后我们生成对象的时候,分别生成了xiaomaquexiaoxique,但是指向了其父类的类型brid

最终我们实现了同样是brid类型的xiaomaquexiaoxique,他们属于同一个类型(brid),但方法执行内容却不同。

这就是多态,同一种类型的不同状态可是其意义在哪里呢?

我们将上述代码中,Program里面的内容改成这样:

这段改动后的结果和改动前输出的内容是一样的,但这段代码里我们新增了一段内容:

当某些时候,函数需要传入一个对象,之后执行这个对象的某些方法时,同样需要确定传入对象的类型

在上述例子中,dosomething函数我们传入了一个 brid类型的对象b,执行了bshout方法,多态的存在让我们在传入同一个类型的不同对象时,函数的方法可以变化,这是多态的意义

但我们之所以强调多态,其根本在于:静态语言必须确定对象的类型!,多态多态,相同类型有不同的状态

我们可以感受下,如果是python这种动态类型语言

在代码中可以看出,def dosomething(b)时,动态语言的特性决定了不需要告诉IDE对象的类型,所以这里 的b可以是任意一个对象,所以说python天生支持多态,甚至说python都不存在“态”,使用起来比静态语言要灵活很多

但是动态语言的缺陷在于:

1、动态语言不需要告诉IDE对象的类型,所以IDE也无法获知你这个对象是什么类型的,所以在def dosomething(b)之后,输入b.时,IDE无法智能提示对象b的方法(b有无数个可能)

2、动态语言的执行效率低,动态的灵活性的背后是无数个静态类型的推测

3、有些问题编译时无法检查错误,运行时发现错误,一直有运行隐患

4、可阅读性差,动态语言没有类型,所以很容易混肴,如果加过多注释又和“简单”的出发点有些背道而驰

所以,静态语言更加严肃,更适合我们学习

当前,用接口的方式是作为多态的一种主流,此处不在讨论

二、类型的延续-委托

我们看一段python代码

在代码中我们可以看到,函数本身可以作为另一个函数的参数,也就是我们常说的回调函数

在动态语言中,因为不需要声明类型,所以可以写的很随意,但是在静态语言中,函数参数里必须声明类型,那么问题来了:

一个函数怎么变成类型呢?

C#里,我们采用的方法叫做委托,可以理解为把函数委托成一个类型。

我们看一段C#代码

在上面代码中,我们首先定义了一个委托类型,类型名为Mydelegate,因为委托委托,委托的是一个函数,所以需要有返回值和参数,在上例中,Mydelegate的返回值为float,参数为float x,也就是说,所有返回值为float,参数为float x的函数都可以委托到这个类型上

在使用过程中,有一个函数amazing,他的参数分别是Mydelegate类型的对象m以及float的对象x,并在函数体里执行委托成Mydelegate类型对象的函数,并将float x进行传递

在最后有一个power函数,这个函数符合Mydelegate的返回值及参数要求,所以可以将power通过委托的方式传递给amazing

简述:上面例子中,power函数被委托成了Mydelegate类型的对象m,注意:

a、m是一个对象(实例),其类型是Mydelegate,所以被 作为参数传递

b、此时m()=power()m=power,注意()

c、所有符合Mydelegate委托定义要求的函数均可以被委托进去

.net 提供了一个已经定义好的 action 委托,其无返回值,无参数,相当于

public delegate void action() 无需我们再定义,可以直接使用

上面代码中,定义test时,参数为Action a,这里的Action.net已经定义好的委托类型

委托的高级应用需要结合泛型,我们马上进入

三、类型的进阶-泛型

我们在看C#代码的时候,经常会碰到这么个符号 <>, 这个符号一定要攻克下来,彻底明白

因为C#等静态类型语言,需要声明类型,所以有时候我们还会面对一些问题,比如:

有一个容器,里面存放的是一种类型的对象并作简单处理,当对象很多时,我们需要相应的写很多个容器

泛型的出现,使我们定义好一种容器并贴上标签,需要放入什么样的类型对象时,注明标签即可

(注:多态是同一类型不同状态,泛型这里因存储对象的类型差距较大,通常只能做容器,方法里是对象的一些通用方法)

我们贴一段代码

上面的GenericClass就是一种容器,里面根据不同的类型定义了多种方法,每种方法都操作存放对象的相关信息

为了简化编码过程,泛型登场了

public void ShowInt<T> 这里,就声明了我后面要用一个T的类型,但是这个类型我还步确定,当使用时再指明类型

GenericClass().ShowInt<int>(2)

上面的方法为泛型方法,同理,还有泛型类

注意,泛型是定义时说明有一个T类型的对象我要使用,然后在调用函数时指明T的类型,比如

想想,如果是python等动态语言,因为不需要声明类型,所以:

public void ShowInt<T>(T t)python表示 def ShowInt(t) 就无需泛型这样特别处理了,当然,动态类型的缺点前面已经说过

注意:因泛型是可以传入完全不同的类型,所以泛型使用的内部,不能指定某个类型的自有方法(不同类型,无法判断)

所以泛型通常当容器等使用。

四、类型的高阶-泛型委托

泛型除了用作容器外,用作委托也是天造之和

我们前面已经介绍过了自定义委托以及.net自带委托,我们先从自定义委托说起

委托结合泛型:

上面代码中,我们先定义了一个泛型委托 Mydelegate,其返回值为void,其参数有一个,为T型的对象t

在使用时,我们指定了的类型为string,所以此时所有返回值为void,参数为string类型对象的函数皆可被委托进去

然后将Console.WriteLine这个函数(返回值为空,参数为string)委托进去,成功执行了m(str)

有了泛型委托,那么对于一个参数的函数来说,无论是int、float还是任意类型,无需再分别声明委托,定义一个泛型委托即可,在使用时指定类型,减少了代码量(定义委托)。

.net为了方便,提供了Action的泛型委托Action

为了满足泛型需要,泛型委托Action中可以多达16个参数,也有具备返回值多参数的泛型委托Func

所以,我们基本上不用自定义委托了,.net已经把各种可能定义好了,我们只需要使用时声明泛型的类型即可

泛型还有另一种用途是反射,通常会在泛型函数里,函数里执行了GetType等操作,在asp.net core等框架中被大量使用

五、类型的深入-类型是什么

很多初学者,包含我本人在内,对于类型的理解不够深入,通常我们写的类里面,包含几个数字及字符串,很少类中有其他类,这是因为我们的经验少,还需要不断的学习

此部分的思考从一个循环 嵌套开始:

开始思考:

1、类的字段里经常性包含另一个类型,且这个类型不是string、int等常见类型,而是我们自定义的类型

比如asp.net corehttpcontext里有request类型的对象,其对象里又有heder等类型的对象,很多层嵌套,一定要脱离只包含基本类型这种思维(封装)

2、脱离类型与实例之间限制在一种维度的局限性思想,非常重要!

如上面所例,以往些代码时,在一个类型里包含另一个类型时(本例中为Pet),通常会把字段命名为和类型相近的名称,从思路上理解就是:我有一个宠物类型的 对象叫 **宠物。

从真实世界理解,我们要跨域到另一个思路:我有一个宠物,他的类型是动物,这一点非常重要,仔细理解,完全跨越以前的思维

此种思维可能存在的问题是:people().pet我们下意识会认为是一个pet类型,但其实际是一个Dongwu类型,所以即便我们不能很好的使用,但我们一定要知道这种用法的广泛存在,具体是否应用这种方法灵活掌握。

另外,在IDE中输入people().pet.时,智能提示的方法是定义pet字段时其类型的方法(上例中为Dongwu),可不要误解为petPet类型,继承自Dongwu(此误解很少发生,此处纯属提示)

注:上面的people().pet中的people()表示一个people的对象,在C#中必须用new关键字,即 new people().pet 否则报错,后同

3、字段与类型的区别

我们知道,访问一个对象的属性时,用A.b这种方式,在A类的声明过程中,bA的一个字段,但是b可以定义为任何类型

但是A.b.方法里面包含的是已经定义好的b字段的类型的方法(上面代码中为anytype包含的方法)

(此内容第2条已写,此处再重复提示)

4、尝试按真实世界去命名对象!

我们在定义A.b字段时,b的类型已经确定(定义时需要指定类型),但是b这个名字应该怎么起?

尝试按真实世界的角度去命名b的名字,比如 Ren().chongwu ,这个对象名应该符合好记、好认,同时结合第2条

在涉及到一个抽象形容时,尤其适合,比如 Ren.Guanzhu(),一个人的关注,这个关注按照自然命名法,关注不适宜作为一个对象(暂时理解,后续验证)

按真实世界命名的好处是,在调用时智能提示好理解,有利于编码

5、类之间可以互相嵌套,比如前例:

嵌套之所以可行,是因为自定义类型为引用类型,可以理解为一个指针,一个类甚至可以包含自身类型的字段

之所以类可以包含自身的类型,可以理解为:在创建一个类的实例时,这个类的字段可以为空,所以不会无限循环创建。

当然如果上述代码变成 public people friend=new people(),编译器不报错,但是很快内存溢出。

上述代码为例,这个类对象的friend字段也可以是自己

这里和真实世界就很相像,一个人的朋友也可以包括自己。编程里可行的原因可以理解为friend这个字段是个指针指向了对象本身

如果往深里研究,类最终怎么编译成可执行程序,需要去学习编译原理/class结构,此处不叙。

6、对象的封装、嵌套

A().b.c.d 此种封装很常见,表示A类型的对象里有一个b的字段,这个字段是某种类型,其类型里定义了c的字段,这个字段又是另外一种类型,其定义里有一个d的字段

类还有很多用法,比如一个类的静态方法返回这个类的一个对象,继而执行其对象方法等,需要不断的学习

7、好玩的用法

一个类的静态成员也可以包含着运算,一个类的静态方法返回一个类的对象等等

一个类的某个方法返回这个类型的对象,这种方式很常见(静态方法返回实例,如Task.Run()返回一个Task类型的对象)

六、小结

之所以有以上这些总结,是因为个人写的、看的代码很少,所以作为一个小总结使用,成长需要不断的码代码,此文仅供参考

注:1、此文是针对静态语言而写,选择静态语言,我们需要理解关于类型的各种要素。

2、不要把面向对象和此文混肴,python等动态语言同样具备面行对象特性(之所以写这一条,是个人产生了混肴)

七、示例

1、模仿TASK

TASK有一个用法:

var task=Task<int>.Run(()=>{return 5;});

注:按各种教程,前置<>和后置return的类型需要相同,但在试验过程中发现后面即使return一个string 也不报错,所以此处理解暂时存在问题,为了教程,按照Task<T>和后面Run方法里的委托返回值同样是T来考虑,我们尝试其实现方式