C#高级编程 读书笔记


.NET Framework 是微软开发的软件环境,而C#是专门用于.NET Framework 的编程语言。
.NET 的公共语言运行库CLR可以将源代码编译成MS的中间语言代码IL,类似于Java字节码:平台无关性(并不完全)、提高性能、语言的互操作性。

.NET运行库对于垃圾回收采用的是垃圾回收器,所有动态请求的内存都分配到堆上,每隔一段时间,当.NET检测到需要清理内存时就调用垃圾回收器。垃圾回收的重要性质是不确定性,也就是说不能保证什么时候会调用垃圾回收器,这个时间由CLR决定。

.NET拥有异常机制来处理错误,也就是try{}catch{}finall{}代码块。

.NET支持特性的使用。

C# 常量(更容易读懂、修改,且更好避免出错)
1.常量必须在声明时初始化且不能用变量来初始化,初始化后不能改写
2.常量用const修饰,不允许再常量声明再包含修饰符static

C# 基本类型
1.值类型:直接存储值,存储再栈上
2.引用类型:存储的是对值得引用地址,通过该地址可以找到值,存储在堆上

Vector x,y;
x = new Vecot();
x.val = 30;
y = x;
Console.WriteLine(y.val);
y.val = 50;
Consolo.WriteLine(x.val);

在上面的程序中,Vector是引用类型,x指向了新建对象(new),然后x的val变成了30,y指向了x,因此第一个Console显示的是 30,然后y的val变成了50,所以第二个Console显示的是50。在这里内存只有一个对象就是new那个,而x指向了该对象,在y = x,y也指向了这个对象,因此x和y指向的是同一个对象,对x和y的操作都会直接影响那个对象。这就是引用类型的特点。而值类型的情况是这样的:声明了一个bool类型的变量,并且赋值false,将这个变量赋值给另一个bool变量,这样会使得内存中存在两个bool值,如果其中的一个值修改为true,那内存中有一个是true另一个是false。

在C#中short,int,long类型的长度并不受平台限制,总是16,32,64位带符号整数。float是32位单精度浮点数,double是64位双精度浮点数。decimal是128位高精度十进制数,如果要指定数字为decimal类型的需要在数字后面加上M。C#的char是16位的Unicode字符。C#支持两种预定义的引用类型object(基类)和string(Unicode字符串)

string类型
string是一种引用类型,他的对象仍然分配在堆上,但是与其他引用类型不完全一样
1.当把一个string变量赋值给另一个string变量的时候,内存中仍然是一个对象两个引用,但是修改其中一个变量的字符串的时候,会在堆上再分配新的string对象,这时内存中就有两个对象,各自有一个引用,修改各自变量的值不会互相影响。

if中的表达式必须等于bool值,不像C++那样可以用整数

C#的switch语句中如果激活了一个较前的case子句,后面的case子句就不会被激活,没有break的case将被编译器视为fault。例外的情况时,如果case子句为空,那就可以从这个case跳到下一个case。
C#的break语句可以用于跳出循环语句,然后执行循环语句后面一条语句。
C#的continue语句用于跳出当前循环迭代,开始执行下一次循环迭代。
C#的return语句用于退出类的方法。
十一
枚举
枚举是用户定义的整数类型,在声明时就要指定枚举值

public enum TimeOfDay
{
	Morning = 0,
	Afternoon = 1,
	Evening = 2
}
//调用TimeOfDay.Morning就会得到0。

