zoukankan      html  css  js  c++  java
  • 解读经典《C#高级编程》第七版 Page68-79.对象和类型.Chapter3

    前言

    新年好,本篇开始进入第三章,《对象和类型》,深刻理解C#的对象,对于使用好.Net类库非常重要。

    01

    类和结构

    从使用角度看,结构和类的区别很小,比如,将结构定义转换为类,只需要将关键字struct改为class即可。创建结构的时候,也同样可以用关键字new。它们的本质区别是,结构是值类型,存储在栈上,而类存储在堆上。

    但我还没碰到什么情况下需要使用结构。因为一般的业务代码,在设计时很难提炼出足够可泛化的含义,而如果只是少数情况下采用结构,对性能的提升微乎其微。但我们也可以发现在.Net框架下,还是比较多的使用了结构,这估计是因为:

    1. .Net作为千万程序员都在使用的框架和类库,性能上精益求精就有必要了
    2. 其次,我们发现.Net使用结构是成体系的,比如Point,Size, Rectangle等,大量的“形状”都采用了结构,这从对象化的设计角度看就非常有意义了

    02

    类的数据和函数称为类的成员。清晰的理解类的“数据成员”和“函数成员”,对于用好反射功能非常重要。因为反射针对不同的成员类型有不同的功能实现。

    数据成员

    数据成员是包含类的数据——字段、常量和事件的成员。特别注意的是,“事件”是数据成员。为什么呢?这后面会讲到,事件是一种特殊的委托,而委托实际上是类,所以事件的实例当然是数据。

    函数成员

    函数成员提供了操作类中数据的某些功能,包括方法、属性、构造函数和终结器(finalizer)、运算符以及索引器。方法、属性、构造函数大家经常碰到,会比较好理解。而对终结器、运算符、索引器来说,它们都是“特殊的方法”。

    属性:函数组,即get函数和set函数的组合。

    终结器:类似于构造函数,如果构造函数名为“FuncA”,终结器则名为“~FuncA”,类似Java的析构函数finalize()。

    运算符:类的运算符可以重载,实现自定义功能,那么运算符归类为函数,也就好理解了。

    索引器:将对象以集合的方式访问。

    方法 / ref / out

    方法的参数传递值得探讨。函数的参数传递有“值传递”和“引用传递”两种方式。在C#中,除非明确带关键字,所有的参数都通过值来传递。这让我们会产生一个疑问,因为在实际编程中,往往会使用引用类型作为参数,而又没有使用ref关键字时,此时传递的到底是值呢还是引用呢?答案是:传递的还是值。值传递时,仍然进行了值的复制,但复制的是“引用”,而不是对象本身。所以,我们也可以推论说:对于传递引用类型的参数,加不加ref,效果都同加ref是一样的。

    和ref对应,还有个out关键字。使用out关键字定义的参数,out会强制要求函数中将变量初始化或赋值后再输出。实际上,使用或者不使用参数关键字,对应了实际使用方法中的三种常见场景:

    1. 不带关键字:传入参数,不传出
    2. ref关键字:传入参数,再传出
    3. out关键字:不接受参数传入,但传出参数。out和ref一样,传递的是引用。

    命名参数

    使用命名参数的作用是,书写调用时,参数的书写顺序不再受限制。比如:定义时为:

    public void Test(int arg1, int arg2)

    调用时可以为:

    user.Test(arg2: 1, arg1: 2);

    不过我没有用过命名参数,因为实在找不到应用它的场景。但我回想起在之前公司的基于Repository的架构下,倒是能为它找到用武之地。为什么呢?因为在Repository架构下,为遵循架构规则,需要为每一个表建立对外的业务访问接口,而复杂的业务需求又导致接口需要大量参数的方法重载,比如一个方法可能有超过10个参数定义。这时候,在调用方法的时候,万一有一两个参数写错了顺序,就会导致程序的bug。为了避免这种问题,使用命名参数,明确的指定 参数名:值,相当于程序员在写代码时就进行了明确的参数名检查,从而避免bug。但这种问题,是由于不合理的Repository架构导致的,当废弃了Repository架构后,这种应用场景不复存在。

    可选参数

    可选参数如:public void Test(int arg1, int arg2 = 10)

    可选参数必须是方法定义的最后一个参数。

    方法重载

    书中说到“如果不能使用可选参数,就可以使用方法重载”。这说明了语言设计的倾向性。即原则上应该多使用可选参数,而避免过多的使用重载,毕竟方法重载的“代码复用度”一般不如可选参数。

    何为“方法的重载”?方法的重载,即一个方法的几个版本有不同的签名(即,方法名相同,但参数的个数和/或类型不同,注:不包括返回值)。方法重载的参数限制:

    1. 两个方法不能仅在返回类型上有区别。
    2. 两个方法不能仅根据参数是声明为ref还是out来区分。

    属性

    属性(Property)的概念是:它是一个方法或一对方法,在客户端代码看来,它(们)是一个字段。这个定义非常有意思。所谓“客户端代码”,就是调用属性的代码。在调用者看来,属性和字段并无区别。区别在于内部,属性可以对其get/set访问器进行代码定制,从而实现更复杂的需求。

    因为属性是特殊的方法,那么要实现一个功能时,是应该定义为属性还是定义为方法,这在12月25日的文章中有深入分析。

    在有不少老的代码里,属性是这么定义的:

            string column1;

            public string Column1

            {

                get

                {

                    return column1;

                }

                set

                {

                    column1 = value;

                }

            }

    这是因为在.Net2.0时,对属性的定义还没那么方便。虽然它已经比Java的get,set方法好多了,但还是比较繁琐。在更新版本的.Net下,已经可以使用这种方式定义属性:

    public int Column1 { get; set; }

    这叫做“自动实现的属性”。实际上它只是一个语法糖,本质并没有变。

    get,set访问器可以带上修饰符。如果没有带上修饰符,那么它使用的是属性的修饰符。比如可以:

            public int Column1 { get; private set; }

            public int Column2 { get; }

            public int Column3 { private get; set; }    //只写属性,不应该出现

    内联

    因为属性本质是方法,我们也看到实际使用中,大多数属性都是采用“自动实现属性”的,那么为了实现属性的偶尔才使用的“访问限制”功能,而将“字段访问”改为“方法”访问,会增加方法访问的额外开销吧?值得吗?

    我们不用担心这个问题。因为.Net编译器已经将属性访问代码编译为“内联代码”而不是“函数调用”代码。

    类是对象化编程的基本概念,内容非常多。下一篇将继续讲讲:构造函数、只读字段、匿名类型、结构详解、部分类、静态类、Object类、扩展方法,等。

    觉得文章有意义的话,请动动手指,分享给朋友一起来共同学习进步。

    欢迎关注本人微信公众号,更及时的关注最新文章(每周三篇原创文章,以及多篇专题文章):

    附文:

    c# 方法参数(传值,传引用,ref,out,params,可选参数,命名参数)

    C#中的字段与属性的区别及属性的作用

    上一篇:解读经典《C#高级编程》第七版 Page50-68.核心C#.Chapter2

  • 相关阅读:
    深入探讨多态性及其在Java中的好处
    可扩展的Java线程池执行器
    Java并发:线程限制
    CF集萃3
    CF1151F
    [欧拉路]CF1152E Neko and Flashback
    LOJ#3119 随机立方体
    UOJ#449 喂鸽子
    CF1140F
    洛谷P5071 此时此刻的光辉
  • 原文地址:https://www.cnblogs.com/holyknight17/p/10251383.html
Copyright © 2011-2022 走看看