C#.NET in、out、ref详解

简介

C# 中,in、refout 是用于修改方法参数传递方式的关键字,它们决定了参数是按值传递还是按引用传递,以及参数是否必须在传递前初始化。

基本语义对比

修饰符 传递方式 可读写性 必须初始化 调用前必须赋值 典型场景
ref 引用传递 可读可写 需先在调用前初始化 修改调用者变量;传大对象避免拷贝
in 只读引用传递 只读(不能赋值) 需先在调用前初始化 传递大值类型以避免拷贝
out 引用传递 必须在方法体内赋值 调用前可未初始化 返回多个值

示例用法

ref:引用传递,可读可写

csharp 复制代码
void Increment(ref int x)
{
    x++;   // 修改调用者的值
}

int a = 5;
Increment(ref a);
Console.WriteLine(a);  // 输出 6
  • 调用前:a 必须已被赋值;

  • 语义:方法内对参数的任何写操作会直接反映到调用者;

  • 适用:需要在方法中"回写"数据,或对大结构体避免复制(如 ref struct)。

out:输出参数,必须在方法内赋值

csharp 复制代码
bool TryParse(string s, out int result)
{
    if (int.TryParse(s, out var tmp))
    {
        result = tmp;
        return true;
    }
    result = 0;
    return false;
}

int value;
if (TryParse("123", out value))
    Console.WriteLine(value);  // 输出 123
  • 调用前:value 可未初始化;

  • 方法体内:必须为 out 参数赋值(否则编译不通过);

  • 语义:专门用于"输出"数据或多返回值场景;

  • C# 7+ 支持声明式 out 变量:if (int.TryParse(s, out var result)) ...。

in:只读引用传递

csharp 复制代码
public struct BigStruct
{
    public long A, B, C, D;
    public long Sum() => A + B + C + D;
}

void PrintSum(in BigStruct bs)
{
    // bs = new BigStruct();       // 编译错误:不能修改 in 参数
    Console.WriteLine(bs.Sum());
}

var big = new BigStruct { A=1, B=2, C=3, D=4 };
PrintSum(in big);
  • 调用前:参数 big 必须初始化;

  • 方法内:只能读取(编译器禁止赋值或调用会改变其字段的成员);

  • 语义:避免对大值类型做整拷贝,同时保证安全只读;

  • 性能:适用于 16 字节以上的值类型以减少栈/寄存器拷贝开销;

  • 限制:不能与可变成员、属性 setter、ref 局部变量一起使用。

最佳实践

优先使用返回值

csharp 复制代码
// 避免使用 out
(bool success, int result) = TryParseBetter("123");

大型结构体使用 in

csharp 复制代码
void ProcessLargeData(in BigStruct data) { ... }

ref 用于需要修改原始值的情况

csharp 复制代码
void UpdatePosition(ref Vector3 position) { ... }

out 用于需要返回多个值的场景

csharp 复制代码
bool TryGetValue(string key, out object value)

避免引用类型使用 in/ref

csharp 复制代码
// 通常不需要 - 引用类型已经通过引用传递
void ProcessList(ref List<int> items) { ... }

差异对比

内存与性能

  • 传值(无修饰符)会复制值类型(大对象拷贝成本高);

  • ref/out/in 都传地址,避免复制;

  • in 保证只读,编译器可做额外优化。

重载差异

可以同时定义带不同修饰符的方法:

csharp 复制代码
void Foo(int x) { }
void Foo(ref int x) { }
void Foo(in int x) { }
void Foo(out int x) { x = 0; }

调用时必须明确修饰符:Foo(a), Foo(ref a), Foo(in a), Foo(out a)

泛型约束

C# 7.3+ 支持泛型中使用 in/ref 修饰符:

csharp 复制代码
void Process<T>(in T item) where T : unmanaged { ... }

总结

  • ref:双向修改,需初始化,适合状态更新。

  • out:输出参数,无需初始化,方法必须赋值,适合返回多个值。

  • in:只读引用,需初始化,适合大结构体性能优化。

相关推荐
智码看视界9 分钟前
.NET 10 推理大模型TensorSharp 3.3.0 部署实测:纯.NET推理引擎反超llama.cpp 1.5倍,DFlash2提速62%
c#·.net·llama.cpp·.net 10·tensorsharp·本地大模型推理·开源推理引擎
格林威1 小时前
C# 图像异步落盘存储:基于Channel 配合 ArrayPool 实现异步落盘
开发语言·人工智能·数码相机·机器学习·计算机视觉·c#·视觉检测
软件黑马王子1 小时前
11.缓存池优化:窗口布局
开发语言·前端框架·c#
格林威1 小时前
C# 图像使用AVX2指令集:使用OpenCvSharp实现字节图像解压缩速度和map_image算子速度提升
开发语言·图像处理·人工智能·计算机视觉·c#·视觉检测·工业相机
软件黑马王子2 小时前
12.缓存池优化:对象上限
开发语言·前端框架·c#
唐青枫15 小时前
别把日志、权限写进业务方法:C#.NET 动态代理从原理到实战
c#·.net
风云15 小时前
Vane.Dispatch 1.0.0 发布:一个与容器、传输层零耦合的 .NET 服务分发引擎
微服务·mvc·.net·ndf·vane.dispatch
fujisheng66117 小时前
Unity UI 生命周期状态机:处理 Covered、异步竞态与事务回滚
c#·unity3d
leoZ2311 天前
第 8 篇:与 AI 协作的工作流 + 完整案例
前端·人工智能·神经网络·自然语言处理·性能优化·c#·php