C# 允许代码引发从System.Exception 派生。 例如:
public sealed class TextNumberParser
{
public static int Parse(string textDigit)
{
string[] digitTexts = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
int result = Array.IndexOf(digitTexts, textDigit.ToLower());
if(result < 0)
throw new ArgumentException( "The argument did not represent a digit", nameof(textDigit));
return result;
}
}
两个类似的异常是ArgumentNullException 和 NullReferenceException. 一般在解引用null值时,底层触发NullReferenceException。
参数异常类型(ArgumentException、ArgumentNullException和ArgumentOutRangeException) 一个重要特征是,每个异常都有一个构造器参数,允许实参名标识为一个字符串. C#的规范是, 对于参数类型异常中的参数名称应该使用nameof 操作符。
不要引发System.SystemException 和它派生的异常类型 System.stackoverflowException, System.OutOfMemoryException, System.Runtime.InteropServices.COMException, System.ExecutionEngineException 和System. Runtime.InteropServices.SEHException
不要引发System.Exception 或者System.ApplicationException
考虑在程序继续执行会变得不安全时调用System.Environment.FailFast()来终止进程。
C# 允许使用多个catch 块, 每个catch 块都能定位特定的异常类型。
public static void Main(string[] args)
{
try
{
// throw new Win32Exception(42);
throw new InvalidOperationException("Arbitrary exception");
}
catch(Win32Exception exception)
when(args.Length == exception.NativeErrorCode)
{
//....
}
catch(NullReferenceException exception) {
// Handle NullReferenceException
}
catch(ArgumentException exception) {
// Handle ArgumentException
}
catch(InvalidOperationException exception) {
// Handle ApplicationException
}
catch(Exception exception) {
//…
}
finally {
// Handle any cleanup code here as it runs regardless of whether there is an exception
}
}
重新引发已存在的异常:如果引发一个特定的异常, 会更新所有的栈信息来匹配新的引发位置。这会造成指示异常最初发生的调用位置所有栈信息丢失。 因此C#只能在catch 语句中重新抛出异常,例如:
public static void Main(string[] args)
{
//….
catch(InvalidOperationException exception)
{
bool exceptionHandled = false ;
if (!exceptionHandled )
throw;
}
//……
}
引发现有异常而不替换栈信息:C# 5.0 新增了一种机制,允许引发从前引发的异常而不丢失原始异常中的栈跟踪信息。 这样即使在catch 外也能重新引发异常。System.Runtime.ExceptionServices.ExceptionDispatchInfo 处理这种情况, 例如:
try{
//....
}
catch(AggregateException exception){
exception = exeption.Flatten();
ExceptionDispatchInfo.Capture(exception.InnerException).Throw();
}
从c# 2.0 开始,所有异常(无论是否从System.Exception) 在进入程序集中,都会被包装成从System.Exception 派生。
C# 还支持常规catch 块, 即catch {}. 它在行为上和catch(System.Exception exception) 块完全一致,只是没有类型名和变量名。除此之外, 常规catch块必须是所有catch 块的最后一个。
异常处理规范:
自定义异常的唯一要求是从System.Exception或者它的子类派生。例如:
class DatabseException: System.Expection {
public DatabaseException()
{
//…..
}
public DatabaseException(string message )
{
//…..
}
public DatabaseException(string message, Exception innerException )
{
InnerException= innerException;
//…..
}
public DatabaseException( System.Data.OracleclientException exception )
{
InnerException= innerException;
//…..
}
}
使用自定义异常时,应遵守以下实践:
类似于C++,C# 中的范型类和结构要求使用尖括号声明泛型类型参数以及指定范型类型实参
public void Sketch()
{
Stack path = new Stack();
Cell currentPosition;
ConsoleKeyInfo key; // Added in C# 2.0
Console.WriteLine("Use arrow keys to draw. X to exit.");
for(int i = 2; i < Console.WindowHeight; i++)
Console.WriteLine();
currentPosition = new Cell(Console.WindowWidth / 2, Console.WindowHeight / 2);
path.Push(currentPosition);
FillCell(currentPosition);
do {
bool bFill = false;
key = Move();
switch(key.Key) {
case ConsoleKey.Z:
if(path.Count >= 1)
{
// Undo the previous Move.
currentPosition = path.Pop();
Console.SetCursorPosition(currentPosition.X, currentPosition.Y);
FillCell(currentPosition, ConsoleColor.Black);
Undo();
}
break;
case ConsoleKey.DownArrow:
if(Console.CursorTop < Console.WindowHeight - 2)
currentPosition = new Cell(Console.CursorLeft, Console.CursorTop + 1);
bFill =true;
break;
case ConsoleKey.UpArrow:
if(Console.CursorTop > 1)
currentPosition = new Cell(Console.CursorLeft, Console.CursorTop - 1);
bFill =true;
break;
case ConsoleKey.LeftArrow:
if(Console.CursorLeft > 1)
currentPosition = new Cell(Console.CursorLeft - 1, Console.CursorTop);
bFill =true;
break;
case ConsoleKey.RightArrow:
if(Console.CursorLeft < Console.WindowWidth - 2)
currentPosition = new Cell(Console.CursorLeft + 1, Console.CursorTop);
bFill =true;
break;
default:
Console.Beep(); // Added in C# 2.0
break;
}
if (bFill){
path.Push(currentPosition); // Only type Cell allowed in call to Push().
FillCell(currentPosition);
}
}
while(key.Key != ConsoleKey.X); // Use X to quit.
} | |
private static ConsoleKeyInfo Move() => Console.ReadKey(true);
private static void Undo() {
// stub
}
private static void FillCell(Cell cell)
{
FillCell(cell, ConsoleColor.White);
}
private static void FillCell(Cell cell, ConsoleColor color)
Console.SetCursorPosition(cell.X, cell.Y);
Console.BackgroundColor = color;
Console.Write(' ');
Console.SetCursorPosition(cell.X, cell.Y);
Console.BackgroundColor = ConsoleColor.Black;
}
public struct Cell
{
readonly public int X;
readonly public int Y;
public Cell(int x, int y)
{
X = x;
Y = y;
}
}
在定义泛型类时, 仅需在类名之后使用一对尖括号指定类型参数。使用泛型类时, 类型实参将替换所有指定的类型参数。
public class Stack
{
// Use read-only field prior to C# 6.0
private T[] InternalItems { get; }
public void Push(T data)
{
//...
}
public T Pop()
{
//...
return InternalItems[0];//just for the example.
}
}
泛型类的优点:
类型参数的命名规范:
和方法参数类似,类型参数应具有可描述性,定义泛型类时参数类型名称应包含T 前缀, 例如:public class EntityCollection
C# 支持在语言中全面使用泛型,其中包括接口和结构。语法和类的语法完全相同。 例如:
interface IPair
{
T First { get; set; }
T Second { get; set; }
}
实现接口的语法与非泛型类的语法相同,一个泛型的类型实参可以成为另一个泛型类型的类型参数。
public struct Pair : IPair
{
public T First { get; set; }
public T Second { get; set; }
}
相同泛型接口的不同构造被看成是不同的类型,所以类或结构能多次实现“同一个”泛型接口。
public interface IContainer
{
ICollection Items { get; set;}
}
public class Person : IContainer, IContainer, IContainer {
ICollection IContainer.Items {
get {
//...
return new List();
}
set { //… }
}
ICollection IContainer.Items {
get {
//...
return new List();
}
set { //… }
}
ICollection IContainer.Items {
get {
//...
return new List();
}
set { //… }
}
}
public class Address { } // For example purposes only
public class Phone { } // For example purposes only
public class Email { } // For example purposes only
泛型类或结构的构造器(析构器)不要类型参数。 例如:
public struct Pair : IPair
{
public Pair(T first, T second)
{
First = first;
Second = second;
}
public Pair(T first)
{
First = first;
Second = default(T); // must be initialized
}
public T First { get; set; }
public T Second { get; set; }
}
泛型类型可以使用任意数量的类型参数。
interface IPair
{
TFirst First { get; set; }
TSecond Second { get; set; }
}
public struct Pair : IPair
{
public Pair(TFirst first, TSecond second)
{
First = first;
Second = second;
}
public TFirst First { get; set; }
public TSecond Second { get; set; }
}
从C# 4.0 开始,CLR 团队定义了9 个新的泛型类型,它们都叫Tuple. 和 Pair<…> 一样, 相同名称可以重用,只要元数不同。
public class Tuple
{ // ... }
public class Tuple // : IStructuralEquatable, IStructuralComparable, IComparable
{ // ... }
public class Tuple // : IStructuralEquatable, IStructuralComparable, IComparable
{ // ... }
public class Tuple // : IStructuralEquatable, IStructuralComparable, Comparable
{ // ... }
public class Tuple // : IStructuralEquatable, IStructuralComparable, IComparable
{ // ... }
public class Tuple // : IStructuralEquatable, IStructuralComparable, IComparable
{ // ... }
public class Tuple // : IStructuralEquatable, IStructuralComparable, IComparable
{ // ... }
public class Tuple // : IStructuralEquatable, IStructuralComparable, IComparable
{ // ... }
public class Tuple // : IStructuralEquatable, IStructuralComparable, IComparable
{ // ... }
嵌套类型自动获得包容类型的类型参数,例如: 假如包含类型声明类型参数T,则 类型T也可以在嵌套类型中使用, 如果嵌套类型包含了自己的类型参数T,那么它会隐藏包容类型的同名类型参数。
class Container
{
// Nested classes inherit type parameters.
// Reusing a type parameter name will cause
// a warning.
class Nested
{
void Method(T param0, U param1)
{}
}
}
在声明类型参数的类型主体的人和地方都能访问该类型参数。
泛型允许为类型参数定义约束,这些约束强迫作为类型实参提供的类型遵守各种规则。
public class BinaryTree {
public T Item { get; set; }
public Pair> SubItems
{
get { return _SubItems; }
set {
IComparable first;
first = (IComparable)value.First.Item;
if(first.CompareTo(value.Second.Item) < 0) {
//… // first is less than second.
}
else {
// second is less than or equal to first.
}
_SubItems = value;
}
}
private Pair> _SubItems;
}
如果BinaryTree
约束描述了泛型要求的类型参数的特征。为了声明一个约束, 需要使用where 关键字, 后面跟一对参数:要求。 其中, ”参数”必须时泛型类型中声明的一个参数,而”要求” 描述了类型参数要能转换成的类或接口是否必须有默认构造器, 或者是引用类型还是值类型。
接口约束规定了某个数据类型必须实现某个接口。 例如:
public class BinaryTree
where T : System.IComparable
{
public T Item { get; set; }
public Pair> SubItems
{
get { return _SubItems; }
set {
IComparable first = value.First.Item; // Notice that the cast can now be eliminated.
if(first.CompareTo(value.Second.Item) < 0) {
// … // first is less than second.
}
else {
//... // second is less than or equal to first.
}
_SubItems = value;
}
}
private Pair> _SubItems;
}
有时可能要求将类型实参转换为特定的类类型,这是通过类类型做到的。 例如:
public class EntityDictionary
: System.Collections.Generic.Dictionary
where TValue : EntityBase
{
//...
}
public class EntityBase
{}
如果同时指定了多个约束,那么类类型约束必须第一个出现。同一个参数的多个类类型约束是不被允许的。类似的, 类类型约束不能指定密封类或者不是类的类型。
另一个重要的约束是将类型参数限制为任何非可空的值类型或者任何引用类型 。 编译器不允许在约束中将System.ValueType指定为基类。但是C# 提供了关键字struct/class指定参数类型是值类型还是引用类型。
public struct Nullable :
IFormattable, IComparable,
IComparable>, INullable
where T : struct
{
// ...
}
由于类类型约束要求指定特定的类哦, 所以类类型和struct/class 约束一起使用会相互矛盾, 因此,struct/class 约束和类类型约束不能一起使用。
struct约束有个特别的地方, 可空值类型不符合此约束,应为可空值类型是从Nullable
对于任何给定的类型参数,都可以指定任意数量的接口约束,但是类类型约束只能是一个。每个约束都在一个以逗号分隔的列表中声明,每个参数类型前都需要使用where关键字。
public class EntityDictionary
: Dictionary
where TKey : IComparable, IFormattable
where TValue : EntityBase
{
// ...
}
某些情况下,需要在泛型类中创建类型实参的实例。但是并非所有的对象都保证有公共默认构造器,所以编译器不允许为未约束的类型调用默认构造器。
为了克服这一限制,类在指定了其他约束后使用new().这就是所谓的构造器约束。 它要求实参类型必须有默认构造器。只能对默认构造器约束,不能为带参数的构造器指定约束。
public class EntityBase
{
public TKey Key { get; set; }
}
public class EntityDictionary :
Dictionary
where TKey : IComparable, IFormattable
where TValue : EntityBase, new()
{
// ...
public TValue MakeValue(TKey key)
{
TValue newEntity = new TValue();
newEntity.Key = key;
Add(newEntity.Key, newEntity);
return newEntity;
}
// ...
}
无论是泛型类型参数还是它们的约束,都不会被派生类继承,因为泛型类型参数不是成员。
由于派生的泛型类型参数现在是泛型基类的类型实参,所以类型参数必须具有与基类相同(或更强)的约束。
class EntityBase where T : IComparable
{
// ...
}
// ERROR:
// The type 'T' must be convertible to 'System.IComparable'
// to use it as parameter 'T' in the generic type or
// method.
// class Entity : EntityBase
// {
// ...
// }
约束的限制:
public abstract class MathEx
{
public static T Add(T first, T second)
{
// Error: Operator '+' cannot be applied to
// operands of type 'T' and 'T'.
// return first + second;
return default(T);
}
}
public class BinaryTree
// Error: OR is not supported.
//where T: System.IComparable || System.IFormattable
{
// ...
}
// Error: Constraint cannot be special class 'System.Delegate'
//public class Publisher
// where T : System.Delegate{
// public event T Event;
// public void Publish()
// {
// if(Event != null)
// Event(this, new EventArgs());
// }
//}
public class EntityBase
{
public EntityBase(TKey key)
{
Key = key;
}
public TKey Key { get; set; }
}
public interface IEntityFactory
{
TValue CreateNew(TKey key);
}
public class EntityDictionary :
Dictionary
where TKey : IComparable, IFormattable
where TValue : EntityBase
where TFactory : IEntityFactory, new()
{
public TValue New(TKey key)
{
TFactory factory = new TFactory();
TValue newEntity = factory.CreateNew(key);
Add(newEntity.Key, newEntity);
return newEntity;
}
//...
}
public class Order : EntityBase
{
public Order(Guid key) :
base(key)
{
// ...
}
}
public class OrderFactory : IEntityFactory
{
public Order CreateNew(Guid key)
{
return new Order(key);
}
}
泛型方法要使用泛型类型参数,这一点和泛型类型一致。在泛型和非泛型类型中都可以使用泛型方法。 要使用泛型方法,要在方法名后添加类型参数,例如:
public static class MathEx
{
public static T Max(T first, params T[] values)
where T : IComparable
{
T maximum = first;
foreach(T item in values)
{
if(item.CompareTo(maximum) > 0)
{
maximum = item;
}
}
return maximum;
}
// …..
}
调用泛型方法时,要在方法的类型名后提供类型实参, 例如:
public static void Main()
{
Console.WriteLine(
MathEx.Max(7, 490));
//….
}
在大多数情况下,可以在调用时不指定类型实参。这就是所谓的类型推断。方法类型推断算法在进行推断时,只考虑方法实参、实参类型以及形参类型。
泛型方法的类型参数也允许指定约束,其方式与在泛型类型中指定类型参数方式相同,例如:
public class ConsoleTreeControl {
public static void Show(BinaryTree tree, int indent)
where T : IComparable
{
Console.WriteLine("\n{0}{1}", "+ --".PadLeft(5 * indent, ' '), tree.Item.ToString());
if(tree.SubItems.First != null)
Show(tree.SubItems.First, indent + 1);
if(tree.SubItems.Second != null)
Show(tree.SubItems.Second, indent + 1);
}
}
由于BinaryTree
基于值类型的泛型实例化:用值类型作为类型参数首次构造一个泛型类型时,”运行时” 会将指定类型参数放到CIL的合适位置,从而创建一个具体化的泛型类型。总之,”运行时” 会针对每个新的” 参数值类型“ 创建一个新的具体化的泛型类型。
基于引用类型的泛型实例化:使用引用类型作为类型参数首次构造一个泛型类型时,运行时会在CIL代码中用object 替换参数类型创建一个具体化的泛型类型(而不是基于所提供的类型参数创建一个具体化的泛型类型). 以后,每次用引用类型参数实例化一个构造好的类型,运行时都会重用以前生成好的泛型类型的版本。