C#主函数调用其他函数的正确姿势:从基础到实战避坑指南

1次阅读
没有评论

共计 2455 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

为什么需要从主函数调用其他方法?

想象你正在开发一个控制台程序,需要完成以下任务:

C# 主函数调用其他函数的正确姿势:从基础到实战避坑指南

  1. 读取配置文件
  2. 初始化数据库连接
  3. 启动后台服务

如果所有代码都堆在 Main 方法里,很快就会变成难以维护的 ” 意大利面条代码 ”。通过合理拆分功能到其他方法,代码会变得模块化且易于测试。

三种基本调用方式

1. 静态方法调用

静态方法属于类而非实例,最适合工具类函数。注意命名空间的组织规范:

// Utilities/Logger.cs
namespace MyApp.Utilities
{
    public static class Logger
    {public static void Log(string message)
        {Console.WriteLine($"[{DateTime.Now}] {message}");
        }
    }
}

// Program.cs
using MyApp.Utilities;

Logger.Log("Application started");  // 直接通过类名调用

2. 实例方法调用

需要先创建对象实例,注意对象生命周期管理(特别是实现了 IDisposable 的类型):

public class DataProcessor
{public void ProcessData(string input)
    {// 处理逻辑}
}

// 使用 using 语句管理资源
using var processor = new DataProcessor();
processor.ProcessData("sample");

3. 委托调用(Delegate)

Action(无返回值)和 Func(有返回值)是预定义的委托类型,适合回调场景:

// 定义接受字符串参数的 Action
action<string> callback = msg => Console.WriteLine(msg);

// 在方法中调用委托
void DoWork(Action<string> logger)
{logger("Work started");
    // ...
    logger("Work completed");
}

DoWork(callback);

参数传递与异常处理实战

参数传递的三种方式

// 值传递(默认方式)void UpdateValue(int x) {x = 10;}

// ref 传递(需初始化)void UpdateRef(ref int x) {x = 10;}

// out 传递(无需初始化)bool TryParse(string s, out int result) 
{return int.TryParse(s, out result); 
}

// 使用示例
var num = 5;
UpdateValue(num);      // num 仍为 5
UpdateRef(ref num);    // num 变为 10

if (TryParse("123", out var parsed))
{Console.WriteLine(parsed);  // 输出 123
}

完整的异常处理示例

try
{var riskyValue = int.Parse(Console.ReadLine()!);
    ProcessInput(riskyValue);
}
catch (FormatException ex)
{Logger.Log($"Invalid input: {ex.Message}");
}
catch (OverflowException)
{Logger.Log("Number too large or small");
}
finally
{
    // 清理资源
    database?.Dispose();}

生产环境警示

1. 堆栈溢出风险

递归调用没有终止条件会导致 StackOverflowException,.NET 无法捕获这种错误:

// 危险示例!void RecursiveCall()
{RecursiveCall();  // 无限递归
}

解决方案
– 确保递归有终止条件
– 考虑改用迭代实现
– 使用尾递归优化(C# 不自动支持,需手动转换)

2. 异步方法中的 ConfigureAwait

在库代码中使用 ConfigureAwait(false)避免不必要的上下文切换:

public async Task<string> FetchDataAsync()
{using var client = new HttpClient();
    var response = await client
        .GetAsync("https://api.example.com/data")
        .ConfigureAwait(false);

    return await response.Content.ReadAsStringAsync();}

3. 跨线程同步问题

从非 UI 线程更新 UI 控件会导致异常,WinForms/WPF 中需要 Invoke:

// WPF 示例
void UpdateUI(string text)
{if (!Dispatcher.CheckAccess())
    {Dispatcher.Invoke(() => UpdateUI(text));
        return;
    }

    textBlock.Text = text;
}

思考题

  1. 第三方库隔离:通过适配器模式包装第三方调用,当需要更换库时只需修改适配器实现
  2. 单元测试 Mock:使用接口抽象依赖,通过 Moq 等框架创建模拟对象
// 隔离层示例
public interface IDataService
{string GetData();
}

public class ThirdPartyAdapter : IDataService
{public string GetData()
    {return ThirdPartyLib.AdvancedGet("config");
    }
}

// 测试中使用 Mock
var mock = new Mock<IDataService>();
mock.Setup(x => x.GetData()).Returns("test");

总结要点

  • 静态方法 适合无状态工具函数
  • 实例方法 需要管理对象生命周期
  • 委托 提供灵活的回调机制
  • 生产环境 要特别注意线程安全和资源释放
  • 测试友好 的设计从方法拆分开始

记住:好的方法划分应该像乐高积木——每个零件独立且可组合,而不是像一团理不清的耳机线。

正文完
 0
评论(没有评论)