C#语法最基础知识
一. System.Object
公共实例方法 |
简要说明 |
virtual bool Equals(object obj) | 确定两个对象是否相等,如果相等则返回true,否则false |
virtual int GetHashCode() | 返回this对象的一个哈希码,如果该对象被一个集合当作键值使用,则这个值将起作用 |
Type GetType() | 返回当前对象的类型。类型由一个继承自 System.Type的实例对象表示 |
virtual string ToString() | 返回一个可以代表当前对象的字符串,在 System.Object中该方法返回当前对象类型的完整名称 |
实例 Equals 方法
比较当前对象和参数对象是否相等。
在 System.Object中,这个方法是“引用比较”比较this引用和参数引用是否引用同一个对象,是则true否则false
假设两个字符串变量a,b分别赋相同值,逻辑相同但此时按照Equals()规则则不同。故设计新类时可重写此方法
实现内容相同。.NET中,System.ValueType作为所有值类型的基类,重写此方法以实现内容的比较。
实例 GetHashCode 方法
用来返回当前对象的一个哈希码,当对象被用作集合的键值时,此方法被调用。
3种方法检验GetHashCode的算法正确性
1. 如果两个对象相等(Equals),则两个对象调用GetHashCode方法应该返回相同值
2. 同一个对象无论在何种 场合调用GetHashCode方法,返回值应该返回想同值
3. 不同对象调用GetHashCode方法,返回值应该均匀分布在整数集合之中
GetType 方法
公共方法:GetType。 返回一个代表当前“对象类型”的实例。
即查找程序集中的元数据,找到该类型的定义。 System.Object的ToString方法利用了 GetType方法
公共静态方法 |
简要说明 |
bool Equals(object objA, object objB) | 确定两个对象是否相等,如果相等则返回true,否则false |
bool ReferenceEquals(object objA, object objB) | 比较两个对象的引用是否相等。相等则返回true,否则false |
ReferenceEquals方法
实现两个对象的引用比较,如果引用相等返回true,否则false。无论是何种值类型,返回结果必为false。
Equals方法
实现较简单,依靠实例的Equals方法实现内容比较
public static bool Equals(object left, object right) { //查看是否是同一对象 if (left == right) return true; if ((left == null) || (right == null)) return false; return left.Equals(right); }
受保护实例方法 |
简要说明 |
object MemberwiseClone() | 浅复制当前对象实例,并返回复制对象的引用 |
Finalize | .NET的析构方法 |
二. 值类型与引用类型
(1)值类型
继承自 System.ValueType
常有的值类型包括 结构、枚举、整数型、浮点数、布尔型等
(2)引用类型
以class关键字定义的类型都是引用类型
(3)区别
1).赋值时区别
值类型的变量将直接获得一个真实的数据副本
引用类型的赋值仅仅是吧对象的引用赋给变量,可导致多个变量引用一个实际对象实例上
2).内存分配的区别
值类型的对象会在堆栈上分配内存,
引用类型的对象会在堆上分配内存。
3).继承结构的区别
值类型都继承自 System.ValueType 对象分配在 堆栈 上
System.Object和所有引用类型对象分配在 堆 上
三.装箱和拆箱
装箱
System.Object是所有值类型的基类,所有值类型必然可以隐式转换成 System.Object类型。转换发生时,CLR需要做额外的工作把堆栈上的值类型移到堆上。 这个操作为 装箱。
步骤
1. 在堆上分配一个内存空间,大小等于需要装箱的值类型对象大小加上两个引用类型对象都拥有的成员:类型对象
指针和同步块引用
2. 把堆栈上的值类型对象复制到堆上新分配的对象
3. 返回一个指向堆上新对象的引用,并且存储到堆栈上被装箱的那个值类型的对象里
这些步骤不需程序员自己编写,在出现装箱的地方,编译器会自动加上执行以上功能的中间代码。
拆箱
装箱的反操作,把堆中的对象复制到堆栈中,并且返回其值
过程中,拆箱操作将 判断拆箱的对象类型 和 将要被复制的值类型引用 是否一致,不一致将抛出一个 InvalidCastException的异常
影响: 装箱与拆箱意味着堆和堆栈空间的一系列操作,这些操作对性能代价很大,尤其是在速度相对于堆栈慢很多的堆操作,并且这些操作可能引发垃圾回收。
适用场合:
1. 值类型格式化输出 (装箱)
以下代码可编译执行,但会引发一次不必要的装箱操作
int i = 10; Console.WriteLine("i的值是:"+i);
优化后,不在涉及装箱
int i = 10; //ToString方法得到一个字符串对象,字符串是引用类型 Console.WriteLine("i的值是:o{0}"+i.ToString());
2.System.Object类型的容器
常用的容器类如 ArrayList就是System.Object容器,任何值类型被放入ArrayList的对象中,都会引发一次装箱操作,相应的取出引发一次拆箱操作。
因此,在可以确定类型的情况下应该使用泛型技术而避免使用System.Object类型容器。
三. C#中是否全局变量
C#中没有传统意义的全局变量,在C#程序中,任何对象数据都必须属于某个类型。
通过公共静态变量,可以实现以前全局变量的所有功能。如asp.net程序中使用Application对象和使用配置文件存储可配置数据等。
四. 结构 struct
可看作 一个微型的类。 支持 私有成员变量和公共成员变量
支持属性和方法
支持构造函数 但 不能自定义无参的构造方法,C#编译器会为结构添加一个无参公共构造
函数 在该方法中成员被自动化为0。也不能在定义时设置初始值
支持重写定义在System.Object中的虚方法
不支持继承
继承自 System.ValueType 类,故struct是值类型。
经常用于存储数据的集合,但那些涉及复杂操作的类型,应被设计成类而不是结构。
五. 类型初始化器
1.概念
在.NET中,每个类型都有一个初始化器。可以自己定义初始化器,也可以使用默认的编译器添加的初始化器
类型的初始化器拥有和类型相同的名字 以 static 关键字定义,并且没有参数,也没有任何返回值。
类型初始化器不同于构造函数,初始化器不能被显式地调用,并且每个类型只能有一个类型初始化器。 CLR保证
的任何静态成员被使用之前,类型的初始化器会被调用。这个概念和静态成员变量的初始化表达式本质上没区别。
public class CCtor { //初始化表达式 public static int m_Int = 1; //初始化器 static CCtor() { m_Int = 2; } //构造函数 public CCtor() { Console.WriteLine("构造函数调用。"); } static void Main(string[] args) { Console.Read(); } }
C# 编译器在编译时,会把所有静态成员变量初始化表达式加入到初始化器中,并且添加的位置在自定义的初始化器内容之前。
2.调用策略
在中间代码中初始化器的名字为cctor()。 两种调用策略
1). 在类型不具有BeforeFieldInit元数据属性时,CLR将在类型的任何成员被使用之前调用初始化器。
2). 在当类型具有BeforeFieldInit元数据属性时,CLR可以自由调度初始化器的调用,只需确保任何静态成员变量在使用之前初始化器已经被调用。
六. 方法参数的的传递方式
关键字 |
简要说明 |
Ref | 引用传递参数,需要在传递前初始化 |
Out | 引用传递参数,需要在返回前初始化 |
Params | 可以指定在参数数目可变处采用参数的方法参数 |
1). ref和out关键字
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Test { class useles { static void Main(string[] args) { int i = 0; Console.WriteLine("原始值:" + i.ToString()); NotRef(i); Console.WriteLine("调用非引用传递参数方法后:" + i.ToString()); Ref(i); Console.WriteLine("调用引用传递参数方法后:"+i.ToString()J); Console.Read(); } //不使用ref关键字来传递参数 public static void NotRef(int i) { i++; } //使用ref关键字来传递参数 public static void Ref(int i) { i++; } } }
结果为 0,0,1
NotRef方法并不以引用方式传递参数,所以方法得到的是整数i的一个副本,怎么操作,与原数据无关。
2). params
params是一个非常实用的关键字,它允许方法在定义时不确定参数的数量,类似于数组参数。
缺点: 方法声明了params参数后,不允许在其后面再有任何参数。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Test { class useles { //不使用params关键字来传递参数 public static void NotParams(Object[] par) { foreach (Object obj in par) Console.WriteLine(obj); } //使用params关键字来传递参数 public static void ParamsParam(params Object[] par) { foreach (Object obj in par) Console.WriteLine(obj); } //测试 static void Main(string[] args) { String s = "我是字符串"; int i = 10; double f = 2.3; //需现声明数组对象以通过其传入 Object[] par = new Object[3] { s, i, f }; NotParams(par); //params关键字方式 ParamsParam(s, i, f); Console.Read(); } } }
七. 访问级别
类型访问级别
名称 |
C#中关键字 |
简述 |
Private | private | 同一类型的所有方法或类型中的内嵌类型的所有方法可访问 |
Family | protected | 同一类型的所有方法、类型中的内嵌类型的所有方法和派生类型中的所有方法可访问 |
Assemly | internal | 同一程序集中的所有方法可访问 |
Family&Assemly | 没有实现 | 同时满足Family约束和Assemly约束的所有方法 |
Assemly or Family | protected internal | 所有满足Assembly约束或满足Family约束的方法 |
Public | public | 所有方法都可访问 |
类型成员的访问级别
类型 |
C#中默认的可访问性级别 |
可设置的可访问性级别 |
Enum | public | 不可设置 |
Class | private | pubic protected internal private protected internal |
Interface | public | 不可设置 |
Struct | private | public internal private |
八. 深复制与浅复制
浅复制
复制一个对象时,复制原始对象中所有的非静态值类型成员和所有的引用类型成员的引用。
深复制
不因复制所有的非静态值类型成员,而且复制所有引用类型成员的实际对象。
基类System.Object已经为所有类型都实现了浅复制 object MemberwiseClone() 方法。
接口 ICloneable 只包含了一个方法 Clone()。继承此接口的类根据取舍不同实现Clone()可实现为深复制或前复制。
也可以用其他方式实现深复制,不局限与IConeable接口的Clone()方法。
可被继承的类应避免实现IConeable接口,因为那样将导致所有子类都必须实现ICloneable接口。
九. 循环
while 和 do…while经常用在循环不确定的场合,死循环危险系数较高。
for 和 foreach 经常用来遍历数组和集合。for循环功能强大
foreach用来遍历那些实现了IEnumberable的容器类型。数组和常用容器类如ArrayList、List等都实现了IEnumberable接口。 但它有一定限制
不能改变项目值,不能对项目赋值,不能通过属性为项目内部成员赋值。 但项目的公共方法可以被调用。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Test { class useles { public struct A { public int _i; //通过属性改变成员 public int I { set { _i = value; } } //通过公共方法改变成员 public void ModifyI(int i) { _i=i; } public override string ToString() { return _i.ToString(); } } //测试 static void Main(string[] args) { A[] array = new A[3]; foreach (A a in array) { //a=new A(1); //编译不通过,不能改变项目 //a._i=1; //编译不通过,不能改变项目成员变量 //a.I=1; //编译不通过,不能通过属性改变成员变量 a.ModifyI(1); Console.WriteLine(a); } } } }
十. using机制
1)概述
.NET的环境中,托管的资源由.NET的垃圾回收机制来释放
非托管的资源需要程序员自己手动地释放。
两种主动和被动释放非托管资源方式。
IDisposable接口的Dispose方法
类型自己的Finalize方法
任何带有非托管资源的类型,都有必要实现IDisposable的Dispose方法,并且在使用完这些类型之后需要手动地调用对象的Dispose方法来释放对象中的非托管资源。
如果类型正确地实现了Finalize方法,即使Dispose方法不被调用,非托管资源也最终会被释放,但那时资源已被很长时间无谓地占用了
2)using语句提供了一个高效的调用对象Dispose方法的方式。
对于任何实现IDisposable接口的类型,都可使用using语句,而对于那些没有实现IDisposable接口的类型,使用using则导致编译错误
using 语句能够保证使用的对象的Dispose方法在using语句块结束时被调用,无论是否有异常被抛出。
C#编译器在编译时为using语句加上try/finally语句块。本质与异常捕捉语句一样,但是它更简洁。