16.类和结构的区别?
答:
类:
类是引用类型在堆上分配,类的实例进行赋值只是复制了引用,都指向同一段实际对象分配的内存
类有构造和析构函数
类可以继承和被继承
结构:
结构是值类型在栈上分配(虽然栈的访问速度比较堆要快,但栈的资源有限放),结构的赋值将分配产生一个新的对象。
结构没有构造函数,但可以添加。结构没有析构函数
结构不可以继承自另一个结构或被继承,但和类一样可以继承自接口
示例:
根据以上比较,我们可以得出一些轻量级的对象最好使用结构,但数据量大或有复杂处理逻辑对象最好使用类。
如:Geoemtry(GIS里的一个概论,在OGC标准里有定义)最好使用类,而Geometry中点的成员最好使用结构
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
namespaceExample16
{
interfaceIPoint
{
doubleX
{
get;
set;
}
doubleY
{
get;
set;
}
doubleZ
{
get;
set;
}
}
//结构也可以从接口继承
structPoint:IPoint
{
privatedoublex,y,z;
//结构也可以增加构造函数
publicPoint(doubleX,doubleY,doubleZ)
{
this.x=X;
this.y=Y;
this.z=Z;
}
publicdoubleX
{
get{returnx;}
set{x=value;}
}
publicdoubleY
{
get{returnx;}
set{x=value;}
}
publicdoubleZ
{
get{returnx;}
set{x=value;}
}
}
//在此简化了点状Geometry的设计,实际产品中还包含Project(坐标变换)等复杂操作
classPointGeometry
{
privatePointvalue;
publicPointGeometry(doubleX,doubleY,doubleZ)
{
value=newPoint(X,Y,Z);
}
publicPointGeometry(Pointvalue)
{
//结构的赋值将分配新的内存
this.value=value;
}
publicdoubleX
{
get{returnvalue.X;}
set{this.value.X=value;}
}
publicdoubleY
{
get{returnvalue.Y;}
set{this.value.Y=value;}
}
publicdoubleZ
{
get{returnvalue.Z;}
set{this.value.Z=value;}
}
publicstaticPointGeometryoperator+(PointGeometryLeft,PointGeometryRigth)
{
returnnewPointGeometry(Left.X+Rigth.X,Left.Y+Rigth.Y,Left.Z+Rigth.Z);
}
publicoverridestringToString()
{
returnstring.Format("X:{0},Y:{1},Z:{2}",value.X,value.Y,value.Z);
}
}
classProgram
{
staticvoidMain(string[]args)
{
PointtmpPoint=newPoint(1,2,3);
PointGeometrytmpPG1=newPointGeometry(tmpPoint);
PointGeometrytmpPG2=newPointGeometry(tmpPoint);
tmpPG2.X=4;
tmpPG2.Y=5;
tmpPG2.Z=6;
//由于结构是值类型,tmpPG1和tmpPG2的坐标并不一样
Console.WriteLine(tmpPG1);
Console.WriteLine(tmpPG2);
//由于类是引用类型,对tmpPG1坐标修改后影响到了tmpPG3
PointGeometrytmpPG3=tmpPG1;
tmpPG1.X=7;
tmpPG1.Y=8;
tmpPG1.Z=9;
Console.WriteLine(tmpPG1);
Console.WriteLine(tmpPG3);
Console.ReadLine();
}
}
}
结果:
X:1,Y:2,Z:3
X:4,Y:5,Z:6
X:7,Y:8,Z:9
X:7,Y:8,Z:9
17.接口的多继承会带来哪些问题?
答:
C#中的接口与类不同,可以使用多继承,即一个子接口可以有多个父接口。但如果两个父成员具有同名的成员,就产生了二义性(这也正是C#中类取消了多继承的原因之一),这时在实现时最好使用显式的声明
示例:
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
namespaceExample17
{
classProgram
{
//一个完整的接口声明示例
interfaceIExample
{
//属性
stringP
{
get;
set;
}
//方法
stringF(intValue);
//事件
eventEventHandlerE;
//索引指示器
stringthis[intIndex]
{
get;
set;
}
}
interfaceIA
{
intCount{get;set;}
}
interfaceIB
{
intCount();
}
//IC接口从IA和IB多重继承
interfaceIC:IA,IB
{
}
classC:IC
{
privateintcount=100;
//显式声明实现IA接口中的Count属性
intIA.Count
{
get{return100;}
set{count=value;}
}
//显式声明实现IB接口中的Count方法
intIB.Count()
{
returncount*count;
}
}
staticvoidMain(string[]args)
{
CtmpObj=newC();
//调用时也要显式转换
Console.WriteLine("Countproperty:{0}",((IA)tmpObj).Count);
Console.WriteLine("Countfunction:{0}",((IB)tmpObj).Count());
Console.ReadLine();
}
}
}
结果:
Countproperty:100
Countfunction:10000
18.抽象类和接口的区别?
答:
抽象类(abstractclass)可以包含功能定义和实现,接口(interface)只能包含功能定义
抽象类是从一系列相关对象中抽象出来的概念,因此反映的是事物的内部共性;接口是为了满足外部调用而定义的一个功能约定,因此反映的是事物的外部特性
分析对象,提炼内部共性形成抽象类,用以表示对象本质,即“是什么”
为外部提供调用或功能需要扩充时优先使用接口
19.别名指示符是什么?
答:
通过别名指示符我们可以为某个类型起一个别名
主要用于解决两个命名空间内有同名类型的冲突或避免使用冗余的命名空间
别名指示符在所有命名空间最外层定义,作用域为整个单元文件。如果定义在某个命名空间内,那么它只在直接隶属的命名空间内起作用
示例:
Class1.cs:
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
namespacecom.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01
{
classClass1
{
publicoverridestringToString()
{
return"com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01'sClass1";
}
}
}
Class2.cs:
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
namespacecom.nblogs.reonlyrun.CSharp25QExample.Example19.Lib02
{
classClass1
{
publicoverridestringToString()
{
return"com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib02'sClass1";
}
}
}
主单元(Program.cs):
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
//使用别名指示符解决同名类型的冲突
//在所有命名空间最外层定义,作用域为整个单元文件
usingLib01Class1=com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01.Class1;
usingLib02Class2=com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib02.Class1;
namespaceExample19
{
namespaceTest1
{
//Test1Class1在Test1命名空间内定义,作用域仅在Test1之内
usingTest1Class1=com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01.Class1;
classClass1
{
//Lib01Class1和Lib02Class2在这可以正常使用
Lib01Class1tmpObj1=newLib01Class1();
Lib02Class2tmpObj2=newLib02Class2();
//TestClass1在这可以正常使用
Test1Class1tmpObj3=newTest1Class1();
}
}
namespaceTest2
{
usingTest1Class2=com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01.Class1;
classProgram
{
staticvoidMain(string[]args)
{
//Lib01Class1和Lib02Class2在这可以正常使用
Lib01Class1tmpObj1=newLib01Class1();
Lib02Class2tmpObj2=newLib02Class2();
//注意这里,TestClass1在这不可以正常使用。
//因为,在Test2命名空间内不能使用Test1命名空间定义的别名
//Test1Class1tmpObj3=newTest1Class1();
//TestClass2在这可以正常使用
Test1Class2tmpObj3=newTest1Class2();
Console.WriteLine(tmpObj1);
Console.WriteLine(tmpObj2);
Console.WriteLine(tmpObj3);
Console.ReadLine();
}
}
}
}
结果:
com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01'sClass1
com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib02'sClass1
com.nblogs.reonlyrun.CSharp25QExample.Example19.Lib01'sClass1
20.如何手工释放资源?
答:
.NET平台在内存管理方面提供了GC(GarbageCollection),负责自动释放托管资源和内存回收的工作。但在以下两种情况需要我们手工进行资源释放:一、由于它无法对非托管资源进行释放,所以我们必须自己提供方法来释放对象内分配的非托管资源,比如你在对象的实现代码中使用了一个COM对象;二、你的类在运行是会产生大量实例(象GIS中的Geometry),必须自己手工释放这些资源以提高程序的运行效率
最理想的办法是通过实现一个接口显式的提供给客户调用端手工释放对象,System命名空间内有一个IDisposable接口,拿来做这事非常合适,省得我们自己再声明一个接口了
示例:
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
namespaceExample20
{
classProgram
{
classClass1:IDisposable
{
//析构函数,编译后变成protectedvoidFinalize(),GC会在回收对象前会调用调用该方法
~Class1()
{
Dispose(false);
}
//通过实现该接口,客户可以显式地释放对象,而不需要等待GC来释放资源,据说那样会降低效率
voidIDisposable.Dispose()
{
Dispose(true);
}
//将释放非托管资源设计成一个虚函数,提供在继承类中释放基类的资源的能力
protectedvirtualvoidReleaseUnmanageResources()
{
//Dosomething...
}
//私有函数用以释放非托管资源
privatevoidDispose(booldisposing)
{
ReleaseUnmanageResources();
//为true时表示是客户显式调用了释放函数,需通知GC不要再调用对象的Finalize方法
//为false时肯定是GC调用了对象的Finalize方法,所以没有必要再告诉GC你不要调用我的Finalize方法啦
if(disposing)
{
GC.SuppressFinalize(this);
}
}
}
staticvoidMain(string[]args)
{
//tmpObj1没有手工释放资源,就等着GC来慢慢的释放它吧
Class1tmpObj1=newClass1();
//tmpObj2调用了Dispose方法,传说比等着GC来释放它效率要调一些
//个人认为是因为要逐个对象的查看其元数据,以确认是否实现了Dispose方法吧
//当然最重要的是我们可以自己确定释放的时间以节省内存,优化程序运行效率
Class1tmpObj2=newClass1();
((IDisposable)tmpObj2).Dispose();
}
}
}