大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。
当前系列: C#语法 修改讲义

C#中进行类型转换,可以有这样的写法:

int.Parse("23");

Parse()是一个方法,但Parse()究竟是谁的方法?F12转到定义,结果我们就看到了:

    public readonly struct Int32 : IComparable, IComparable<int>, IConvertible, IEquatable<int>, IFormattable
    {
        public static int Parse(string s)

这什么意思?我们看到了Parse()是Int32的一个静态方法。而Int32又是什么?它是一个

结构

结构(struct)是一个和类(class)非常非常相似的封装容器。它的成员和语法,和类几乎一模一样。除了上面的静态方法调用,还可以:

//new一个结构实例,生成它的实例对象
int age = new Int32();
//调用结构的实例方法
age.CompareTo(21);

但是,结构

  1. 能够实现接口,但能继承其他结构和类,自然也不能被其他结构和类继承(这可能是结构较少使用的一大原因);
  2. 默认有一个无参构造函数,但不能显式声明。有参构造函数可以显式声明,声明的有参构造函数也不会“隐藏”默认的无参构造函数。但构造函数必须保证结构中的所有字段和自动属性被赋值。

我们可以自己创建一个结构,

#试一试#

    internal struct Bed   //源栈同学的床位
    {
        //public Bed(){}  //报错
        //报错:没有给_id赋值
        //public Bed(string room)
        //{
        //    Room = room;
        //}
        private int _id;
        internal string Room { get; set; }
        public bool HasBooked;
    }

值类型

结构是值类型。(class是引用类型)

所以结构类型的变量是不能被赋值为null(空地址)的:

Bed bed = null;  //报错的

此外,下面的代码可以帮助你理解值类型和引用类型的区别:(对比Bed分别为struct和class)

Bed bed /*= new Bed()*/;     //bed没有指向任何对象
bed.HasBooked = true;        //但bed.HasBooked可以直接被赋值,为什么?
如果Bed不是struct而是class的话,这样的代码是会报错(编译时或运行时)的。因为引用类型变量bed,只能存放对象地址,无法存放HasBooked的值。

但当dream是struct的时候,dream本身是有“结构”的,存放HasBooked的位置是预留着的,所以可以赋值。

选择使用

虽然因为结构是值类型,直接存放在栈中,可以快速读取;但它的数据量不能太大,否则就会:

  • 耗用栈空间
  • 在传递时耗费更多性能

引用类型的优劣正好相反:节约栈空间;只赋值地址不copy内容,传递时更快……

但我们一般不使用struct,因为:

  • (数据量不大的)常用的结构都已经由C#定义为内置(build-in)类型又被称之为简单类型)。int其实是Int32的别名,double是Double的别名,bool是Boolean的……所有“可简写”的内置类型,除了string和object,其他全部都是值类型。(详见:内置类型表 - C# 参考
  • 结构不能继承,所以诸多面向对象的特性它都不能使用,不够面向对象。


时间

复习:时间漫谈:时区 / 日历 / 计算机简单粗暴处理  

DateTime

包括日期(年月日)和时间(小时分钟秒等)。.NET为我们提供了大量关于日期的属性和方法,以方便我们的开发。

演示F12查看metadata:……

常用属性方法

1)构造函数:通过new DateTime()构造一个时间对象,可以指定年月日小时分钟秒等:

//2019年12月1日
DateTime date = new DateTime(2019, 12, 1);
//2019年12月1日 19点52分24秒
DateTime dateTime = new DateTime(2019, 12, 1, 19, 52, 24);

2)静态属性:DateTime.Now,获取当前时间

Console.WriteLine(DateTime.Now);
3)实例属性:根据时间变量取得它的年月日(Day/Year/Month)等,通常和DateTime.Now搭配使用:
Console.WriteLine(DateTime.Now.Day);  //当月的第几日

4)实例方法:在现有时间基础上增减,比如AddDays()/AddMonths()/AddYears()……

5)ToString()方法:可以用于指定日期显示的格式。通常,我们使用字符串指定。用y代表year,用M(注意大写)代表month,用d代表day等,如下所示:

Console.WriteLine(DateTime.Now.ToString(
                "yyyy年MM月dd日 hh点mm分ss秒"));
            //显示:2019年12月01日 07点59分14秒

另外,注意DateTime中已经出现了:

TimeSpan

它也是一个struct,表示的是一段时间。注意:

  • Timespan计量的最大单位是天(Days),没有年和月
  • 用Days/Hours/Minutes等取出的是Timespan中天/小时/分钟等部分的值
  • 用TotalDays/TotalHours/TotalMinutes等取出的才是Timespan代表的时间间隔换算成天/小时/分钟等的值
TimeSpan span = new TimeSpan(2, 4, 12, 30, 0);
Console.WriteLine(span.Hours);
Console.WriteLine(span.TotalHours);


运算符重载

阅读源代码的时候,我们还发现了这样的代码:

public static DateTime operator +(DateTime d, TimeSpan t);

