C#学习笔记(一):一些零散但重要的知识点汇总

时间:2022-03-17 20:01:03

集合类型

数组

数组需要注意的就是多维数组和数组的数组之间的区别,如下:

 using System;

 namespace Study
{
class Program
{
static void Main(string[] args)
{
//一维数组
int[] a1 = new int[];
int[] a2 = {, , , , }; //取最后一项
int i1 = a1[]; //二维数组
int[,] a3 = new int[, ];
int[,] a4 = {{, , }, {, , }}; //取最后一项
int i2 = a3[, ]; //数组的数组
int[][] a5 = new int[][];
int[][] a6 = {new int[] {, , }, new int[] {, }}; //取最后一项
int i3 = a6[][];
}
}
}

多维数组,每个子数组的长度必须一致;

数组的数组,每个子数组的长度可以不一致;

集合

  • ArrayList 可添加任意类型的列表,非泛型;
  • List 支持指定类型的列表,泛型;
  • HashTable 键和值都可以添加任意类型的哈希表,非泛型;
  • Dictionary 键和值都必须指定类型的哈希表,泛型;
  • ConcurrentDictionary 是线程安全的哈希表类,泛型;

类与结构体的区别

  • 定义类使用class,定义结构体使用struct。
  • 结构体不能对字段进行初始化,类可以。
  • 如果没有为类定义构造函数,则C#会自动定义一个无参的构造函数,如果定义了构造函数则不会自动定义无参的构造函数。而结构体无论是否定义构造函数都会自动添加一个无参的构造函数。
  • 结构体不能定义一个无参的构造函数,可以定义有参数的构造函数。类则无此限制。
  • 在结构体的有参构造函数中,必须为所有的字段进行赋值。
  • 创建结构体不使用new。
  • 结构体不能继承结构体或类但可以实现接口,类不能继承结构体。
  • 类是引用类型,结构体是值类型。
  • 结构体不能定义析构函数,类可以。
  • 结构体不能使用abstract和sealed关键字,类可以。

命名空间

命名空间不需要声明,直接namespace XXX就创建了一个名称为XXX的命名空间,也没有public,private之类的标识符。

namespace 关键字用于声明一个范围。此命名空间范围允许您组织代码并为您提供了创建全局唯一类型的方法。

无论您是否在 C# 源文件中显式声明了命名空间,编译器都会添加一个默认的命名空间。该未命名的命名空间(有时称为全局命名空间)存在于每一个文件中。全局命名空间中的任何标识符都可用于命名的命名空间中。

