本系列为dotnettutorial教程中关于C#语言的一些基础知识,前面语言基础介绍部分就简单过一下没做记录了。主要记录一些平时可能关注比较少,或是非常重要的一些知识点。
参考连接:C#.NET Tutorials
基础知识
-
重写对象Equals,ToString方法:默认ToString返回对象的命名空间+名字,Equals默认与==操作相同,当比较对象时,两个比较两个对象引用的内容是否一致。
-
check/uncheck关键字:check关键字用于检查操作符左侧的变量是否会溢出,如果溢出抛出运行时错误。uncheck和编译器默认动作一致,不做检查。
OOP面向对象
-
构造函数
-
析构函数
- 析构函数只能在类中定义,不可用于struct中。
- 只能定义一个析构函数,不可以有重载,不可以有参数,不可以有访问修饰符。
- 析构函数无法显示调用,它是由GC来触发。可以请求GC执行析构GC.Collect()
应用程序中应该避免使用空的析构函数。类包含析构函数的话,会创建一个Finalize函数链入口。如果析构函数仅调用父类析构函数,析构函数未执行任何操作,将会对垃圾回收性能造成影响。
- 垃圾回收
托管对象:运行在dotnet framework的CLR(公共语言运行时)中,由C#、VB.NET、F#等语言编写的程序。
非托管对象:需要自己的运行环境,如C、C++、Java等开发的程序。
垃圾回收是一个独立服务,定期执行回收不再使用的托管对象(仅回收托管对象,非托管对象不受垃圾回收控制)。
机制:用一个虚拟的代的概念,来作为回收内存依据。三代:0代、1代、2代。
代用来表示对象是否需要继续被使用。初创建的对象,归入0代,垃圾回收开始运行,检查0代中是否有对象不再需要,如有重置其内存;其余需要继续使用的归入1代。垃圾回收再次运行时,同样执行0代检测,再执行对1代对象的检测。如1代中有不再使用的对象,释放其内存;并将1代中继续使用的对象归入2代中。
优化:归入1代和2代中的对象,表明他们需要更多的被使用,在应用程序中需要驻留的时间更长。因此,垃圾回收不需要像检测0代一样,每次运行都去检测1代和2代的对象。这样就能够达到提升效率的目的。0代中越多对象,越高效,越多的内存能被利用到。
托管对象,可以不需要析构函数,默认创建对象存放在0代中,这样内存利用效率最高,垃圾回收会尽快回收不再需要使用的对象。
非托管对象,需要手动释放。建议在调用非托管对象的类中,实现IDispose接口,使用公共的Dispose方法。
public class MyClass2 : IDisposable
{
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
}
disposedValue = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
~MyClass2()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(false);
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
GC.SuppressFinalize(this);
}
#endregion
}
- disposedValue: 该变量用来标记是否为重复调用。
- Dispose(bool disposing): 在该方法内编写实际销毁托管资源和非托管资源的代码程序。disposing作为输入变量,指示是否需要销毁托管资源。第一次请求时,该变量检查disposedValue值为false,执行clean-up活动,并在结束时将值设置为true。重复调用时,则不再执行clean-up活动。
- ~UmmanagedResource(): 默认该析构函数代码都被注释掉了。如果取消注释,则需要自己编写清理托管资源的程序,然后调用Dispose(false)方法来清理非托管资源。只有当Dispose(bool disposing)方法包含了释放非托管资源的程序时,才能重写析构函数或终结器函数。
- Dispose(): 该方法用于暴露给外部程序,用于外部程序调用来清理托管和非托管资源。在该方法内部调用私有方法Dispose(true)来真正实现清理工作。如果重写析构函数或者终结器函数,还需要调用GC.SuppressFinalize(this),告诉CLR不要调用析构函数或终结器函数。只有重写了析构函数或终结器函数才需要将该行代码取消注释。
总结
托管资源不需要考虑垃圾回收,交给GC即可。(比如下面封装中创建的Bank类)
包含非托管资源的类,需要实现IDispose接口(比如FileStream类,它的父类Stream就实现了IDispose接口)
- 访问修饰符
类的访问修饰符只能是public 和 internal
修饰符 | 类内部 | 继承类 | 非继承类 | 不同程序集继承类 | 不同程序集非继承类 |
---|---|---|---|---|---|
private | YES | NO | NO | NO | NO |
public | YES | YES | YES | YES | YES |
protected | YES | YES | NO | YES | NO |
internal | YES | YES | YES | NO | NO |
protected internal | YES | YES | YES | YES | NO |
private protected | YES | YES | NO | NO | NO |
- 封装
优点: 数据保护(保存数据前进行验证)、 实现数据隐藏、 安全、 灵活性、 控制
使用属性get、set提供数据存取,类似于使用两个公共的方法
public class Bank
{
private double _Amount;
public double Amount
{
get
{
return _Amount;
}
set
{
if(value<0)
{
throw new Exception("Input Positive Value");
}
else
{
_Amount = value;
}
}
}
/* 使用方法提供数据隐藏
public double GetAmount()
{
return _Amount;
}
public void SetAmount(double amount)
{
if(amount<0)
{
throw new Exception("Input Positive Value");
}
else
{
this.Amount = amount;
}
}
*/
}
- 抽象: 把必要的功能展示出来并不关注具体实现细节的过程。
使用接口,仅定义服务提供的方法,不提供实现。
使用抽象类和抽象方法,定义服务提供的方法,可以将方法的实现在抽象类中进行。