C#中一些易混淆概念总结(二)--------构造函数,this关键字,部分类,枚举

简介:

目录:

【C#小知识】C#中一些易混淆概念总结

继上篇对一些C#概念问题进行细节的剖析以后,收获颇多。以前,读书的时候,一句话一掠而过,但是现在再去重读的时候,每句话发现都包含大量的信息。这一篇继续总结自己的学习笔记,给大家深度的剖析一些概念性问题,有助于大家对C#的理解。

--------------------------------------------------分割线---------------------------------------------

一,构造函数

我们先创建一个类,如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
class  Program
     {
         static  void  Main( string [] args)
         {
       
         }
     }
   //创建一个Person类
     class  Person
     {
     }


然后生成代码。

我们使用.NET Reflector反编译该程序集。会发现该类一被编译,CLR会自动的为该类创建一个默认的构造函数。如下图:

022201265475879.png

所以在创建该对象的时候,会默认的为该类生成一个无参数的空方法体的构造函数。如果我们不显式的写明构造函数,CLR会为我们调用默认的构造函数。

1
2
3
4
5
6
7
8
class  Person
     {
         //声明有实现的构造函数
         public  Person()
         {
             Console.WriteLine( "我是超人!" );
         }
     }


再次反编译该程序集,会发现添加的构造函数覆盖了C#编译器默认为该类生成的构造函数,如下图:

022210156412070.png

所以,当程序员手动添加了任意类型的构造函数,C#编译器就不会为该类添加默认的构造函数。

构造函数的特点:

①访问修饰符一般是Public②没有返回值,方法名与类名称一致;


二,This关键字的作用

①this关键字代表当前对象,当前运行在内存中的那一个对象。我们添加如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
private  int  nAge;
         public  int  NAge
         {
             get  return  nAge; }
             set  { nAge = value; }
         }
         //声明有实现的构造函数
         public  Person()
         {
             this .NAge = 100;
             Console.WriteLine( "我是超人!" );
         }


这时候我们反编译该程序集,会看到如下结果:

022232057819481.png

可以看到this关键字代替的就是当前的Person对象。

②this关键字后面跟“:”符号,可以调用其它的构造函数

我们再添加如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#region 对象的构造函数
         //声明有实现的构造函数
         public  Person()
         {
             this .NAge = 100;
             Console.WriteLine( "我是超人!" );
         }
         public  Person( int  nAge)
         {
             Console.WriteLine( "超人的年龄{0}" , nAge);
         }
      //使用this关键字调用了第二个一个参数的构造函数
         public  Person( int  nAge,  string  strName)
             this (1)
         {
             Console.WriteLine( "我是叫{0}的超人,年龄{1}" , strName, nAge);
         }
         #endregion


我们创建该对象看看是否调用成功。在Main函数中添加如下代码:

Person p = new Person(10,"强子");

我们运行代码,看到的打印结果如下:

022239107971538.png

由结果我们可以分析出,当含有两个默认参数的对象创建的时候应该先调用了一个参数的构造函数对对象进行初始化,然后有调用了含有两个参数的构造函数对对象进行初始化。

那么到底是不是这个样子呢?看下边的调试过程:

022245163753351.gif

通过上面的调试过程我们会发现,当构造函数使用this关键字调用其它的构造函数时,首先调用的是该调用的构造函数,在调用被调用的构造函数,先执行被调用的构造函数,在执行直接调用的构造函数。

为什么要这个顺序执行?因为我们默认的传值是10,我们需要打印的超人的年龄是“10”,如果先执行直接调用的构造函数,就会被被调用构造函数覆盖。

三,部分类

在同一命名空间下可以使用partial关键字声明相同名称的类(同一命名空间下默认不允许出现相同的类名称),叫做部分类或者伙伴类。

如下图,当在同一命名空间下声明相同名称的类,编译器报错:

022255518289949.png

当我们使用Partial关键字时,可以顺利编译通过,如下图:

022257594694254.png

分别添加如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
partial  class   Person
     {
         private  string  strAddress;
         public  string  StrAddress
         {
             get  return  strAddress; }
             set  { strAddress = value; }
         }
         private  string  strNumber;
         public  string  StrNumber
         {
             get  return  strNumber; }
             set  { strNumber = value; }
         }
         public  void  Run()
         {
         }
     }
                                                                                         
     partial  class   Person
     {
         #region 对象属性
         private  int  nAge;
         public  int  NAge
         {
             get  return  nAge; }
             set  { nAge = value; }
         }
         private  string  strName;
         public  string  StrName
         {
             get  return  strName; }
             set  { strName = value; }
         }
                                                                                             
         #endregion
         #region 对象的构造函数
         //声明有实现的构造函数
         public  Person()
         {
             this .NAge = 100;
             Console.WriteLine( "我是超人!" );
         }
         public  Person( int  nAge)
         {
             Console.WriteLine( "超人的年龄{0}" , nAge);
         }
         public  Person( int  nAge,  string  strName)
             this (1)
         {
             Console.WriteLine( "我是叫{0}的超人,年龄{1}" , strName, nAge);
         }
         #endregion
         public  void  Sing()
         {
         }
     }


