问题十一: override与overload的区别。
override(重写)修饰符,覆写一个基类中的方法。
overload(重载)则是将同名方法重新写过,达到同名的函数实现不同的功能。从而实现了一个方法有不同的版本。
问题十二: DataReader和DataSet的异同。
DataReader和DataSet最大的区别在于,DataReader使用时始终占用SqlConnection,在线操作数据库。任何对SqlConnection的操作都会引发DataReader的异常。因为DataReader每次只在内存中加载一条数据,所以占用的内存是很小的。因为DataReader的特殊性和高性能,所以DataReader是只进的,你读了第一条后就不能再去读取第一条了。
DataSet则是将数据一次性加载在内存中,抛弃数据库连接,是离线操作数据库。读取完毕即放弃数据库连接。因为DataSet将数据全部加载在内存中,所以比较消耗内存,但是确比DataReader要灵活,可以动态的添加行、列、数据,对数据库进行回传更新操作。
问题十三: C#的异常处理机制。
Try...Catch...Finally 异常处理程序的 Try 块包含希望错误处理程序监视的代码节。如果该代码节中的任何代码在执行期间发生错误, 将检查 Try...Catch...Finally 内的每个 Catch 语句,直到找到条件与该错误匹配的语句。如果找到了这样的语句,则控制转移到 Catch 块内的第一个代码行。如果没有找到匹配的 Catch 语句,则继续搜索包含发生异常的块的外部 Try...Catch...Finally 块的 Catch 语句。此过程在整个堆栈中继续,直到在当前过程中找到匹配的 Catch 块。如果没有找到,将产生错误。
Finally 节中的代码总是最后执行,并且刚好在错误处理块失去范围之前执行,不论 Catch 块内的代码是否已执行。将清理代码(如用于关闭文件和释放对象的代码)放在 Finally 节中。
详细解释如下:
(1)Throw
在发生引发异常的条件时,可以用 throw 语句来对发出信号。例如,如果例程要求非空字符串作为参数,则它可能包含下列代码:
public void Process(string location){
if (location == null)
throw new ArgumentNullException("null value", "location");
}
在该示例中,ArgumentNullException 的新实例是用特定的消息和参数名称来创建的,throw 语句用于引发它。
(2)Try-Catch
用于编写错误处理的最基本的构造是 try-catch。考虑下列代码:
try{
Process(currentLocation);
Console.WriteLine("Done processing");
}
catch (ArgumentNullException e)
{
// handle the exception here
}
在该示例中,如果 ArgumentNullException 被 try 块中的代码引发(在此情况下,是 Process() 函数),控制将被立即转到 catch 块,而不会执行 Console.WriteLine() 调用。
(3)更通用的捕捉:
在上一个示例中,catch 子句捕捉到了 ArgumentNullException,它完全与 Process() 所引发的异常匹配。
不过,这不是一项要求。Catch 子句将捕捉指定的异常或者任何从该类派生的异常。例如:
try
{
Process(currentLocation);
Console.WriteLine("Done processing");
}
catch (ArgumentException e)
{
// handle the exception here
}
由于 ArgumentNullException 是从 ArgumentException 派生的,因此该 catch 子句将捕捉任何一种异常。此外,它还将捕捉其他的派生异常:ArgumentOutOfRangeException、InvalidEnumArgumentException 和 DuplicateWaitObjectException。
由于所有的异常归根到底都是从 Exception 类派生的,因此捕捉 Exception 将捕捉任何异常。对了,不是捕捉任何异常;这是因为 C++ 不会将用户限制为只引发从 Exception 派生的类,C# 提供了捕捉所有异常的语法:
catch
{
// handle the exception here
}
尽管这个语法是为了完整性而提供的,但是实际上很少使用它。大部分的 C++ 程序都将选择引发从 Exception 派生的异常,并且即使它们不这样做,此 catch 语法也不会让您超出引发了什么。
(4)Catch 排序:
对于给定的 try 子句,可以有一个以上的 catch 子句,每个 catch 捕捉不同的异常类型。在上一个示例中,对 ArgumentException 进行特殊处理,然后对所有其他异常进行别的处理可能很合适。选择最具体的(即,派生程度最大的)catch 子句。
那么示例将是这样的:
try
{
Process(currentLocation);
Console.WriteLine("Done processing");
}
catch (ArgumentException e)
{
// handle the exception here
}
catch (Exception e)
{
// handle the more general exception here
}
当使用多个 catch 子句时,导出类型必须始终列在任何基类型之前(或者,以从比较合适到不太合适的顺序列出)。这是为了提高可读性。通过阅读较早的 catch 子句,您可以确定运行时行为将是什么。
(5)Catch 操作::
既然我们已经捕捉到了异常,我们可能希望对它来做一些有意义的事情。我们可能想做的第一件事是用一些附加的上下文信息包装异常。
这是以下面的方式来实现的:
try
{
Process(currentLocation);
Console.WriteLine("Done processing");
}
catch (ArgumentException e)
{
throw new ArgumentException("Error while processing", e);
}
它为 ArgumentException 使用获取一个消息和另一个异常的构造函数。该构造函数将把传递的异常包装在新的异常中,而那个新的将被引发。
此过程为开发人员带来了巨大的好处。不是只获得单个一条有关所发生的事情的信息,包装异常还提供了一种类似堆栈跟踪的东西:
Exception occurred:
System.Exception: Exception in Test1
---> System.Exception: Exception in Test2
---> System.DivideByZeroException: Attempted to divide by zero.
这样的输出使调试变得容易得多。如果用 /debug 编译,在各个等级上还将获得文件名和行号。
包装有助于为调试提供附加的信息。另一种方案是针对需要根据异常采取一些操作的情况。将输出发送到文件的代码看起来可能是这样的:
try
{
FileStream f = new FileStream(filename, FileMode.Create);
StreamWriter s = new StreamWriter(f);
s.WriteLine("{0} {1}", "test", 55);
s.Close();
f.Close();
}
catch (IOException e)
{
Console.WriteLine("Error opening file {0}", filename);
Console.WriteLine(e);
}
如果文件不能打开,则引发异常,激发 catch,出现错误,而程序可以继续执行。在许多情况下,这没问题。
不过,在这种情况下就存在问题。如果异常在文件打开之后发生,则文件将无法关闭。这很糟糕。
所需要的是确保即使发生了异常也可以关闭文件的方法。
(6)Try-Finally
finally 构造用于指定即使发生异常也始终会运行的代码。finally 通常用于清理发生异常时所产生的内容。修订上一个示例:
try
{
FileStream f = new FileStream(filename, FileMode.Create);
StreamWriter s = new StreamWriter(f);
s.WriteLine("{0} {1}", "test", 55);
s.Close();
f.Close();
}
catch (IOException e)
{
Console.WriteLine("Error opening file {0}", filename);
Console.WriteLine(e);
}
finally
{
if (f != null)
f.Close();
}
使用修订后的代码,即使发生了异常 finally 子句也将被运行。