C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > C# ref与out参数传递

浅析C#中ref与out参数传递的核心区别与实战指南

作者:bugcome_com

这篇文章为大家系统梳理了C#中ref与out参数传递的核心区别与使用场景,ref用于双向传递,调用前必须初始化,方法内可读写,out用于单向输出,方法内必须赋值,调用前无需初始化,下面小编就和大家详细介绍一下吧

在 C# 中,参数传递方式是一个基础却极易产生误解的知识点,尤其是 ref 与 out。

很多初学者会简单地把它们理解为“按引用传递”,却忽略了编译器约束、数据流方向以及与引用类型的真实关系,从而在实际开发中频繁踩坑

本文将从以下三个层面系统梳理 ref 与 out

一、ref 与 out 的核心区别(从“数据流”理解)

ref 与 out 的共同点是:

方法可以直接操作“调用方变量本身”,而不是它的副本。

但二者的设计目标不同,因此编译器施加了不同的约束。

核心差异对照表

对比维度refout
调用前是否必须初始化必须初始化不要求初始化
方法内是否必须赋值是(强制)
数据流方向传入 + 传出仅传出
语义侧重点修改已有值生成新值

本质总结

ref:双向传递

out:单向输出

ref = “我给你一个值,你可以在它的基础上改”

out = “你来负责给我一个结果”

二、基础用法实战验证

未带ref 和 out

 internal class Program
 {
     static void Main(string[] args)
     {
         string a = "123";
         List<int> i = new List<int>() { 1, 3, 12, 12, 1 };
         Person person = new Person() { Id = 1 ,Name = "邓培"};
         aAction(a, i,person);
         Console.WriteLine(a);
         Console.WriteLine(string.Join(" ", i));
         Console.WriteLine(JsonConvert.SerializeObject(person,Formatting.Indented));
         Console.ReadKey();
     }
     static void aAction(string a, List<int> i,Person person)
     {
         a = "123213";
         i = new List<int>() { 1, 21, 312, 312 };
         person.Name = "罗倩";
     }
 }

 public class Person
 {
     public int Id { get; set; }
     public string Name { get; set; }
 }

注意:整体修改值不会发生改变

ref:典型的“读 + 改”场景

class RefDemo
{
    static void Main()
    {
        int num = 10;          // 必须初始化
        Modify(ref num);
        Console.WriteLine(num); // 20
    }

    static void Modify(ref int value)
    {
        value *= 2;           // 既能读,也能写
    }
}

关键点:

out:典型的“只负责输出结果”

class OutDemo
{
    static void Main()
    {
        int result;           // 可不初始化
        CreateValue(out result);
        Console.WriteLine(result); // 100
    }

    static void CreateValue(out int value)
    {
        value = 100;          // 必须赋值,否则编译失败
    }
}

关键点:

三、引用类型的特殊处理:常见误区澄清

很多问题并不来自 ref/out,而是来自对“引用类型传参”的误解

重要前提:默认情况下传的是什么?

C# 默认是“值传递”

对引用类型而言,传递的是引用本身的副本

也就是说:

四、无 ref / out 时的两种典型行为

场景一:修改引用指向 ❌(不生效)

class Demo
{
    static void Main()
    {
        // 初始化原始变量
        string s = "old value";
        List<int> list = new List<int> { 4, 5, 6 };
        
        // 调用方法尝试修改引用指向
        Change(s, list);
        
        // 输出结果:原始变量完全没变化
        Console.WriteLine(s); // 输出:old value
        Console.WriteLine(string.Join(",", list)); // 输出:4,5,6
    }

    static void Change(string s, List<int> list)
    {
        // 修改的是「参数副本」的引用指向
        s = "new value"; // 让参数s的副本指向新字符串
        list = new List<int> { 1, 2, 3 }; // 让参数list的副本指向新List
    }
}

调用后:

原因:

场景二:修改对象内容 ✅(生效)