十二
using语句
1.用于引入命名空间
2.给类和名称空间指定别名
3.类似try{}catch{}结构
十三
C#的预处理器指令
如 #region和#endregion用于给代码块指定名称
十四
C#的变量名不能是C#关键字且必须以字母或者下划线开头
十五
命名约定
1.推荐camel大小写形式:第一个单词首字母小写,后面单词首字母大写
2.类中私有成员字段名使用camel,并在字段前加一个下划线
3.参数使用camel
4.类名、方法名第一个单词首字母大写,其余单词首字母也大写
十六
结构和类的区别
主要区别是值类型和引用类型的区别
十七
在方法传参时,一般来说,引用类型通过引用传递,值类型通过值传递来传递参数。值传递时,方法获得的是该值的一个数据副本,在方法中对该数据副本作任何操作不会影响原数据;引用传递时,方法获得是这个引用,通过这个引用修改数据会修改原数据。
十八
非要将值类型数据使用引用传递的方式传给方法,比如在该值所占内存很大的时候,可以使用关键字ref,值传递变成引用传递可以避免复制该值减少内存消耗。使用方法是在函数定义的时候,在该参数的类型前面加上关键字ref,同时,在调用函数时,参数变量的前面也加上关键字ref。
另外,C#要求传递给任何方法的任何变量要初始化。
十九
out参数
有时候引用传递传进来的参数的初值没有意义,因为他可能被重写。
但C#又有参数初始化的要求,这时可以使用out关键字,这样做可以使该参数不用被初始化,并且该参数是通过引用传递的。使用方法是在定义函数的时候,在该参数的类型前面加上关键字out,同时,在调用函数的时候在参数变量前面也加上out。
二十
可选参数
在函数传参过程中,有时候有些参数不一定存在,因此可以定义为可选参数。可选参数的位置必须在非可选参数后面并且必须提供默认值。
二十一
方法重载
方法重载是在函数设计的时候函数可能有不同几个版本,实现方法是声明同名但是参数个数或者类型不同的方法。
二十一
一般情况下,C#不需要自己编写类的构造函数,因为编译器在编译时自己会创建默认构造函数。如果需要带参数的构造函数这时候就需要编写构造函数。
二十二
静态构造函数
静态构造函数的使用原因是,类的一些静态字段需要在第一次使用类之前,从外部数据源初始化这些静态字段。静态构造函数不允许private和public这样的访问修饰符修饰。静态构造函数与实例构造函数不同,即使是同参,两个构造函数也可以存在于同一个类定义中,因为静态构造函数在加载类的时候执行,实例构造函数在创建实例时执行。但是由于静态构造函数是在加载类的时候执行,因此当存在多个类都有静态构造函数,就不能确定到底哪个类的静态构造函数先行执行,因此编码不应该有静态构造函数相互依赖的情况。
二十三
只读字段readonly
只读字段readonly与const一样修饰常量,常量是值不能改变的变量。但是readonly更加灵活。readonly允许将字段设置为常量,这个常量来自于某些计算结果。
二十四
匿名类型var
匿名类型只是一个继承自Object且没有名称的类。这个类的定义从初始化器中推断。
二十五
部分类
多个人员开发的时候,类的定义可能会存在于多个文件中,这时候使用部分类可以使得在类定义分布在多个文件中。使用方法是在class前面加上关键字partial
二十六
静态类
静态类只能包含静态属性和静态方法,并不能创建静态类的实例。静态的意思是在程序加载的时候运行,而且只会运行一次。
需要详细介绍静态
二十七
Object类
Object类是.NET的所有类的基类。
二十八
实现继承和接口继承
在C#中,实现继承是一个类派生自一个基类,那子类就拥有基类的成员字段和函数。接口继承是类只是继承了函数的签名,没有继承任何实现代码。
二十九
多重继承
C#不支持多重继承,但是C#允许类继承自多个接口。也就是多重接口继承。在定义类的时候只要把多个接口基类用“ , ”号隔开,就可以实现多重接口继承。
三十
虚方法
把一个基类的函数声明为virtual,就可以在子类中重写该函数。重写的关键字是override。

class MyBaseClass
{
	public virtual string getString()
	{
		retrurn " This is a string ";
	}
}
class MyDerivedClass:MyBaseClass
{
	public override string getString()
	{
		return " This is still a string ";
	}
}

三十一
C#支持子类调用方法的基类版本,只需要在方法名前使用base关键字再 " . "出来。

class MyBaseClass
{
	public virtual decimal GetPrice()
	{
		return 0.00M;
	}
}
public MyDerivedClass:MyBaseClass
{
	public override decimal GerPrice()
	{
		return base.GetPrice()+0.9M;
	}
}

三十二
抽象类和抽象函数
C#允许把类和函数声明为abstract,这就声明了抽象类和抽象函数。抽象类不能实例化。抽象函数不能直接实现,必须在非抽象的派生类中重写。抽象函数本身也是虚拟的,但是已经有abstract关键字了不能再加上virtual关键字。只要类中声明了抽象函数,那类也必须声明为抽象的。
三十三
密封类和密封方法
C#允许将类和方法声明为sealed。对于类,这表示其他类不能继承该类;对于方法,这表示不能重写该方法。如果将方法声明为sealed,则必须在基类上把它声明为virtual或者abstract。
三十四
有继承关系的类的构造函数的执行顺序
当C#实例化一个类对象时,首先会执行它的构造函数。如果类A继承于类B,那实例化类A的时候,类A的构造函数会运行,类A继承于类B,那类A的构造函数会先运行类B的构造函数,类B的构造函数运行的时候,由于类B继承于System.Object,那类B会先运行System.Object的构造函数。这种构造函数的执行顺序总是从基类向下迭代。
三十五
含有基类的类的构造函数调用
一般情况下构造函数是这样定义的

public MyClass()
{

}

实际上构造函数默认按这个样子编译

public MyClass()
:base()//这一行代表在调用这个构造函数的时候,先调用基类的默认构造函数
{

}

按照规则,假如自定义了类的构造函数,那么编译器编译时就不会给类生成默认构造函数
假如有以下代码

public abstract class MyBaseClass()
{
	private string name;
	public MyBaseClass(string name)
	{
	}
}
public class MyGenericClass:MyBaseClass
{
	private string name;
	public MyGenericClass(string name)
	{
	}
}