命名空间隐式具有公共访问权,并且这是不可修改的。有关可以分配给命名空间中的元素的访问修饰符的讨论,请参见访问修饰符(C# 参考)。

在两个或更多的声明中定义一个命名空间是可以的。

 using System;

 namespace Study
{
class Program
{
static void Main(string[] args)
{
//相同命名空间下的 Test
Test test1 = new Test();
test1.Func("hello namespace"); //全局下的 Test
global::Test test2 = new global::Test();
Console.WriteLine(test2.Func()); // Develop 命名空间下的 Test
Develop.Test test3 = new Develop.Test();
Console.WriteLine(test3.Func(, )); Console.Read();
}
} public class Test
{
public void Func(string info)
{
Console.WriteLine(info);
}
}
} namespace Develop
{
public class Test
{
public int Func(int a, int b)
{
return a + b;
}
}
} public class Test
{
public string Func()
{
return "I`m in default namespace!";
}
}

运行结果如下:

 hello namespace
I`m in default namespace!

global标识符

global表示根命名空间。

如果我们定义的类名在默认命名空间中或者类名和其它命名空间一致则可以使用global标识一下该类。

静态类

静态类与非静态类的重要区别在于静态类不能实例化,也就是说,不能使用 new 关键字创建静态类类型的变量。在声明一个类时使用static关键字,具有两个方面的意义:首先,它防止程序员写代码来实例化该静态类;其次,它防止在类的内部声明任何实例字段或方法。

静态类的声明方式如下:

 public static class MyClass
{
//静态构造函数
static MyClass()
{}
}

静态类有一下几个特点:

  1. 仅包含静态成员;
  2. 无法实例化;
  3. 是密封的;
  4. 不能包含实例构造函数;

静态类和私有构造函数的区别

程序员必须声明一个私有构造器。私有构造器禁止开发者在类的范围之外实例化类的实例。使用私有构造器的效果与使用静态类的效果非常相似。两者的区别在于,私有构造器方式仍然可以从类的内部对类进行实例化,而静态类禁止从任何地方实例化类,其中包括从类自身内部。静态类和使用私有构造器的另一个区别在于,在使用私有构造器的类中,是允许有实例成员的,而C# 2.0和更高版本的编译器不允许静态类有任何实例成员。使用静态类的优点在于,编译器能够执行检查以确保不致偶然地添加实例成员,编译器将保证不会创建此类的实例。静态类的另一个特征在于,C#编译器会自动把它标记为sealed。这个关键字将类指定为不可扩展;换言之,不能从它派生出其他类。

更多静态类的信息可以参考这篇博客:http://blog.sina.com.cn/s/blog_48a45b950100j68w.html

参数数组

我们在某些情况下希望一个方法可以支持传递个数不限的参数(不传或传递无限多个),这时可以使用params定义一个参数数组,我们具体看下例子:

 using System;

 namespace Study
{
class Program
{
private static void Main(string[] args)
{
UseParams(, , );
UseParams2(, 'a', "test");
int[] myarray = new int[] { , , };
UseParams(myarray); Console.Read();
} public static void UseParams(params int[] list)
{
for (int i = ; i < list.Length; i++)
{
Console.WriteLine(list[i]);
}
Console.WriteLine();
} public static void UseParams2(params object[] list)
{
for (int i = ; i < list.Length; i++)
{
Console.WriteLine(list[i]);
}
Console.WriteLine();
}
}
}

当前参数数组也可以和普通参数搭配使用,但必须是最后一个参数,如下:

 using System;

 namespace Study
{
class Program
{
private static void Main(string[] args)
{
UseParams(, , , 'a', "test");
int[] myarray = new int[] { , , };
UseParams(, , myarray); Console.Read();
} public static void UseParams(int a, int b, params object[] list)
{
for (int i = ; i < list.Length; i++)
{
Console.WriteLine(list[i]);
}
Console.WriteLine();
}
}
}

const和readonly

  1. readonly修饰符来表示只读域,const来表示不变常量;
  2. const只能在声明时初始化;
  3. readonly只能在声明初始化或构造器初始化时赋值,其他地方不能进行对只读域的赋值操作,否则编译器会报错;
  4. readonly可以使用static关键字描述为静态属性,const本身即为静态属性不用添加static关键字;
  5. const的类型只能为下列类型之一(或能够转换为下列类型的):sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string和enum;
  6. readonly的类型可以是C#语言的任何类型;
  7. readonly的类型如果是引用类型,则可以修改该引用类型的内部数据:
 using System;

 namespace Study
{
class Program
{
private static readonly Test test = new Test(); static void Main(string[] args)
{
test.a = ;
test.b = ; Console.WriteLine(test.a + " " + test.b); Console.Read();
}
} public class Test
{
public int a = ;
public int b = ;
}
}

结果如下:

  

类型转换

显示转换和隐式转换

 //隐式转换, 直接转换即可, 小范围数据转换大范围数据或
//子类转父类都可以使用, 这样可以安全转换
int i = ;
long l = i; //显示转换, 需要添加转换的类型, 大范围数据转换小范围数据或
//父类转子类时使用, 一般是会发生数据丢失或转换失败
double d = 11.5;
int i2 = (int)d;

is

用来判断是否是指定的类型。

as

显示转换,和(type)xxx不同的是,如果转换失败(type)xxx会进行报错,而as会返回空。

同时as只能用在引用类型和可空类型上。

方法转换

转换为字符串一般使用对象的toString()方法;

转换为int等类型可以使用Convert.ToInt32或Int32.Parse两种方法,其它类型类似;

装箱和拆箱

装箱:值类型转换为引用类型,堆栈变成记录地址,数据被存放到托管堆中,隐式转换;

拆箱:引用类型转换为值类型,数据从托管堆中放到堆栈,显示转换,可能报错;

异常处理

一般的代码在报错后就会停止运行,但写在finally中的代码在抛出异常后仍然会继续运行,所以一般finally中都会写上释放资源和内存的代码。

指定枚举的类型

默认情况下枚举的类型为int类型,但是我们也可以指定枚举类型,如下:

 enum Gender : byte
{
Female,
Male
}

静态构造函数

C#中还存在静态构造函数,即创建动态实例或者引用静态属性之前,都会调用静态构造函数。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace StudyTest
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(StaticConstructor.name);
Console.WriteLine(StaticConstructor.name);
Console.ReadKey();
}
} class StaticConstructor
{
private static string _name; static StaticConstructor()
{
Console.WriteLine("静态构造函数被调用了!");
_name = "StaticConstructorInstance";
} public static string name
{
get { return _name; }
}
}
}

输出如下:

 静态构造函数被调用了!
StaticConstructorInstance
StaticConstructorInstance

析构函数

当对象被GC回收时会调用析构函数,一般情况无需显示定义析构函数,但是当需要销毁非托管资源时可以显示定义析构函数进行处理。析构函数的显示定义如下:

 class Person
{
~Person()
{
Console.WriteLine("析构函数被调用了!");
}
}

使用析构函数时需要注意下面几点:

  • 不能再结构体中使用;
  • 一个类只能有一个析构函数;
  • 无法继承或重载析构函数;
  • 不能显示调用,析构函数由垃圾回收器自动调用;
  • 析构函数没有修饰符也没有参数;

索引器

当一个类需要使用类似数组的[]访问内部数据时可以使用索引器。索引器类似于属性,可使用get/set访问器,具体形式如下:

 [修饰符] 数据类型 this[索引类型 index]
{
get { //返回数据 }
set { //设置数据 }
}

下面我们看一个例子:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace StudyTest
{
class Program
{
static void Main(string[] args)
{
IndexTest it = new IndexTest();
it[] = ;
it[] = ;
it[] = ; Console.WriteLine(it[]);
Console.WriteLine(it[]);
Console.WriteLine(it[]);
Console.ReadKey();
}
} class IndexTest
{
private int[] arr = new int[]; public int this[int index]
{
get { return arr[index]; }
set { arr[index] = value; }
}
}
}

输入如下:


goto

goto用于运行代码的跳转,一般来说我们的代码都是严格的从上到下执行的,如果引入goto则会打乱这个运行顺序,造成比较难以发现的bug,那为啥C#还保留了这个功能呢,因为在某些情况下我们还是需要借助goto来方便的实现一些功能。

在Java中,我们会发现存在标签的概念,具体点击这里查看,而C#中与之对应的就是goto,比如我们在一个双重循环中希望可以直接退出整个循环,或多重循环中可以跳出到指定的某个循环,使用goto就会异常方便,示例如下:

 using System;

 namespace Study
{
class Program
{
static void Main(string[] args)
{
for (int i = ; i < ; i++)
{
Console.WriteLine("i: " + i); for (int j = ; j < ; j++)
{
Console.WriteLine("i: " + i + ", j: " + j); if (j == )
{
if (i == )
{
//跳到指定行
goto myBreak2;
}
else
{
//跳到指定行
goto myBreak;
}
}
} //跳出小循环
myBreak:;
} //跳出大循环
myBreak2:; Console.Read();
}
}
}

运行结果如下:

 i:
i: , j:
i: , j:
i: , j:
i: , j:
i: , j:
i: , j:
i:
i: , j:
i: , j:
i: , j:
i: , j:
i: , j:
i: , j:
i:
i: , j:
i: , j:
i: , j:
i: , j:
i: , j:
i: , j: