C#中一些易混淆概念
这几天一直在复习C#基础知识,过程中也发现了自己以前理解不清楚和混淆的概念。现在给大家分享出来我的笔记:
一,.NET平台的重要组成部分都是有哪些
1)FCL (所谓的.NET框架类库)
这些类是微软事先定义好的。
例如当我们新创建一个windows窗体应用程序是,VS会帮我们自动生成下面的代码:
using System;
using System.Collections.Generic;
using System.Text;
这些就是微软事先为程序员定义好的类库。程序员可以直接使用的。
2)CLR (所谓的公共语言运行时)
创建部署.NET程序的必备环境
使用C#,F#,VB等语言都可以来创建.NET应用程序。这时候就需要一个公共语言规范,来把不同的语言解释成.NET FramWork认识的东西。
二,什么是程序集
程序集主要有MSIL组成(所谓的微软中间语言,主要由dll文件组成)
不同编程语言程序被.NET FrameWork编译成程序集(dll文件),当程序需要被执行时,通过CLR中的JIT(及时编译器)编译成本地代码,并将指令发送给CPU执行。
程序集一般有两种:.dll和.exe文件(但是不是所有的dll和exe都叫程序集)
比如:我们在同一个解决方案下,建立多个应用程序或者类库文件。这些程序或者类库编译后就会变成不同的程序集。他们之间是相互独立的。之间如果想要相互访问,需要添加引用。
三,Parse转换和Convert转换的区别
1)Parse转换
①Parse转换只能转换字符串
②自变量是指定的数据类型才可以转换成功
下面的是.NET Reflector编译的源代码
2)Convert转换
①可以转换其他类型(如:类)
②与Parse的区别就是,转换前会对被转换的对象进行判断,如果对象为null则会转换失败
下面是实例源代码:
class Program
{
static void Main(string[] args)
{
string a = Console.ReadLine();
//Parse只可以转换字符串
int b = Int32.Parse(a);
//Convert可以转换类等对象
ParseNumber parNum = new ParseNumber();
//这种写法编译器会报错
//int b = Int32.Parse(parNum);
int c = Convert.ToInt32(parNum);
Console.WriteLine(b);
Console.WriteLine(b.GetType());
Console.ReadKey();
}
}
class ParseNumber
{
private int nunm;
public int Num { get; set; }
}
四,数据类型的存储位置
1)存储在栈中的数据类型
所有数值类型,char,bool,枚举,结构体
2)存储在堆中
string,数组,类
管这些类型,他们的变量的声明都是保存在栈里,真实的对象保存在堆里面,栈里面的变量存储打的是对象的地址。
下面以数组来简单说一下这个问题:
//声明一个一维数组
int[] arr = new int[4];
那么这个表达式的执行顺序是什么呢?
①首先程序会在栈中开辟一段名为arr的int类型的空间
②然后在堆中开辟连续的4块内存空间
③堆中开辟连续的4块内存空间返回类型为地址,即new int[4]表达式返回的是地址
示意图如下:
五,C#方法调用
1)在C#中我们可以给参数传递默认值,所以当我们调用这个方法的时候,可以不给这个参数传递值
static void Main(string[] args)
{
//声明一个一维数组
int[] arr = new int[4];
Program pro = new Program();
//直接调用,没有传递参数值
pro.para();
}
public void para(int i=5)
{
Console.WriteLine(i);
Console.ReadKey();
}
2)带默认参数的方法,默认值必须放在最右侧
下面的写法编译器会报错
3)方法的可变参数
①可变参数被Params
②Params只能用来修饰一维数组
static void Main(string[] args)
{
//声明一个一维数组
int[] arr = new int[4];
for (int i = 0; i < arr.Length; i++)
{
arr[i] = i;
}
Program pro = new Program();
pro.para();
//传递一位数组arr
pro.param(arr);
Console.ReadKey();
}
//params用来修饰一维数组
public void param(params int[] arr)
{
foreach (var item in arr)
{
Console.WriteLine(item);
}
}
③给可变参数赋值的时候可以直接传递数组元素
//声明一个一维数组
int[] arr = new int[4];
for (int i = 0; i < arr.Length; i++)
{
arr[i] = i;
}
Program pro = new Program();
pro.para();
//传递一位数组arr
pro.param(arr);
//直接传递数组元素,调用时会自动将这些数封装成数组,并将数组传递
pro.param(0, 1, 2, 3);
Console.ReadKey();
④与默认参数一样,可变参数的声明必须放在方法参数的最后
4)方法的out和ref参数
①out参数侧重于输出,必须在方法内对其赋值
如下图的声明编译器会报错
正确的使用方法
static void Main(string[] args)
{
//声明参数m
int m=0;
Program pro = new Program();
//传递参数m,必须带有out参数标识
pro.outPara( out m);
Console.WriteLine(m);
Console.ReadKey();
}
//out参数侧重于输出,必须在方法内对其赋值
public void outPara(out int i)
{
//方法内部必须对out参数进行赋值
i=5;
}
②ref参数侧重于修改,但是也可以不修改参数的值
static void Main(string[] args)
{
//声明参数m
int m=0;
Program pro = new Program();
//传递参数m,必须带有out参数标识
pro.outPara( out m);
//ref参数传递之前必须对其赋值,因为在方法内部可能会用到该参数
pro.refPara(ref m);
//Console.WriteLine(m);
Console.ReadKey();
}
//
public void refPara(ref int i)
{
Console.WriteLine("可以不对参数i进行任何操作!");
}
输出结果如下:
六,属性易混淆点辨别
①属性本身不存值,值是存在这个属性所封装的字段里面
class Study
{
private int nID;
//属性的值存储在封装的字段里面
public int NID
{
get { return nID; }
//这里我们给属性赋值
set { nID = value; }
}
}
通过访问属性字段获取字段的值
Study stu = new Study();
//通过访问属性字段获取字段的值
int nID = stu.NID;
②属性的返回值类型和字段的值类型没有关系
//属性的值类型为bool
private bool gender;
//字段的返回类型为string
public string Gender
{
get{return gender==true?"男":"女";}
set{gender =value=="男"?true:false;}
}
属性的返回值类型决定了get返回值的类型和set参数的类型
//属性的值类型为bool
private bool gender;
//字段的返回类型为string
public string Gender
{
//get的返回值类型为bool
get{return gender==true?"男":"女";}
//set参数类型为bool
set{gender =value=="男"?true:false;}
}
③自动属性到底是怎么回事?
看如下的代码:
private string strName;
//自动属性封装strName
public string StrName
{
get;
set;
}
这就是所谓的自动属性封装字段。在非自动属性中,程序默认的会有value值来给字段赋值,但是在自动属性中是怎么赋值的呢?
我们使用.NET Reflector反编译来看源代码:
这是我们封转的属性代码:
反编译set函数源代码:
我们可以看到.NET会默认为我们的程序生成一个成员变量<StrName>k__BackingField
get函数的源代码:
返回的也是该成员变量;
那么什么时候可以使用自动属性呢?
如果对一个字段取值和赋值的时候没有任何逻辑验证并且可读可写的时候,就可以使用自动属性。
七,C#类声明易混淆知识点
①首先给大家说明一个问题就是,文件名和类名必须是一样的么(就是我们在创建类的时候要命明,这个时候会默认的生成一样的类名称)?
如图所示
这个是必须的么?
我们尝试修改类名称为ChildName,然后访问类
可以看到我们要访问类,需要通过类名称访问而与文件名没有关系。
②类表达式的执行顺序和其意义
Study stu = new Study();
编译器执行代码的时候,
首先会先在栈中开辟一块类型为Study的内存空间放置变量stu
然后在堆中创建该变量的对象
然后调用该对象的构造函数,并且返回该对象在堆中的地址。