上面代码会报错,因为在子类中,没有明写出来,调用的就是无参数的基类的构造函数,但是基类定义了有参的 构造函数,没有无参的构造函数,导致编译出错。
子类要调用基类的有参的构造函数方法如下:

public class MyGenericClass:MyBaseClass
{
	private string name;
	public MyGenericClass(string name)
	:base(name)
	{
	}
}

三十六
可见性修饰符
public:所有代码均可访问
protected:只有派生的类型能够访问
private:只能在所属类型中访问
internal:只能在包含它的程序集中访问
protected internal:只能在包含它的程序集和派生类中访问
类可以定义为public或者internal,但是不能定义为其它可见性修饰符,因为没有意义,但可以用来定义类的成员属性和成员方法。
三十七
接口
定义接口不能提供任何成员的实现方式,接口不能有构造函数,也不能被实例化,不能有字段,接口必须实现。接口成员的可见性修饰符总是public,且不用明写出来。
简单说明属性和字段的区别

public class Man()
{
	private int _age;//字段一般私有化,以下划线开头
	public int Age//属性一般公有化,大写字母开头,属性实际上是一个方法
	{
		get { return _age; }
		set { _age = value; }
	}
}

三十八
ArrayList类和List类
ArrayList类存储的是对象,Add()要求把一个对象作为参数。如果ArrayList的Add操作的是一个int类型的整数,这就会发生装箱操作,把一个int类型的值类型的值装箱成一个引用类型的对象。而在读取这个引用的值的时候,需要拆箱操作将对象转换为int类型,这时可以使用类型强制转换运算符。因为ArrayList在这种情况下要进行装箱拆箱,因此性能损失较大。
List类将泛型T定义为int就有List,泛型类不使用对象,而是使用时定义类型。对于List,在Add()一个int值时不需要进行装箱操作,因此在获取值的时候也不需要拆箱。
ArrayList类可以存储不同类型的对象,不是类型安全的;List泛型类在确定泛型T的类型时只能存储类型T的值或者对象,因此是类型安全的。
三十九
当只有一个泛型类的时候,用T作为泛型类的名称;当有多个泛型类,或者泛型类必须实现接口或者派生自基类,泛型类规定用字母T作为前缀,加上描述性名称。
四十
定义以下非泛型的链表类

public classs LinkedListNode
{
	public linkedListNode(object value)
	{
		this.Value = value;
	}
	public object Value { get; private set; }
	public LinkedListNode Next { get; internal set; }
	public LinkedListNode Prev { get; internal set; }
}//class LinkedListNode define END

public class LinkedList:IEnumerable
{
	public LinkedListNode First { get; private set; }
	public LinkedListNode Last { get; private set; }
	public LinkedListNode AddLast(object node)
	{
		var newNode = new LinkedListNode(node);
		if(First == null)
		{
			First = newNode;
			Last = First;
		} 
		else
		{
			LinkedListNode previous = Last;
			Last.Next = newNode;
			Last = newNode;
			Last.Prev = previous ; 
		}
		return newNode;
	}
	public IEnumerator GetEnumerator()
	{
		LinkedListNode current = First;
		while(current != null)
		{
			yield return current.Value;
			current = curren.Next;
		}
	}
}//class LinkedList define END

现在照着上面的代码,写一个链表类的泛型版本

public class LinkedListNode<T>
{
	public LinkedListNode(T value)
	{
		this.Value = value;
	}
	public T Value{ get; private set; }
	public LinkedListNode<T> Next { get; internal set; }
	public LinkedListNode<T> Prev { get; internal set; }
}//class LinkedListNode<T> define END

public class LinkedList<T>:IEnumerable<T>
{
	public LinkedListNode<T> First { get; private set; }
	public LinkedListNode<T> Last { get;private set; }
	public LinkedListNode<T> AddLast(T node)
	{
		var newNode = new LinkedListNode<T>(node);
		if(First == null)
		{	
			First = newNode;
			Last = First;
		}
		else
		{
			LinkedListNode<T> previous = Last;
			Last.Next = newNode;
			Last = newNode;
			Last.Prev = previous;
		}
		return newNode;
	}
	public IEnumerator<T> GetEnumerator()
	{	
		LinkedListNode<T> current = First;
		while(current != null)
		{
			yield return current.Value;
			current = current.Next;
		}
	}
	IEnumerator IEnumerable.GetEnumerator()
	{
		return GetEnumerator();
	}
}//class LinkedList<T> define END

四十一
泛型类初始化的时候为了避免值类型还是引用类型的问题,应该使用default关键字,default关键字可以把值类型初始化为0,引用类型初始化为null。例如

public T GetDocument()
{
	T doc = default(T);
	lock(this)
	{
		doc = docDocument.Dequeue();
	}
	return doc;
}
阅读更多
换一批

没有更多推荐了,返回首页