我们再次反编译该程序集,会发现如下的结果:

022303433919284.png

我们会发现使用Partial关键字的两个同名类,被编译成了同一个类。

所以部分类的特点:

①必须在同一个命名空间下的使用Partial关键字的同名类

②部分类其实就是一个类,C#编译器会把它们编译成一个类

③在一个伙伴类中定义的变量可以在另一个伙伴类中访问(因为他们就是一个类)。


四,Const关键字和Readonly关键字的区别

1)const关键字

在Main函数中添加如下的代码:

conststring strName = "强子";          

Console.WriteLine("我的名字叫{0}",strName);

编译过后,我反编译该程序集发现如下结果:

022315237665949.png

发现定义的常量并没有出现在反编译的代码中,而且使用Const常量的地方被常量代替了。

2)readonly关键字

添加如下代码:

1
2
3
4
5
6
7
8
class  cat
     {
         readonly  string  reOnlyName =  "强子" ;
         public  cat()
         {
             Console.WriteLine(reOnlyName);
         }
     }


生成后反编译该程序集发现,如下结果:

022327589538902.png

我们发现被readonly修饰的变量并没有被赋值,这是什么回事呢?我们点击cat类的构造函数时,看到如下结果:

022330090166033.gif

我们发现被readonly修饰的变量是在被调用的时候赋值的。

那么被readonly修饰的变量的是就是不可变的么?当然不是,由反编译的结果我们知道,readonly修饰的变量是在被调用的时候在构造函数中被赋值的,那么我们可以在构造函数中修改readonly的默认值

添加如下代码

1
2
3
4
5
6
7
8
9
class  cat
     {
         readonly  string  reOnlyName =  "强子" ;
         public  cat()
         {
             this .reOnlyName =  "子强" ;
             Console.WriteLine(reOnlyName);
         }
     }


在Main()函数中添加如下的代码:

  cat ct = new cat();

运行结果如下:

022335354698833.png

说明我们成功在构造函数中修改了readonly变量的值。

readonly和const的区别:

const常量在声明的时候就必须赋初始值,这样声明变量可以提高程序的运行效率。而readonly变量声明时可以不赋初始值,但一定要早构造函数中赋初始值。

也就是说,const变量在编译的时候就要确定常量的值,而readonly是在运行的时候确定该变量的值的。


五,解析枚举

枚举的级别和类的级别一样,可以自定义数据类型,可以在枚举名称后使用“:”来指明枚举类型。看如下代码:

1
2
3
4
5
6
7
8
//定义一个方向的枚举类型,枚举成员使用","分割
     enum  Direction: string
     {
         east,
         west,
         south,
         north
     }


编译会报错,错误信息如下:

022350400948831.png

由此我们可以知道枚举的数据类型是值类型。

因为枚举是数据类型,所以可以直接声明访问,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class  Program
     {
         static  void  Main( string [] args)
         {
             //枚举是数据类型可以直接声明
             Direction dr = Direction.east;
             Console.WriteLine(dr);
             Console.ReadKey();
         }
     }
     //定义一个方向的枚举类型,枚举成员使用","分割
     enum  Direction
     {
         east,
         west,
         south,
         north
     }


也可以这样访问枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class  Program
     {
         static  void  Main( string [] args)
         {
             //枚举是数据类型可以直接声明
            // Direction dr = Direction.east;
             Person p= new  Person();
             //直接调用枚举变量
             p.dir = Direction.east;
             Console.WriteLine(p.dir);
             Console.ReadKey();
         }
     }
      class  Person
     {
         private  string  strName;
          //直接声明枚举变量
         public  Direction dir;
     }


每一个枚举成员都对应了一个整型的数值,这个数值默认从0开始递增,可以通过强制转换获取该枚举所代表的值。可以通过如下的代码访问:

Direction dr = Direction.east;          

int i = (int)dr;

我们还可以手动为每一个枚举成员赋值,代表的是整型数值,赋值后该枚举成员所代表的值就是所赋的值。如下代码:

1
2
3
4
5
6
7
enum  Direction
     {
         east=1,
         west=0,
         south=2,
         north=3
     }


将字符串转换成枚举

1
2
3
4
5
string  strDir =  "east" ;
            //将字符串转换成枚举类型
            Direction d1=(Direction)Enum.Parse( typeof (Direction),strDir);
            //转换的时候忽略大小写
            Direction d2 = (Direction)Enum.Parse( typeof (Direction), strDir, true );


--------------------------------分割线----------------------------------------

最后我们再来探究一个空指针异常的问题

