C#基础06-函数异常
1、函数定义与语法
(1)定义与语法
[ 访问修饰符] [ static ] 返回类型 函数名( 参数列表)
{ return 返回值;
}
访问修饰符:public
(全局可见)、private
(类内访问)、protected
(派生类可访问) 静态修饰符:static
使函数无需实例化即可调用(如工具类方法) 返回值:void
表示无返回值;其他类型需用 return
返回匹配的值 参数列表:可包含零或多个参数,格式:类型 参数名
案例
public static int Add ( int a, int b)
{ return a + b;
}
(2)参数传递机制
传递方式 关键字 内存行为 特点 调用示例 值传递 无 传递原始数据的副本 函数内修改不影响外部变量 Add(x, y)
引用传递 ref
传递原始数据的内存地址 函数内修改直接影响外部变量 Swap(ref a, ref b)
输出参数 out
传递内存地址,必须赋值 用于返回多个结果 Parse(str, out int num)
void Increment ( int num) { num++ ; }
void IncrementRef ( ref int num) { num++ ; }
void GetValues ( out int x, out int y) { x = 1 ; y = 2 ; }
(3)可选参数与命名参数
void Log ( string msg, bool isError = false ) { .. . }
Log ( "Info" ) ;
Log ( msg : "Error" , isError : true ) ;
(4)返回值控制
非 void
函数所有分支必须返回匹配类型的值 避免在返回前修改引用类型的状态(可能引发副作用)
(5)异常处理
public int SafeDivide ( int a, int b)
{ if ( b == 0 ) throw new DivideByZeroException ( ) ; return a / b;
}
2、ref 和 out 关键字
(1)ref
:修改现有变量
static void Swap ( ref int a, ref int b)
{ int temp = a; a = b; b = temp;
} static void Main ( )
{ int x = 10 , y = 20 ; Swap ( ref x, ref y) ; Console. WriteLine ( $"x= { x } , y= { y } " ) ;
}
关键点: 调用前 x
和 y
必须初始化(否则编译报错)。 方法内可直接读取或修改参数值(非强制修改)。
(2)out
:返回多个结果
场景:尝试解析字符串为整数(需返回解析结果和成功状态)。
static bool TryParseInt ( string input, out int result)
{ if ( int . TryParse ( input, out int parsedValue) ) { result = parsedValue; return true ; } result = 0 ; return false ;
} static void Main ( )
{ string input = "123" ; int number; if ( TryParseInt ( input, out number) ) Console. WriteLine ( $"解析成功: { number } " ) ;
}
关键点: 调用前 number
无需初始化。 方法内 必须为 out
参数赋值(否则编译报错)。
(3)进阶:大型结构体优化性能
struct Config { public int Size; public string Name; } static void ModifyConfig ( ref Config config)
{ config. Size *= 2 ;
} static void Main ( )
{ Config cfg = new Config { Size = 100 , Name = "Default" } ; ModifyConfig ( ref cfg) ; Console. WriteLine ( $"New Size: { cfg. Size } " ) ;
}
为何用 ref
: 避免值类型结构体复制的性能开销。
(4)ref
与 out
核心区别总结
特性 ref
out
初始化要求 调用前必须初始化变量 调用前无需初始化变量 方法内赋值 可读可写(非强制赋值) 必须赋值(否则编译错误) 典型场景 修改现有变量、交换数据 返回多个结果、解析类操作 示例 Swap(ref a, ref b)
TryParse(..., out result)
(5)注意事项
重载限制:仅因 ref
/out
不同的方法无法重载(编译器视为相同签名)。 禁止场景:异步方法(async
)中不能使用 ref
/out
参数。 替代方案:需返回多个值时,可考虑元组 (int, bool)
或自定义类。
3、params数组参数
(1)params
的核心功能
params
:用于声明可变数量的方法参数,允许在调用方法时传递: 同类型的多个独立参数 一个数组 或不传递任何参数(空数组)
public int Sum ( params int [ ] numbers) { int sum = 0 ; foreach ( int num in numbers) sum += num; return sum;
}
Sum ( 1 , 2 , 3 ) ;
Sum ( new int [ ] { 4 , 5 } ) ;
Sum ( ) ;
(2)关键语法规则与限制
位置要求 params
参数必须是方法参数列表中的最后一个参数。错误示例:void Foo(params int[] nums, string name)
❌(编译错误) 类型限制 必须声明为一维数组(如 int[]
、string[]
),不支持多维数组或集合类型(如 List<T>
)。 与其他修饰符冲突 不可与 ref
、out
、in
关键字同时使用。 错误示例:void Process(ref params int[] values)
❌ 传递规则 若实参为数组 → 按引用传递(修改形参会影响原数组) 若实参为独立值 → 按值传递(生成临时数组副本)
(3)典型应用场景
Console. WriteLine ( JoinWords ( "Hello" , "world" ) ) ; static string JoinWords ( params string [ ] words) => string . Join ( " " , words) ;
public static double Average ( params double [ ] values) { if ( values. Length == 0 ) return 0 ; return values. Sum ( ) / values. Length;
}
void Log ( params object [ ] messages) { foreach ( var msg in messages) File. AppendAllText ( "log.txt" , msg. ToString ( ) + "\n" ) ;
}
(4)注意事项与性能优化
空值处理:当参数为空时,params
数组长度为 0(非 null
),无需额外判空:
if ( numbers. Length == 0 )
性能影响 频繁调用含 params
的方法可能触发堆内存分配(临时数组)。 优化方案:对高频调用的方法提供固定参数的重载版本:
public int Sum ( int a, int b) => a + b;
public int Sum ( params int [ ] numbers) { .. . }
Sum ( 1 , "two" ) ;
(5)对比其他语言的类似特性
特性 C# (params
) Java (...
) Python (*args
) 语法 params int[]
int...
*args
空参数 允许(空数组) 允许(空数组) 允许(空元组) 类型安全 编译时检查 编译时检查 运行时检查 参数位置 必须最后 必须最后 可变位置后需命名参数
4、args命令行参数
(1)基本概念与访问方式
传递机制 命令行参数通过 Main
方法的 string[] args
参数接收,args
是字符串数组。 索引规则:args[0]
对应第一个参数(不包含程序名本身)。 参数读取示例
static void Main ( string [ ] args)
{ Console. WriteLine ( $"参数数量: { args. Length } " ) ; Console. WriteLine ( "参数列表:" ) ; foreach ( string arg in args) { Console. WriteLine ( arg) ; }
}
输入命令 MyApp.exe param1 "param with space" param3
输出:
参数数量: 3
参数列表:
param1
param with space
param3
️(2)不同应用场景的实现
控制台应用程序 WinForms 应用程序 修改 Program.cs
的 Main
方法,将参数传递给窗体:
static void Main ( string [ ] args)
{ Application. Run ( new MainForm ( args) ) ;
}
- 窗体类接收参数:
public MainForm ( string [ ] cmdArgs)
{ InitializeComponent ( ) ; if ( cmdArgs. Length > 0 ) label1. Text = $"首个参数: { cmdArgs[ 0 ] } " ;
}
(3)进阶参数解析方法
自定义解析类 适用于复杂参数(如 -key=value
格式),需自行拆分字符串:
var paramDict = new Dictionary< string , string > ( ) ;
foreach ( string arg in args)
{ string [ ] parts = arg. Split ( '=' ) ; if ( parts. Length == 2 ) paramDict[ parts[ 0 ] ] = parts[ 1 ] ;
}
专用解析库推荐 使用第三方库(如 CommandLineParser
)自动化处理: 支持短选项(-f
)、长选项(--file
)、默认值等高级特性。 简化错误处理与帮助文档生成。
️(4)关键注意事项
参数分隔规则:空格作为默认分隔符,需用双引号包裹含空格的参数(如 "file path"
)。 特殊字符转义:引号或等号需用反斜杠转义(如 \"
或 \=
)。 参数数量限制:系统对参数总长度有限制(Windows 默认为 8191 字符),超出会导致错误。
(5)典型应用场景
MyApp.exe -input= "data.txt" -output= "result.csv"
MyApp.exe --mode= debug --log-level= verbose
5、函数使用场景
(1)输入验证与解析
public bool TryParseInt ( string input, out int value )
{ if ( int . TryParse ( input, out int result) ) { value = result; return true ; } value = 0 ; return false ;
}
(2)递归计算阶乘
public int Factorial ( int n)
{ if ( n == 0 ) return 1 ; return n * Factorial ( n - 1 ) ;
}
6、异常处理
(1)核心处理机制
try-catch-finally
结构 try
:包裹可能引发异常的代码块。catch
:捕获特定类型异常并处理,支持多个 catch
块(按异常派生程度从高到低排列)。上面没捕获的异常传到下面继续捕捉。finally
:无论是否发生异常都会执行,常用于释放资源(如关闭文件、数据库连接)。
try {
}
catch ( DivideByZeroException ex)
{ Console. WriteLine ( $"除零错误: { ex. Message } " ) ;
}
catch ( Exception ex)
{ Console. WriteLine ( $"未知错误: { ex. Message } " ) ;
}
finally {
}
throw
关键字 主动抛出异常:throw new InvalidOperationException("操作无效");
重新抛出异常(保留原始堆栈):throw;
而非 throw ex;
执行流程 异常发生时,try块中后续代码中止,直接跳转至匹配的catch块 finally块即使遇到return也会执行
(2)关键实践原则
资源清理:使用 finally
或 using
语句确保资源释放(如文件流、数据库连接)。
using ( StreamReader sr = new StreamReader ( "file.txt" ) )
{
}
catch ( IOException ex) when ( ex. Message. Contains ( "磁盘已满" ) )
{
}
(3)常见内置异常类
异常类型 触发场景 DivideByZeroException
整数除零操作 NullReferenceException
访问空对象成员 IndexOutOfRangeException
数组索引越界 FileNotFoundException
文件路径不存在 InvalidCastException
无效类型转换 ArgumentNullException
方法参数为 null
(4)自定义异常
继承 System.ApplicationException(推荐)或 System.Exception 包含三个构造函数(无参/字符串参数/含内部异常参数)
public class OrderException : ApplicationException { public OrderException ( ) { } public OrderException ( string msg) : base ( msg) { } public OrderException ( string msg, Exception inner) : base ( msg, inner) { }
}
if ( orderCount < 0 ) { throw new OrderException ( "订单数不能为负!" ) ;
}
(5)关键实践与注意事项
最佳实践 优先用逻辑判断(如 if)预防可预见的错误(如非空校验),而非依赖异常处理 使用 using 语句自动释放资源(替代手动 finally) 记录异常细节:ex.Message(错误信息)、ex.StackTrace(调用堆栈) 避坑指南 ❌ 避免用异常控制正常流程(性能开销大) ❌ 不在 finally 中抛出新异常(会掩盖原始错误) ✅ 重新抛出异常时用 throw; 而非 throw ex;(保留原始堆栈)