1. C#函数基础概述
在C#编程语言中,函数是构建程序逻辑的基本单元。作为一门面向对象的现代编程语言,C#中的函数不仅实现了传统的过程式编程范式,还深度融入了面向对象的特性。每个C#函数都是一个独立的代码块,用于执行特定任务,可以接收输入参数并返回处理结果。
函数在C#中通常被称为"方法"(Method),特别是在类上下文中。但严格来说,当函数作为类的成员存在时我们称之为方法,而独立存在的(如在顶级语句中)则称为函数。这种区分在实际开发中并不绝对,大多数C#开发者会交替使用这两个术语。
2. 函数的基本结构
2.1 函数声明语法
一个标准的C#函数声明包含以下核心部分:
csharp复制[访问修饰符] [返回类型] 函数名([参数列表])
{
// 函数体
[return 返回值;]
}
例如,一个简单的加法函数:
csharp复制public int Add(int a, int b)
{
return a + b;
}
2.2 函数组成要素详解
访问修饰符:控制函数的可见范围,常见的有:
public:完全公开private:仅当前类可见(默认值)protected:当前类及其派生类可见internal:同一程序集内可见protected internal:上述两种情况的并集
返回类型:指定函数返回值的数据类型。当函数不返回任何值时,使用void关键字。
参数列表:定义函数接收的输入参数,每个参数需要指定类型和名称,多个参数用逗号分隔。
函数体:包含实际执行逻辑的代码块,用大括号{}包围。
3. 函数参数的高级用法
3.1 参数传递方式
C#支持多种参数传递方式:
-
按值传递:默认方式,传递参数的副本
csharp复制void ModifyValue(int x) { x = 10; } -
按引用传递:使用
ref关键字,传递参数的实际引用csharp复制void ModifyRef(ref int x) { x = 10; } -
输出参数:使用
out关键字,用于从函数返回多个值csharp复制void GetValues(out int x, out int y) { x = 5; y = 10; } -
参数数组:使用
params关键字,允许传递可变数量的参数csharp复制int Sum(params int[] numbers) { /*...*/ }
3.2 可选参数和命名参数
C# 4.0引入了可选参数和命名参数特性:
csharp复制void Register(string name, int age = 18, string country = "China")
{
// ...
}
// 调用方式
Register("张三"); // 使用默认age和country
Register("李四", country: "USA"); // 指定country,age使用默认值
提示:可选参数必须放在参数列表的最后,且必须有默认值。命名参数可以在调用时显式指定参数名,提高代码可读性。
4. 函数的返回机制
4.1 返回值类型
C#函数可以返回任何有效的数据类型,包括:
- 基本类型:
int,string,bool等 - 自定义类型:类、结构体
- 集合类型:
List<T>,Dictionary<K,V>等 - 特殊类型:
void表示无返回值
4.2 多返回值模式
虽然C#函数语法上只支持返回一个值,但可以通过以下方式实现多返回值:
-
使用out参数:
csharp复制bool TryParse(string input, out int result) -
使用元组(Tuple):
csharp复制(int, string) GetPerson() { return (1, "张三"); } -
使用自定义类型:
csharp复制class PersonInfo { public int Id; public string Name; }
4.3 提前返回与void函数
函数可以在任何位置使用return语句提前返回。对于void函数,可以省略return或使用return;提前结束执行。
csharp复制void ProcessData(string data)
{
if (string.IsNullOrEmpty(data))
return; // 提前返回
// 处理数据...
}
5. 特殊函数类型
5.1 构造函数
构造函数是类中特殊的函数,用于初始化对象:
csharp复制public class Person
{
public string Name { get; }
public int Age { get; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
5.2 静态函数
使用static关键字声明的函数属于类而非实例:
csharp复制public class MathUtil
{
public static int Add(int a, int b) => a + b;
}
// 调用方式
int sum = MathUtil.Add(3, 5);
5.3 扩展方法
扩展方法允许为现有类型添加新方法而无需修改原始类型:
csharp复制public static class StringExtensions
{
public static bool IsNullOrWhiteSpace(this string value)
{
return string.IsNullOrEmpty(value) || value.Trim().Length == 0;
}
}
// 使用方式
string test = " ";
bool isEmpty = test.IsNullOrWhiteSpace();
5.4 匿名函数与Lambda表达式
C#支持函数式编程风格的匿名函数:
csharp复制// 匿名方法
Func<int, int> square = delegate(int x) { return x * x; };
// Lambda表达式
Func<int, int> square = x => x * x;
// 实际应用
var numbers = new List<int> { 1, 2, 3 };
var squares = numbers.Select(x => x * x).ToList();
6. 函数重载与覆盖
6.1 函数重载(Overload)
在同一作用域内定义多个同名函数,但参数列表不同:
csharp复制public class Logger
{
public void Log(string message) { /*...*/ }
public void Log(string message, int severity) { /*...*/ }
public void Log(Exception ex) { /*...*/ }
}
6.2 函数覆盖(Override)
派生类中可以覆盖基类的虚函数:
csharp复制public class Shape
{
public virtual void Draw() { /* 默认实现 */ }
}
public class Circle : Shape
{
public override void Draw() { /* 特定实现 */ }
}
7. 异步函数
C# 5.0引入了async/await模式简化异步编程:
csharp复制public async Task<string> DownloadDataAsync(string url)
{
using (var client = new HttpClient())
{
return await client.GetStringAsync(url);
}
}
关键点:
- 异步函数返回
Task或Task<T> - 使用
await等待异步操作完成 - 避免
async void,除非是事件处理器
8. 本地函数
C# 7.0引入了本地函数,允许在方法内部定义函数:
csharp复制public IEnumerable<int> GetOddNumbers(IEnumerable<int> numbers)
{
foreach (var number in numbers)
{
if (IsOdd(number))
yield return number;
}
bool IsOdd(int num) => num % 2 != 0; // 本地函数
}
本地函数可以访问包含它的成员的所有局部变量,非常适合辅助逻辑的封装。
9. 函数式编程特性
C#逐渐融入了更多函数式编程特性:
9.1 高阶函数
接受函数作为参数或返回函数的函数:
csharp复制public Func<int, int> ApplyFactor(int factor)
{
return x => x * factor;
}
// 使用
var doubler = ApplyFactor(2);
Console.WriteLine(doubler(5)); // 输出10
9.2 纯函数
不依赖也不改变外部状态的函数:
csharp复制public static int PureAdd(int a, int b) => a + b;
纯函数更容易测试和维护,是函数式编程的核心概念。
10. 性能优化与最佳实践
10.1 参数传递选择
- 对于小型值类型(int, double等),按值传递即可
- 对于大型结构体,考虑使用
ref传递减少复制开销 - 需要修改原始值时使用
ref - 需要返回多个值时考虑
out或元组
10.2 避免过度重载
虽然函数重载是强大特性,但过多的重载会使API难以理解。通常3-5个重载版本是合理的上限。
10.3 异常处理策略
- 在函数内部处理预期可能发生的异常
- 只将真正意外的异常抛给调用者
- 使用特定的异常类型而非通用的
Exception
csharp复制public double Divide(double a, double b)
{
if (b == 0)
throw new ArgumentException("除数不能为零", nameof(b));
return a / b;
}
10.4 文档注释
使用XML文档注释提高代码可读性:
csharp复制/// <summary>
/// 计算两个数的和
/// </summary>
/// <param name="a">第一个加数</param>
/// <param name="b">第二个加数</param>
/// <returns>两个参数的和</returns>
public int Add(int a, int b) => a + b;
11. 实际应用案例
11.1 数据验证函数
csharp复制public static class Validator
{
public static bool IsValidEmail(string email)
{
if (string.IsNullOrWhiteSpace(email))
return false;
try {
var addr = new System.Net.Mail.MailAddress(email);
return addr.Address == email;
}
catch {
return false;
}
}
public static bool IsStrongPassword(string password)
{
const int minLength = 8;
if (string.IsNullOrWhiteSpace(password) || password.Length < minLength)
return false;
return password.Any(char.IsUpper) &&
password.Any(char.IsLower) &&
password.Any(char.IsDigit);
}
}
11.2 扩展方法实用案例
csharp复制public static class CollectionExtensions
{
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
{
var rng = new Random();
var buffer = source.ToList();
for (int i = 0; i < buffer.Count; i++)
{
int j = rng.Next(i, buffer.Count);
yield return buffer[j];
buffer[j] = buffer[i];
}
}
public static string JoinStrings(this IEnumerable<string> source, string separator)
{
return string.Join(separator, source);
}
}
12. 调试与测试技巧
12.1 单元测试函数
使用xUnit测试框架示例:
csharp复制public class MathTests
{
[Theory]
[InlineData(1, 2, 3)]
[InlineData(-1, 1, 0)]
[InlineData(0, 0, 0)]
public void Add_ShouldReturnCorrectSum(int a, int b, int expected)
{
// Arrange
var math = new MathUtil();
// Act
var actual = math.Add(a, b);
// Assert
Assert.Equal(expected, actual);
}
}
12.2 调试技巧
- 使用条件断点:右键点击断点→条件
- 使用
DebuggerDisplay特性定制调试显示:csharp复制[DebuggerDisplay("{Name} (Age: {Age})")] public class Person { public string Name { get; set; } public int Age { get; set; } } - 使用
Debug.Assert进行运行时检查:csharp复制Debug.Assert(input != null, "输入不能为null");
13. C#函数的新特性
13.1 C# 8.0的静态本地函数
csharp复制public void ProcessData()
{
int sharedState = 0;
static int Helper(int x) // 静态本地函数不能访问sharedState
{
return x * 2;
}
// 使用Helper...
}
13.2 C# 9.0的函数指针
csharp复制unsafe class FunctionPointerExample
{
static int Add(int a, int b) => a + b;
public static void Demo()
{
delegate*<int, int, int> funcPtr = &Add;
int result = funcPtr(3, 4); // 返回7
}
}
13.3 C# 10.0的全局using和文件作用域namespace
简化了函数定义的环境:
csharp复制global using System;
global using System.Collections.Generic;
namespace MyApp;
public class Utilities
{
public static void UsefulFunction() { /*...*/ }
}
14. 性能敏感场景下的函数优化
14.1 内联方法
对于简单函数,JIT编译器会自动内联:
csharp复制[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Max(int a, int b) => a > b ? a : b;
14.2 避免不必要的闭包
Lambda表达式会创建闭包,可能影响性能:
csharp复制// 不佳的实现:每次调用都创建新委托
public void ProcessItems(List<int> items)
{
items.ForEach(i => Console.WriteLine(i));
}
// 更好的实现:重用委托
private static readonly Action<int> WriteItem = i => Console.WriteLine(i);
public void ProcessItems(List<int> items)
{
items.ForEach(WriteItem);
}
14.3 结构体替代类
对于小型、短生命期的数据,使用结构体可以减少堆分配:
csharp复制public readonly struct Point
{
public double X { get; }
public double Y { get; }
public Point(double x, double y) => (X, Y) = (x, y);
public double DistanceTo(Point other)
{
double dx = X - other.X;
double dy = Y - other.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
}
15. 跨语言互操作
15.1 P/Invoke调用原生函数
csharp复制using System.Runtime.InteropServices;
public class NativeMethods
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
}
// 使用
NativeMethods.MessageBox(IntPtr.Zero, "Hello", "Message", 0);
15.2 与动态语言互操作
使用dynamic关键字:
csharp复制public static void CallPythonFunction(dynamic pythonModule)
{
try {
var result = pythonModule.calculate(10, 20);
Console.WriteLine($"Result: {result}");
}
catch (Exception ex) {
Console.WriteLine($"Error calling Python: {ex.Message}");
}
}
16. 函数设计原则
16.1 SOLID原则应用
- 单一职责原则(SRP):每个函数只做一件事
- 开闭原则(OCP):对扩展开放,对修改关闭
- 里氏替换原则(LSP):派生类函数应能替换基类函数
- 接口隔离原则(ISP):定义专门的接口而非通用接口
- 依赖倒置原则(DIP):依赖抽象而非具体实现
16.2 函数粒度控制
- 理想情况下,函数应该能在屏幕上完整显示(约20-30行)
- 过长的函数通常意味着需要分解
- 但也不应过度分解为大量微小函数
16.3 命名规范
- 使用动词或动词短语:
CalculateTotal,ValidateInput - 避免模糊的名称:
ProcessData,HandleStuff - 异步方法以Async后缀结尾:
GetDataAsync
17. 函数与设计模式
17.1 工厂方法模式
csharp复制public interface IProduct { void Use(); }
public class ConcreteProduct : IProduct
{
public void Use() => Console.WriteLine("Using product");
}
public static class ProductFactory
{
public static IProduct CreateProduct(string type)
{
return type switch
{
"standard" => new ConcreteProduct(),
_ => throw new ArgumentException("Invalid product type", nameof(type))
};
}
}
17.2 策略模式
csharp复制public interface ISortStrategy
{
void Sort(List<int> list);
}
public class QuickSort : ISortStrategy { /*...*/ }
public class MergeSort : ISortStrategy { /*...*/ }
public class Sorter
{
private ISortStrategy _strategy;
public Sorter(ISortStrategy strategy) => _strategy = strategy;
public void SetStrategy(ISortStrategy strategy) => _strategy = strategy;
public void Sort(List<int> list) => _strategy.Sort(list);
}
18. 函数式编程实践
18.1 不可变性与纯函数
csharp复制public static class FunctionalExtensions
{
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source) where T : class
{
foreach (var item in source)
{
if (item != null)
yield return item;
}
}
public static TResult Pipe<T, TResult>(this T value, Func<T, TResult> func) => func(value);
}
18.2 模式匹配
C# 7.0+的模式匹配增强:
csharp复制public static string Describe(object obj)
{
return obj switch
{
int i when i > 0 => $"正整数: {i}",
int i => $"整数: {i}",
string s => $"字符串,长度: {s.Length}",
Array arr => $"数组,长度: {arr.Length}",
null => "空引用",
_ => "未知类型"
};
}
19. 函数性能分析
19.1 BenchmarkDotNet基准测试
csharp复制[MemoryDiagnoser]
public class FunctionBenchmarks
{
[Benchmark]
public int TraditionalLoop()
{
var list = Enumerable.Range(0, 1000).ToList();
int sum = 0;
foreach (var item in list)
{
sum += item;
}
return sum;
}
[Benchmark]
public int LinqSum()
{
var list = Enumerable.Range(0, 1000).ToList();
return list.Sum();
}
}
19.2 性能优化技巧
- 避免在循环中创建对象
- 对于频繁调用的小函数,考虑内联
- 使用
Span<T>和Memory<T>减少分配 - 预计算和缓存结果
20. 现代C#函数实践
20.1 记录类型(Record)
C# 9.0引入的记录类型简化了不可变模型:
csharp复制public record Person(string FirstName, string LastName)
{
public string FullName => $"{FirstName} {LastName}";
public Person WithLastName(string newLastName) =>
this with { LastName = newLastName };
}
20.2 模式匹配增强
csharp复制public static decimal CalculateTax(object item) => item switch
{
Book b => b.Price * 0.05m,
Electronics e => e.Price * 0.15m,
Food f when f.IsOrganic => 0m,
Food => 0.1m,
_ => throw new ArgumentException("未知商品类型", nameof(item))
};
20.3 顶级语句
C# 9.0允许省略类定义,直接编写执行代码:
csharp复制// 直接写在Program.cs中
using System;
Console.WriteLine("Hello, World!");
int result = Add(3, 5);
Console.WriteLine($"3 + 5 = {result}");
int Add(int a, int b) => a + b;
这种简化特别适合小型控制台应用程序和脚本场景。