class Demo
{
    static void Main()
    {
        // 初始化原始Person对象
        Person p = new Person { Id = 1, Name = "旧名字" };
        
        // 调用方法修改对象内容
        Change(p);
        
        // 输出结果:对象内容被修改
        Console.WriteLine(p.Name); // 输出:新名字
    }

    static void Change(Person p)
    {
        // 修改的是「对象内部状态」,而非引用本身
        p.Name = "新名字";
    }
}

class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

原因:

五、string 为什么“像值类型一样”?

string 是引用类型,但它是:

不可变(immutable)对象

using System;

// 示例1:无 ref 时,修改 string 引用指向仅作用于方法内副本
class StringImmutableDemo
{
    static void Main()
    {
        // 1. 初始化原始字符串变量
        string originalStr = "123";
        Console.WriteLine($"【调用方法前】原始变量 originalStr = {originalStr}");
        // 输出:【调用方法前】原始变量 originalStr = 123

        // 2. 调用方法尝试修改字符串
        TryChangeString(originalStr);

        // 3. 调用方法后,原始变量无变化
        Console.WriteLine($"【调用方法后】原始变量 originalStr = {originalStr}");
        // 输出:【调用方法后】原始变量 originalStr = 123
    }

    /// <summary>
    /// 无 ref:修改的是方法内参数副本的引用指向
    /// </summary>
    static void TryChangeString(string strParam)
    {
        // 注意:不是修改原有"123"的内容,而是创建新字符串"456"
        strParam = "456";
        Console.WriteLine($"【方法内部】参数副本 strParam = {strParam}");
        // 输出:【方法内部】参数副本 strParam = 456
    }
}

运行输出

这不是“修改字符串”,而是:

因此:

六、加 ref 后:修改引用指向生效

using System;
using System.Collections.Generic;

// 示例2:加 ref 后,修改 string/List 的引用指向生效
class RefChangeReferenceDemo
{
    static void Main()
    {
        // 1. 初始化原始变量
        string originalStr = "123";
        List<int> originalList = new List<int> { 9, 9 };

        Console.WriteLine("【调用方法前】");
        Console.WriteLine($"原始字符串 originalStr = {originalStr}");
        Console.WriteLine($"原始列表 originalList = [{string.Join(",", originalList)}]");
        // 输出:
        // 原始字符串 originalStr = 123
        // 原始列表 originalList = [9,9]

        // 2. 加 ref 调用方法,修改引用指向
        ChangeReference(ref originalStr, ref originalList);

        Console.WriteLine("\n【调用方法后】");
        Console.WriteLine($"原始字符串 originalStr = {originalStr}");
        Console.WriteLine($"原始列表 originalList = [{string.Join(",", originalList)}]");
        // 输出:
        // 原始字符串 originalStr = 456
        // 原始列表 originalList = [1,2,3]
    }

    /// <summary>
    /// 加 ref:直接操作原始变量的引用指向
    /// </summary>
    static void ChangeReference(ref string strParam, ref List<int> listParam)
    {
        // 直接修改原始字符串变量的指向(新建"456"并让原始变量指向它)
        strParam = "456";
        // 直接修改原始列表变量的指向(新建List并让原始变量指向它)
        listParam = new List<int> { 1, 2, 3 };
    }
}

运行输出

【调用方法前】
原始字符串 originalStr = 123
原始列表 originalList = [9,9]

【调用方法后】
原始字符串 originalStr = 456
原始列表 originalList = [1,2,3]

结果:

本质:

ref 传递的不是“引用的副本”,而是引用变量本身

七、ref 与 out 的典型适用场景

适合使用 ref 的情况

适合使用 out 的情况

bool success = int.TryParse("123", out int value);

八、实践注意事项(非常重要)

ref / out 必须 调用方 + 方法签名同时声明

out 在 C# 7+ 支持内联声明

避免滥用

总结(核心记忆点)

如果你能在脑中始终区分清楚这三点:

值 / 引用 / 引用的副本

那么 ref 与 out 将不再是坑,而是工具。

到此这篇关于浅析C#中ref与out参数传递的核心区别与实战指南的文章就介绍到这了,更多相关C# ref与out参数传递内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文