代码之家  ›  专栏  ›  技术社区  ›  Dale

何时使用in vs ref vs out

c#
  •  355
  • Dale  · 技术社区  · 15 年前

    前几天有人问我什么时候应该使用参数关键字 out 而不是 ref . 当我(我想)理解 裁判 外面的 关键词 asked before )最好的解释是 裁判 = in 外面的 ,我应该经常使用哪些(假设或代码)示例 外面的 而不是 裁判 .

    自从 裁判 更一般,你为什么要用 外面的 ?只是句法上的糖分吗?

    15 回复  |  直到 6 年前
        1
  •  375
  •   peterchen    14 年前

    你应该使用 out 除非你需要 ref .

    当需要将数据编组到另一个流程(可能成本高昂)时,这会产生很大的差异。因此,您希望避免在方法不使用初始值时对其进行封送处理。

    除此之外,它还向声明或调用的读者显示初始值是相关的(并且可能被保留)还是丢弃的。

    作为一个微小的区别,out参数不需要初始化。

    举例 外面的 :

    string a, b;
    person.GetBothNames(out a, out b);
    

    如果getbothnames是一种原子地检索两个值的方法,那么无论a和b是什么,该方法都不会更改行为。如果呼叫转到夏威夷的服务器,那么将初始值从这里复制到夏威夷将浪费带宽。使用ref的类似代码段:

    string a = String.Empty, b = String.Empty;
    person.GetBothNames(ref a, ref b);
    

    可能会混淆读者,因为看起来a和b的初始值是相关的(尽管方法名会指示它们不是相关的)。

    举例 裁判 :

    string name = textbox.Text;
    bool didModify = validator.SuggestValidName(ref name);
    

    这里的初始值与方法有关。

        2
  •  71
  •   Reed Copsey    15 年前

    使用out表示参数未被使用,仅设置。这有助于调用者理解您总是在初始化参数。

    此外,ref和out不仅仅用于值类型。它们还允许您重置引用类型从方法中引用的对象。

        3
  •  36
  •   Adam Robinson    15 年前

    你说得对,从语义上讲, ref 提供“输入”和“输出”功能,而 out 仅提供“输出”功能。有一些事情需要考虑:

    1. 外面的 要求接受参数的方法必须在返回之前的某个时间点为变量赋值。在一些键/值数据存储类(如 Dictionary<K,V> ,您的功能如下 TryGetValue . 此函数需要 外面的 一个参数,用于保存检索到的值。调用方传递值没有意义 进入之内 这个函数,所以 外面的 用于确保在调用后某个值将在变量中,即使它不是“真实”数据(在 TyGET值 没有钥匙的地方)。
    2. 外面的 裁判 处理互操作代码时参数的排列方式不同

    另外,作为一个旁白,重要的是要注意,尽管引用类型和值类型在其值的性质上有所不同, 应用程序中的每个变量都指向存储值的内存位置 ,即使对于引用类型也是如此。碰巧,对于引用类型,该内存位置中包含的值是 另一个 内存位置。当您将值传递给函数(或执行任何其他变量赋值)时,该变量的值将被复制到另一个变量中。对于值类型,这意味着复制类型的整个内容。对于引用类型,这意味着将复制内存位置。不管怎样,它都会创建包含在变量中的数据的副本。它所具有的唯一真正相关性是处理赋值语义;当分配变量或传递值(默认值)时,当对原始(或新)变量进行新赋值时,它不会影响其他变量。对于引用类型,是的,对 实例 两边都有,但这是因为实际变量只是指向另一个内存位置的指针;变量的内容——内存位置——实际上并没有改变。

    通过 裁判 关键字表示两个原始变量 函数参数实际上将指向相同的内存位置。这同样只影响赋值语义。如果一个新值被分配给其中一个变量,那么由于另一个变量指向同一个内存位置,新值将反映在另一个变量上。

        4
  •  25
  •   Lorenz Lo Sauer    12 年前

    它取决于编译上下文(参见下面的示例)。

    out ref 两者都表示通过引用传递的变量,然而 裁判 要求在传递变量之前对其进行初始化,这可能是封送上下文中的一个重要区别(互操作:umanagedTomanagedTransition或反之亦然)

    MSDN warns :

    Do not confuse the concept of passing by reference with the concept of reference types. The two concepts are not the same. A method parameter can be modified by ref regardless of whether it is a value type or a reference type. There is no boxing of a value type when it is passed by reference.

    来自官方的msdn文档:

    The out keyword causes arguments to be passed by reference. This is similar to the ref keyword, except that ref requires that the variable be initialized before being passed

    The ref keyword causes an argument to be passed by reference, not by value. The effect of passing by reference is that any change to the parameter in the method is reflected in the underlying argument variable in the calling method. The value of a reference parameter is always the same as the value of the underlying argument variable.

    我们可以在分配参数时验证out和ref是否确实相同:

    CIL实例 :

    考虑下面的例子

    static class outRefTest{
        public static int myfunc(int x){x=0; return x; }
        public static void myfuncOut(out int x){x=0;}
        public static void myfuncRef(ref int x){x=0;}
        public static void myfuncRefEmpty(ref int x){}
        // Define other methods and classes here
    }
    

    在CIL中, myfuncOut myfuncRef 与预期相同。

    outRefTest.myfunc:
    IL_0000:  nop         
    IL_0001:  ldc.i4.0    
    IL_0002:  starg.s     00 
    IL_0004:  ldarg.0     
    IL_0005:  stloc.0     
    IL_0006:  br.s        IL_0008
    IL_0008:  ldloc.0     
    IL_0009:  ret         
    
    outRefTest.myfuncOut:
    IL_0000:  nop         
    IL_0001:  ldarg.0     
    IL_0002:  ldc.i4.0    
    IL_0003:  stind.i4    
    IL_0004:  ret         
    
    outRefTest.myfuncRef:
    IL_0000:  nop         
    IL_0001:  ldarg.0     
    IL_0002:  ldc.i4.0    
    IL_0003:  stind.i4    
    IL_0004:  ret         
    
    outRefTest.myfuncRefEmpty:
    IL_0000:  nop         
    IL_0001:  ret         
    

    NOP 没有手术, 低密度脂蛋白胆固醇 本地加载, STOLC 堆栈本地, 拉达格 :加载参数, B.S :分支到目标….

    (见: List of CIL instructions )

        5
  •  20
  •   Shivprasad Koirala    9 年前

    下面是我从这个代码项目文章中提取的一些注释 C# Out Vs Ref

    1. 只有当我们期望一个函数或一个方法有多个输出时,才应该使用它。对结构的思考也是一个很好的选择。
    2. ref和out是关键字,它们指示如何将数据从调用者传递到被调用者,反之亦然。
    3. 在REF中,数据双向传递。从呼叫者到被呼叫者,反之亦然。
    4. 输入输出数据从被调用方到调用方只传递一种方式。在这种情况下,如果调用者试图将数据发送给被调用者,它将被忽略/拒绝。

    如果你是一个有视觉效果的人,那么请看这段你的视频,这段视频实际上显示了不同之处。 https://www.youtube.com/watch?v=lYdcY5zulXA

    下图更直观地显示了差异

    C# Out Vs Ref

        6
  •  17
  •   zneak    10 年前

    你需要使用 ref 如果您计划读写参数。你需要使用 out 如果你只打算写。实际上, 外面的 当您需要一个以上的返回值时,或者当您不想使用正常的返回机制进行输出时(但这应该很少见)。

    有一些语言机制可以帮助这些用例。 Ref 参数在传递给方法之前必须已初始化(强调它们是读写的),以及 外面的 在给参数赋值之前,不能读取这些参数,并且保证在方法结束时写入这些参数(强调这些参数是只写的)。违反这些原则会导致编译时错误。

    int x;
    Foo(ref x); // error: x is uninitialized
    
    void Bar(out int x) {}  // error: x was not written to
    

    例如, int.TryParse 返回A bool 接受一个 out int 参数:

    int value;
    if (int.TryParse(numericString, out value))
    {
        /* numericString was parsed into value, now do stuff */
    }
    else
    {
        /* numericString couldn't be parsed */
    }
    

    这是一个很明显的例子,您需要输出两个值:数值结果和转换是否成功。CLR的作者决定选择 外面的 因为他们不在乎 int 可能是以前。

    为了 裁判 ,你可以看看 Interlocked.Increment :

    int x = 4;
    Interlocked.Increment(ref x);
    

    联锁增量 原子地增加 x . 因为你需要阅读 X 为了增加它,这是一种情况 裁判 更合适。你完全在乎什么 X 是在它被传给 Increment .

    在C的下一个版本中,甚至可以在 外面的 参数,更加强调其仅输出的性质:

    if (int.TryParse(numericString, out int value))
    {
        // 'value' exists and was declared in the `if` statement
    }
    else
    {
        // conversion didn't work, 'value' doesn't exist here
    }
    
        7
  •  7
  •   leppie    15 年前

    out 是的更多约束版本 ref .

    在方法体中,需要将 外面的 离开方法前的参数。 也指分配给 外面的 参数被忽略,而 裁判 需要分配它们。

    所以 外面的 允许您执行以下操作:

    int a, b, c = foo(out a, out b);
    

    哪里 裁判 需要分配A和B。

        8
  •  7
  •   Shneor    9 年前

    听起来如何:

    外面的 =仅初始化/填充参数(参数必须为空)返回它 外面的 平原

    裁判 =参考,标准参数(可能有值),但函数可以修改。

        9
  •  6
  •   Ali Jamal    9 年前

    你可以使用 out 上下文关键字在两个上下文中(每个上下文都是指向详细信息的链接),作为参数修饰符或在接口和委托中的泛型类型参数声明中。本主题讨论参数修饰符,但是您可以看到关于泛型类型参数声明的其他主题。

    这个 外面的 关键字使参数通过引用传递。这就像 ref 关键字,除了 裁判 要求在传递变量之前对其进行初始化。使用一个 外面的 参数,方法定义和调用方法都必须显式使用 外面的 关键字。例如: C.*

    class OutExample
    {
        static void Method(out int i)
        {
            i = 44;
        }
        static void Main()
        {
            int value;
            Method(out value);
            // value is now 44
        }
    }
    

    尽管变量作为 外面的 参数在传递之前不必初始化,在方法返回之前,需要调用的方法来分配一个值。

    虽然 裁判 外面的 关键字导致不同的运行时行为,它们在编译时不被视为方法签名的一部分。因此,如果唯一的区别是一个方法 裁判 争论和另一个接受 外面的 争论。例如,以下代码将不会编译: C.*

    class CS0663_Example
    {
        // Compiler error CS0663: "Cannot define overloaded 
        // methods that differ only on ref and out".
        public void SampleMethod(out int i) { }
        public void SampleMethod(ref int i) { }
    }
    

    但是,如果一个方法 裁判 外面的 争论和其他的都没有用,就像这样: C.*

    class OutOverloadExample
    {
        public void SampleMethod(int i) { }
        public void SampleMethod(out int i) { i = 5; }
    }
    

    属性不是变量,因此不能作为 外面的 参数。

    有关传递数组的信息,请参见使用传递数组 裁判 外面的 (C编程指南)。

    你不能用 裁判 外面的 以下方法的关键字:

    Async methods, which you define by using the async modifier.
    
    Iterator methods, which include a yield return or yield break statement.
    

    例子

    声明一个 外面的 方法在希望方法返回多个值时很有用。以下示例使用 外面的 用一个方法调用返回三个变量。注意,第三个参数被指定为空。这使得方法可以选择性地返回值。 C.*

    class OutReturnExample
    {
        static void Method(out int i, out string s1, out string s2)
        {
            i = 44;
            s1 = "I've been returned";
            s2 = null;
        }
        static void Main()
        {
            int value;
            string str1, str2;
            Method(out value, out str1, out str2);
            // value is now 44
            // str1 is now "I've been returned"
            // str2 is (still) null;
        }
    }
    
        10
  •  5
  •   ParmesanCodice    15 年前

    只是为了澄清OP的评论,即在ref和out上的使用是“对方法外部声明的值类型或结构的引用”,该引用已经在错误中建立。

    考虑在StringBuilder上使用ref,它是一种引用类型:

    private void Nullify(StringBuilder sb, string message)
    {
        sb.Append(message);
        sb = null;
    }
    
    // -- snip --
    
    StringBuilder sb = new StringBuilder();
    string message = "Hi Guy";
    Nullify(sb, message);
    System.Console.WriteLine(sb.ToString());
    
    // Output
    // Hi Guy
    

    根据本协议:

    private void Nullify(ref StringBuilder sb, string message)
    {
        sb.Append(message);
        sb = null;
    }
    
    // -- snip --
    
    StringBuilder sb = new StringBuilder();
    string message = "Hi Guy";
    Nullify(ref sb, message);
    System.Console.WriteLine(sb.ToString());
    
    // Output
    // NullReferenceException
    
        11
  •  5
  •   Sina Lotfi    6 年前

    如何使用 in out ref 在C?

    • 所有关键字 C# 具有相同的功能,但有一些 边界 .
    • 在里面 不能用被调用的方法修改参数。
    • 裁判 参数可以修改。
    • 裁判 必须先初始化,然后调用方才能使用它,它可以在方法中读取和更新。
    • 外面的 参数必须由调用方修改。
    • 外面的 必须在方法中初始化参数
    • 传递的变量 作为 在里面 在方法调用中传递参数之前,必须初始化参数。但是,被调用的方法不能赋值或修改参数。

    你不能用 在里面 , 裁判 外面的 以下方法的关键字:

    • 异步方法 ,通过使用 async 修饰语。
    • 迭代器方法 ,其中包括 yield return yield break 语句。
        12
  •  4
  •   vml19    10 年前

    作为引用传递的参数在传递给方法之前必须初始化,而out参数在传递给方法之前不需要初始化。

        13
  •  4
  •   RayLoveless    8 年前

    你为什么要用在外面?

    让其他人知道,当变量从被调用的方法返回时,它将被初始化!

    如上所述: “对于out参数, 调用方法必须在方法返回之前赋值 他说:“我想,我是一个很好的朋友。”

    例子:

    Car car;
    SetUpCar(out car);
    car.drive();  // You know car is initialized.
    
        14
  •  4
  •   CharithJ    8 年前

    基本上都 ref out 用于在方法之间传递对象/值

    out关键字使参数通过引用传递。这类似于ref关键字,只是ref要求在传递变量之前对其进行初始化。

    外面的 :参数未初始化,必须在方法中对其进行初始化

    裁判 :参数已初始化,可以在方法中读取和更新。

    __ref_对于参考类型的用途是什么?

    您可以将给定的引用更改为其他实例。

    你知道吗?

    1. 尽管ref和out关键字会导致不同的运行时行为,但它们在编译时不被视为方法签名的一部分。因此,如果唯一的区别是一个方法接受ref参数,另一个接受out参数,则不能重载方法。

    2. 不能将ref和out关键字用于以下类型的方法:

      • 异步方法,通过使用异步修饰符定义。
      • 迭代器方法,包括yield返回或yield break语句。
    3. 属性不是变量,因此不能作为输出参数传递。

        15
  •  4
  •   Saber    8 年前

    关于C 7的额外说明:
    在C 7中,不需要使用out预先声明变量。这样的代码:

    public void PrintCoordinates(Point p)
    {
      int x, y; // have to "predeclare"
      p.GetCoordinates(out x, out y);
      WriteLine($"({x}, {y})");
    }
    

    可以这样写:

    public void PrintCoordinates(Point p)
    {
      p.GetCoordinates(out int x, out int y);
      WriteLine($"({x}, {y})");
    }
    

    来源: What's new in C# 7.