这被称之为运算符重载。实际上就是改变了运算符加号(+)的运算逻辑:本来加号是只能用于数值型类型的,DateTime显然不是数值,本是不应该能够使用加号的,但重载之后:

Console.WriteLine(DateTime.Now + new TimeSpan(5000)); 

这就是因为在DateTime类中,进行了上述运算符重载。

任何一个类都可以进行运算符重载,比如我们的Student类:

public static Student operator +(Student student, int age)
{
    student.Age += age;
    return student;
}
然后,才可以将Student对象和+并用:
Student student = new Student { Age = 17};
student = student + 1;
Console.WriteLine(student.Age);

其语法要求是:

  • 方法只能是public和static的
  • 必须要有一个关键字operator
  • 至少一个参数和返回值类型相匹配
  • 某些运算符有要求“成套”,比如重载了==,就必须重载 !=

PS:不是所有运算符都可以重载。具体可查看:可以重载的运算符

运算符重载是一种非常强大的语法,在.NET基本类库中大量的使用!极大的方便了开发人员,优雅了代码!

所以,DateTime和TimeSpan,可以使用:

  • 大于/小于/等于/不等于:
  • 减号:TimeSpan也通常由两个日期相减获得


类型转换重载

和运算符重载非常类似,但额外的引入了隐式(implicit)和显式(explicit)两个关键字:

public static implicit /*explicit*/ operator Student(Bed bed)
{
    return new Student { Bed = bed };
}

类型之间的转换,本来只能发生在“相关”(比如数值、继承关系)类型之间。如果没有类型转换重载,这样的代码是不行的:

Student student = /*(Student)*/new Bed();

类型转换重载也主要应用于.NET的基础类库中,比如:

public static implicit operator ReadOnlySpan<char>(string? value)


Object

复习:J&C:Object / hash值 / equals() / toString() / 装箱拆箱 

object是Object的别名/简写。

值/引用类型

简单解释一下:

  • 所有类型都继承自Object
  • 但被分为值类型(Value Type)和引用类型(Reference Type)
  • 所有class定义的都是引用类型(比如:数组Array、String),其他struct/enum定义的都是值类型

Equals()

会判断两个对象是不是值类型:(演示:网站查看源代码

public /*class*/ struct Bed   //源栈同学的床位
{
    public int Id;

如果是值类型,比较两个对象的值内容(所有字段)相同为true,否则为false

Bed dream = new Bed();
dream.Id = 10;
Bed sleep = new Bed();
sleep.Id = 10;

Console.WriteLine(Object.Equals(dream, sleep));

演示:当Bed为class时的结果

关于==

struct定义的值类型,默认是不支持使用==的。能使用的(比如DateTime),都是因为进行了==的运算符重载!或者本身是数值类型(比如int)或enum。

演示报错:

Console.WriteLine(dream == sleep);
public static bool operator ==(Bed bed1, Bed bed2)
{
    return bed1.Id == bed2.Id;
PS:重载==之后会要求(警告)override Equals()

== 作用于引用类型,同Equals()方法,就比较是否指向同一个对象(除非进行了运算符重载,比如string


字符串

复习:J&C:字符串:值类型?/ 池 / 常用方法 / StringBuilder / 正则 

string是String的别名/简写。

String中重写了运算符==,所以不需要(像Java一样)使用Equals()方法

Console.WriteLine(name == "阿泰");

演示(复习)常用属性方法

  • 可以通过索引器直接通过下标获取字符,不需要先通过ToCharArray()转换:
    Console.WriteLine(welcome[0]);
  • 引入了string.Empty代替""(#体会细节#)
    if (name == string.Empty)
  • 通过静态方法判断null、empty和white
    string.IsNullOrEmpty(welcome); 
    string.IsNullOrWhiteSpace(name); 
  • 方法参数重载,可以接受字符char
    Console.WriteLine(name.Contains('阿'));
  • 字符串格式定义上略有差别
    double pai = 3.1415;
    Console.WriteLine(string.Format("圆周率为:{0:0.00}……", pai));
    但现在更多的是使用内插,上述格式同样适用:
    Console.WriteLine($"一起帮·源栈{pai:0.00}");


动态类型(dynamic)

说明:学习这个类型,完全是为了面试需要。^_^

很多时候,dynamic完全和object完全一致,任何类型变量都可以使用dynamic声明。使用dynamic标记的变量可以绕过C#的编译时(注意:仅仅是编译时)的类型检查。

            object o = "986";
            Console.WriteLine(o - 88);       //编译时错误

            dynamic d = "986";
            Console.WriteLine(d - 98);       //绕过了类型检查

但是,dynamic并不意味这变量类型可变,当我们运行d-98时会报出因类型不匹配造成的错误:

#常见面试题:var、object和dynamic的区别#


作业

  1. 见:编程语言,时间漫谈
  2. 见:J&C:Object / hash值 / equals() / toString() / 装箱拆箱 
  3. https://source.dot.net/中找到 Console.WriteLine(new Student()); 输出Student类名的源代码
学习笔记
源栈学历
大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 C#语法 中继续学习:

下一课: C#:异常

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码