首先我们先声明一个Dog类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class  Dog
     {
         private  int  nAge;
         public  int  NAge
         {
             get  return  nAge; }
             set  { nAge = value; }
         }
         private  string  strName;
         public  string  StrName
         {
             get  return  strName; }
             set  { strName = value; }
         }
     }


在Main()函数中我们这样调用

Dog d = null;            

d.StrName "旺旺";

结果会报错,如下图

030028173122939.png

我们已经为属性,封装字段了,但是为什么没有办法给字段赋值呢?我们就来探究一下这个问题。

当我们实例化Dog对象,即

Dog d = new Dog();

.NET Framwork做了什么工作呢?如下图:

030107255026694.png


那为什么会报错呢,原因如下图:

030115561271529.png



-----------------------------------------------分割线-----------------------------------------------------------------

这次分享到这里就结束了。其实蛮享受写这个过程的。因为在初次的学的时候理解了,如果再写成博客就又加深了印象,最后希望大家都能养成了良好的学习习惯。



     本文转自yisuowushinian 51CTO博客,原文链接:http://blog.51cto.com/yisuowushinian/1355783,如需转载请自行联系原作者




相关文章
|
27天前
|
开发框架 .NET C#
C#|.net core 基础 - 删除字符串最后一个字符的七大类N种实现方式
【10月更文挑战第9天】在 C#/.NET Core 中,有多种方法可以删除字符串的最后一个字符,包括使用 `Substring` 方法、`Remove` 方法、`ToCharArray` 与 `Array.Copy`、`StringBuilder`、正则表达式、循环遍历字符数组以及使用 LINQ 的 `SkipLast` 方法。
|
2月前
|
存储 C# 索引
C# 一分钟浅谈:数组与集合类的基本操作
【9月更文挑战第1天】本文详细介绍了C#中数组和集合类的基本操作,包括创建、访问、遍历及常见问题的解决方法。数组适用于固定长度的数据存储,而集合类如`List<T>`则提供了动态扩展的能力。文章通过示例代码展示了如何处理索引越界、数组长度不可变及集合容量不足等问题,并提供了解决方案。掌握这些基础知识可使程序更加高效和清晰。
73 2
|
1月前
|
C#
C# 枚举(Enum)
枚举是一组命名整型常量。枚举类型是使用 enum 关键字声明的。 C# 枚举是值类型。换句话说,枚举包含自己的值,且不能继承或传递继承。
39 9
|
25天前
|
Java 程序员 C#
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
9 0
|
2月前
|
存储 C# 开发者
枚举与结构体的应用:C#中的数据组织艺术
在C#编程中,枚举(`enum`)和结构体(`struct`)是非常重要的数据类型。枚举用于定义命名常量集合,提高代码可读性;结构体则封装相关数据字段,适合小型数据集。本文从基本概念入手,探讨它们的使用技巧、常见问题及解决方案,帮助开发者更好地利用这些特性构建健壮的应用程序。
36 8
|
2月前
|
C# 数据安全/隐私保护
C# 一分钟浅谈:类与对象的概念理解
【9月更文挑战第2天】本文从零开始详细介绍了C#中的类与对象概念。类作为一种自定义数据类型,定义了对象的属性和方法;对象则是类的实例,拥有独立的状态。通过具体代码示例,如定义 `Person` 类及其实例化过程,帮助读者更好地理解和应用这两个核心概念。此外,还总结了常见的问题及解决方法,为编写高质量的面向对象程序奠定基础。
24 2
|
3月前
|
开发框架 .NET C#
C#语言进阶(四) 枚举器和迭代器
C#语言进阶(四) 枚举器和迭代器
32 0
|
6月前
|
开发框架 前端开发 .NET
C#编程与Web开发
【4月更文挑战第21天】本文探讨了C#在Web开发中的应用,包括使用ASP.NET框架、MVC模式、Web API和Entity Framework。C#作为.NET框架的主要语言,结合这些工具,能创建动态、高效的Web应用。实际案例涉及企业级应用、电子商务和社交媒体平台。尽管面临竞争和挑战,但C#在Web开发领域的前景将持续拓展。
184 3
|
6月前
|
SQL 开发框架 安全
C#编程与多线程处理
【4月更文挑战第21天】探索C#多线程处理,提升程序性能与响应性。了解C#中的Thread、Task类及Async/Await关键字,掌握线程同步与安全,实践并发计算、网络服务及UI优化。跟随未来发展趋势,利用C#打造高效应用。
193 3
|
19天前
|
安全 C# 数据安全/隐私保护
实现C#编程文件夹加锁保护
【10月更文挑战第16天】本文介绍了两种用 C# 实现文件夹保护的方法:一是通过设置文件系统权限,阻止普通用户访问;二是使用加密技术,对文件夹中的文件进行加密,防止未授权访问。提供了示例代码和使用方法,适用于不同安全需求的场景。