- C# IDisposable用法详解
简述:
在Net中,由GC垃圾回收线程掌握对象资源的释放,程序员无法掌控析构函数的调用时机。对于一些非托管资源,比如数据库链接对象等,需要实现IDisposable接口进行手动的垃圾回收。
那么什么时候使用Idisposable接口,以及如何使用呢?
一、IDisposable的接口定义如下:
public interface IDisposable
{
// Summary:
// Performs application-defined tasks associated with freeing, releasing, or
// resetting unmanaged resources.
void Dispose();
}
二、IDisposable基本应用
1.定义一个实现了IDisposable接口的类
public class CaryClass :IDisposable
{
public void DoSomething()
{
Console.WriteLine("Do some thing....");
}
public void Dispose()
{
Console.WriteLine("及时释放资源");
}
}
2、两种方式来调用
(1)使用Using语句会自动调用Dispose方法
using (CaryClass caryClass = new CaryClass())
{
caryClass.DoSomething();
}
(2)现实调用该接口的Dispose方法
CaryClass caryClass = new CaryClass();
try
{
caryClass.DoSomething();
}
finally
{
IDisposable disposable = caryClass as IDisposable;
if (disposable != null)
disposable.Dispose();
}
三、IDisposable实现释放资源
public class DisposablClass : IDisposable
{
//是否回收完毕
bool _disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
DisposableClass()
{
Dispose(false);
}
//这里的参数表示示是否需要释放那些实现IDisposable接口的托管对象
protected virtual void Dispose(bool disposing)
{
if(_disposed) return; //如果已经被回收,就中断执行
if(disposing)
{
//TODO:释放那些实现IDisposable接口的托管对象
}
//TODO:释放非托管资源,设置对象为null
_disposed = true;
}
}
四、IDisposable实例分析
Dispose()方法
当需要回收非托管资源的DisposableClass类,就调用Dispoase()方法。而这个方法不会被CLR自动调用,需要手动调用。
DisposableClass(),析构函数
当托管堆上的对象没有被其它对象引用,GC会在回收对象之前,调用对象的析构函数。这里的~DisposableClass()析构函数的意义在于告诉GC你可以回收我,Dispose(false)表示在GC回收的时候,就不需要手动回收了。
虚方法Dispose(bool disposing)
1、通过此方法,所有的托管和非托管资源都能被回收。参数disposing表示是否需要释放那些实现IDisposable接口的托管对象。
2、如果disposings设置为true,就表示DisposablClass类依赖某些实现了IDisposable接口的托管对象,可以通过这里的Dispose(bool disposing)方法调用这些托管对象的Dispose()方法进行回收。
3、如果disposings设置为false,就表示DisposableClass类依赖某些没有实现IDisposable的非托管资源,那就把这些非托管资源对象设置为null,等待GC调用DisposableClass类的析构函数,把这些非托管资源进行回收。
4、另外,以上把Dispose(bool disposing)方法设置为protected virtual的原因是希望有子类可以一起参与到垃圾回收逻辑的设计,而且还不会影响到基类。
比如有这样的一个子类
public class SubDisposableClass : DiposableClass
{
private bool _disposed; //表示是否已经被回收
protected override void Dispose(bool disposing)
{
if(!_disposed) //如果还没有被回收
{
if(disposiing) //如果需要回收一些托管资源
{
//TODO:回收托管资源,调用IDisposable的Dispose()方法就可以
}
//TODO:回收非托管资源,把之设置为null,等待CLR调用析构函数的时候回收
_disposed = true;
}
base.Dispose(disposing);//再调用父类的垃圾回收逻辑
}
}
五、IDisposable总结
当我们自定义的类及其业务逻辑中引用某些托管和非托管资源,就需要实现IDisposable接口,实现对这些资源对象的垃圾回收。
- C# Static关键字
静态类
1.只能包含静态成员(静态方法或静态变量),非静态成员是不能使用的,而非静态类可以包含静态的方法、字段、属性或事件,且无论对这个非静态类创建多少个实例,它的静态成员都只有一个。
2.不能对其实例化。
3.不能被继承,因为静态类本质是一个抽象的密封类。
4.不能包含实例构造函数。
实例构造函数
1、构造函数的名字与类名相同。
2、使用 new 表达式创建类的对象或者结构(例如int)时,会调用其构造函数。并且通常初始化新对象的数据成员。
3、除非类是静态的,否则会为没有构造函数的类,自动生成一个默认构造函数,并使用默认值来初始化对象字段。
4、构造函数可以有参数,可以以多态的形式存在多个构造函数。
静态构造函数
1.静态构造函数不使用访问修饰符或不具有参数。(不加访问修饰符默认为private)
2.在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数以初始化类。
3.不能直接调用静态构造函数。
4.执行顺序:静态变量 > 静态构造函数 > 静态函数
5.执行顺序:静态变量 > 静态构造函数 > 构造函数
class Program
{
public static int x = 0;//静态变量
//静态构造函数
static Program() {
x = 1;
}
//构造函数
Program()
{
x = 2;
}
private static void Main(string[] args)//程序入口,当执行到 Main时,执行public static int x = 0;接着执行静态构造函数(即上面的第二点)
{
System.Console.WriteLine("x={0}", x);
Program p = new Program();//执行构造函数
System.Console.WriteLine("x={0}", x);
System.Console.Read();
}
}
//执行结果:x=1,x=2
创建某个类型的第一个实例时,所进行的操作顺序为:
1.静态变量设置为0
2.执行静态变量初始化器
3.执行基类的静态构造函数
4.执行静态构造函数
5.实例变量设置为0
6.执行实例变量初始化器
7.执行基类中合适的实例构造函数
8.执行实例构造函数
即:子类静态变量初始化>子类静态构造函数>父类静态变量初始化 > 父类静态构造函数 > 父类实例变量初始化>父类实例构造函数 > 子类实例变量初始化>本身实例构造函数。
public class A
{
public static readonly int x;
static A()
{
x = B.y + 1;
}
}
public class B
{
public static int y = A.x + 1;
public static void Main(string[] args)
{
Console.WriteLine("x:{0},y:{1}。", A.x, y);
Console.ReadLine();
}
}
1、首先,每一个项目有且只能有一个静态类的Main函数作为入口函数。而入口函数是最先执行的。
2、由于Main函数在B类里面,首先会初始化B类。而类的初始化顺序是:类里的静态变量,然后执行静态构造函数。
3、运行起先执行 public static int y = A.x + 1 这个,执行的时候,会先把 y 初始化为0,然后计算 y 的值。
4、计算 y 的值的时候,调用了 A 的静态变量 x 。所以会先初始化A。
5、初始化A时首先去执行 public static readonly int x ,先把 x 初始化为0。
6、然后执行A的静态构造函数 x = B.y + 1 此时 y 已经初始化为0了。
7、计算得到 x = 1。然后回到 public static int y = A.x + 1 得到 y = 2。
8、然后再执行Main函数的内容。得出结果x=1,y=2
//父类
class animal
{
public static int leg;
public int head;
static animal()
{
System.Console.WriteLine("animal的静态构造函数,leg={0}", leg);
}
public animal()
{
System.Console.WriteLine("animal的实例构造函数,head={0}", head);
}
}
//子类
class dog:animal
{
public static int leg;
public int head;
public dog() {
head = 1;
System.Console.WriteLine("dog的实例构造方法,head={0}", head);
}
static dog()
{
leg = 1;
System.Console.WriteLine("dog的静态构造方法,leg={0}", leg);
}
}
//主函数
private static void Main(string[] args)
{
dog d = new dog();
}
运行结果:
dog的静态构造方法,leg=1
animal的静态构造函数,leg=0
animal的实例构造函数,head=0
dog的实例构造方法,head=1
//子类静态变量初始化>子类静态构造函数>父类静态变量初始化 > 父类静态构造函数 > 父类实例变量初始化>父类实例构造函数 > 子类实例变量初始化>本身实例构造函数。
静态变量
1.static只能修饰成员变量,不能修饰局部变量。
(在类中定义的变量称为成员变量,在整个类中都有效;在方法中定义的变量称为局部变量,当前定义的方法内有效,方法调用被销毁,不能在其他类中进行调用)
2.表示每次重新使用该变量所在方法、类或自定义类时,变量的值为程序这次运行最后一次为变量赋值时的值。
3.静态变量一直记录变量的值,一直到下次赋值时。
4.不同线程下访问的静态属性总是同一属性,如果某一线程更改了属性值,将造成其他线程访问属性值的错误。因此方法中访问同一静态属性就需要使用lock关键字,或创建互斥对象来保持静态属性在同一时间只能被某一对象的属性或方法访问。
5.静态成员只被创建一次,所以静态成员只有一份,而实例成员有多少个对象,就有多少个成员。
class program2
{
public static int x;
public static void func() {
x += 1;
}
}
private static void Main(string[] args)
{
program2.func();
System.Console.WriteLine("x={0}", program2.x);
program2.func();
System.Console.WriteLine("x={0}", program2.x);
System.Console.Read();
}
结果:x=1,x=2
静态方法
1.在方法(函数)前用static修饰,表示此方法为所在类或所在自定义类所有,而不是这个类的实例所有。
2.在静态方法中只能直接调用同类中其他的静态成员(包括变量和方法), 而不能直接访问类中的非静态成员。
3.每一个线程在同一时间访问的静态方法都是不同的,因此静态方法在多线程调用中不会产生冲突。
4.在静态方法中不能直接调用实例成员,因为静态方法被调用时,对象还有可能不存在。
5.this/base关键字在静态方法不能使用,因为有可能对象还不存在。
6.静态方法只能被重载,不能被重写,因为静态方法不属于类的实例成员。
class program2
{
public static int x;
public static void func() {
x += 1;
}
}
private static void Main(string[] args)
{
program2 p = new program2();
p.func();//报错,无法使用实例引用访问func(),因为此方法为所在类或所在自定义类所有,而不是这个类的实例所有。
}
应用场景
1.当变量需要被共享时可以将变量定义为静态变量。
2.当方法需要被反复调用时可以将方法定义为静态方法。
3.当一个类中包含的成员都是静态时可以将类定义为静态类。
- C# override详解:
重载、重写、覆写,分别指的是overload、override、new。
一、override重写,是在子类中重写父类中的方法,两个函数的函数特征(函数名、参数类型与个数)相同。用于扩展或修改继承的方法、属性、索引器或事件的抽象或虚拟实现。提供从基类继承的成员的新实现,而通过override声明重写的方法称为基方法。
注意事项:
1.重写基方法必须具有与override方法相同的签名。
2.override声明不能更改virtual方法的可访问性,且override方法与virtual方法必须具有相同级别访问修饰符。
3.不能用new、static、virtual修饰符修改override方法。
4.重写属性声明必须指定与继承的属性完全相同的访问修饰符、类型和名称。
5.重写的属性必须是virtual、abstract或override。
6.不能重写非虚方法或静态方法。
7.父类中有abstract,那么子类同名方法必定有override,若父类中有 virtual方法,子类同名方法不一定是override,可能是overload。
8.override必定有父子类关系。
二、overload重载,在同一个类中方法名相同、参数或返回值不同的多个方法即为方法重载。
注意事项:
1.出现在同一个类中。
2.参数列表不同或返回类型和参数列表都不同,只有返回类型不同不能重载。(参数列表包括参数个数和参数类型)
三、overwrite覆写,用new实现。在子类中用 new 关键字修饰定义的与父类中同名的方法,也称为覆盖,覆盖不会改变父类方法的功能。
示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Test2
{
class Parent
{
public void F()
{
Console.WriteLine("1 Parent.F()");
}
public virtual void G() //抽象方法
{
Console.WriteLine("2 Parent.G()");
}
public int Add(int x, int y)
{
return x + y;
}
public float Add(float x, float y) //重载(overload)Add函数
{
return x + y;
}
}
class ChildOne : Parent //子类一继承父类
{
new public void F() //覆写(overwrite)父类函数
{
Console.WriteLine("3 ChildOne.F()");
}
public override void G() //重写(override)父类虚函数,主要实现多态
{
Console.WriteLine("4 ChildOne.G()");
}
}
class ChildTwo : Parent //子类二继承父类
{
new public void F()//用 new 关键字修饰定义的与父类中同名的方法,也称为覆盖。
{
Console.WriteLine("5 ChildTwo.F()");
}
public override void G()
{
Console.WriteLine("6 ChildTwo.G()");
}
}
class Program
{
static void Main(string[] args)
{
Parent childOne = new ChildOne();
Parent childTwo = new ChildTwo();
//调用Parent.F()
childOne.F();
childTwo.F();
//实现多态
childOne.G();
childTwo.G();
Parent load = new Parent();
//重载(overload)
Console.WriteLine("7 "+load.Add(1, 2));
Console.WriteLine("8 "+load.Add(3.4f, 4.5f));
Console.Read();
}
}
}
- IEnumerable的几个简单用法
看到IEnumerable这个接口,我们可能会觉得很神奇,在一般的编程时,基本上我们是想不到去用它的,可是,俗话说得好,存在便是道理,那么,它对我们来说,能够带来哪些奇妙的事情呢?
要想弄懂它,我们还是看看其定义吧!
在MSDN上,是这么说的,它是一个公开枚举数,该枚举数支持在非泛型集合上进行简单的迭代。换句话说,对于所有数组的遍历,都来自IEnumerable,那么我们就可以利用这个特性,来定义一个能够遍历数组的通用方法,这样看来,是不是很神奇呢?
例如:
public static void Print(IEnumerable myList)
{
int i = 0;
foreach (Object obj in myList)
{
if (obj is Student)//这个是类型的判断,这里Student是一个类或结构
{
Student s=(Student)obj;
Console.WriteLine("\t[{0}]:\t{1}", i++, s.Sname);
}
if (obj is int)
{
Console.WriteLine("INT:{0}",obj);
}
}
Console.WriteLine();
}
上面,我们可以在foreach中进行多个if判断,来进行相应的操作。
IEnumerable 的另一个用法是在泛型中的使用。其中用lamda表达式在数组中查询,具体例子如下:
List<string> fruits =
new List<string> { "apple", "passionfruit", "banana", "mango",
"orange", "blueberry", "grape", "strawberry" };
// List<string> query = fruits.Where(fruit => fruit.Length < 6).ToList();
IEnumerable<string> query = fruits.Where(fruit => fruit.Length < 6);
foreach (string fruit in query)
Console.WriteLine(fruit);
以上的两个例子呢,我觉得在平时编程中,还是会经常用到的,我们不妨试试。。。
- C# Stack.ElementAt(int index)
栈中取值:index是栈的索引值,从0开始,如果是0,就取栈顶的元素,因为栈是后进先出的原则。举例:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp10
{
class Program
{
static void Main(string[] args)
{
Stack sta = new Stack();
sta.Push("1");
sta.Push("2");
sta.Push("3");
sta.Push("4");
//string a = sta.ElementAt(0); //取栈顶元素
//string b = sta.ElementAt(1); //取栈的第二个元素
//Console.WriteLine(a);
//Console.ReadLine();
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Queue<int> queNums = new Queue<int>(nums);
List<int> lstNums = new List<int>();
for (int i = 0; i < queNums.Count; i++)
{
lstNums.Add(queNums.ElementAt(i));
}
Console.WriteLine(string.Join("\t", lstNums));
}
}
}