40

我正在使用ref并且不清楚“它是像 C/C++ 中的指针还是像 C++ 中的引用?”

为什么我会问你一时想的那么弱的问题?因为,当我阅读 C#/.NET 书籍、msdn 或与 C# 开发人员交谈时,我对以下原因感到困惑:

  • C# 开发人员建议不要ref在函数的参数中使用,e.g. ...(ref Type someObject)对他们来说味道不好,他们建议...(Type someObject),我真的不太清楚这个建议。我听到的原因:最好使用对象的副本,然后将其用作返回值,而不是通过引用等破坏内存......我经常听到关于数据库连接对象的此类解释。就我普通的 C/C++ 经验而言,我真的不明白为什么在 C# 中使用引用是一件坏事?我控制对象的生命及其内存分配/重新分配等...我在书籍和论坛中阅读的只是建议it's bad, because you can corrupt your connection and cause a memory leak by a reference lose,所以我控制对象的生命,我可以手动控制我真正想要的,那为什么不好?
  • 现在阅读不同的书籍并与不同的人交谈,我不清楚是ref指针(*)还是C ++中的引用&?我记得 C/C++ 中的指针总是分配一个大小为void*4 字节的空间(有效大小取决于体系结构),其中托管结构或变量的地址。在 C++ 中,通过传递引用&没有来自堆/堆栈的新分配,并且您在内存空间中使用已经定义的对象,并且没有像普通 C 中那样为外部指针分配内存。那么refC# 中的内容是什么?.NET VM 是否像普通 C/C++ 中的指针一样处理它,并GC为指针分配临时空间,或者它是否像 C++ 中的引用一样工作?做ref仅适用于正确的托管类型或值类型,例如bool, int最好切换unsafe代码并以非托管样式传递指针?
4

6 回答 6

41

在 C# 中,当您看到引用引用类型(即使用class而不是声明的类型struct)时,您基本上总是通过指针处理对象。在 C++ 中,默认情况下所有内容都是值类型,而在 C# 中,默认情况下所有内容都是引用类型。

当您在 C# 参数列表中说“ref”时,您真正说的更像是“指向指针的指针”。您是说,在方法中,您希望在调用您的方法的代码中替换不是对象的内容,而是对对象本身的引用。

除非这是您的意图,否则您应该直接传递引用类型;在 C# 中,传递引用类型很便宜(类似于在 C++ 中传递引用)。

学习/理解 C# 中值类型和引用类型之间的区别。它们是该语言中的一个主要概念,如果您尝试在 C# 领域考虑使用 C++ 对象模型,事情将会非常混乱。

以下是本质上语义等效的程序:

#include <iostream>

class AClass
{
    int anInteger;
public:
    AClass(int integer)
        : anInteger(integer)
    {  }

    int GetInteger() const
    {
        return anInteger;
    }

    void SetInteger(int toSet)
    {
        anInteger = toSet;
    }
};

struct StaticFunctions
{
    // C# doesn't have free functions, so I'll do similar in C++
    // Note that in real code you'd use a free function for this.

    static void FunctionTakingAReference(AClass *item)
    {
        item->SetInteger(4);
    }

    static void FunctionTakingAReferenceToAReference(AClass **item)
    {
        *item = new AClass(1729);
    }
};

int main()
{
    AClass* instanceOne = new AClass(6);
    StaticFunctions::FunctionTakingAReference(instanceOne);
    std::cout << instanceOne->GetInteger() << "\n";

    AClass* instanceTwo;
    StaticFunctions::FunctionTakingAReferenceToAReference(&instanceTwo);
    // Note that operator& behaves similar to the C# keyword "ref" at the call site.
    std::cout << instanceTwo->GetInteger() << "\n";

    // (Of course in real C++ you're using std::shared_ptr and std::unique_ptr instead,
    //  right? :) )
    delete instanceOne;
    delete instanceTwo;
}

对于 C#:

using System;

internal class AClass
{
    public AClass(int integer)
        : Integer(integer)
    {  }

    int Integer { get; set; }
}

internal static class StaticFunctions
{
    public static void FunctionTakingAReference(AClass item)
    {
        item.Integer = 4;
    }

    public static void FunctionTakingAReferenceToAReference(ref AClass item)
    {
        item = new AClass(1729);
    }
}

public static class Program
{
    public static void main()
    {
        AClass instanceOne = new AClass(6);
        StaticFunctions.FunctionTakingAReference(instanceOne);
        Console.WriteLine(instanceOne.Integer);

        AClass instanceTwo  = new AClass(1234); // C# forces me to assign this before
                                                // it can be passed. Use "out" instead of
                                                // "ref" and that requirement goes away.
        StaticFunctions.FunctionTakingAReferenceToAReference(ref instanceTwo);
        Console.WriteLine(instanceTwo.Integer);
    }
}
于 2013-04-25T06:20:12.307 回答
25

C# 中的Aref等价于 C++ 引用:

  • 他们的意图是通过引用
  • 没有空引用
  • 没有未初始化的引用
  • 您不能重新绑定引用
  • 当您拼写引用时,您实际上是在表示引用的变量

一些 C++ 代码:

void foo(int& x)
{
    x = 42;
}
// ...
int answer = 0;
foo(answer);

等效的 C# 代码:

void foo(ref int x)
{
    x = 42;
}
// ...
int answer = 0;
foo(ref answer);
于 2013-04-25T09:16:35.703 回答
2

C# 中的每个引用都是指向堆上对象的指针,作为 C++ 中的指针,C# 的 ref 与 C++ 中的 & 相同

应该避免使用 ref 的原因是,C# 的基本原理是方法不应该更改传入参数的对象,因为对于没有方法源的人可能不知道它是否会导致数据丢失。

String a = "  A  ";
String b = a.Trim();

在这种情况下,我相信 a 仍然完好无损。在数学中,变化应该被看作是一个视觉上告诉b在程序员同意的情况下在这里被改变的赋值。

a = a.Trim();

此代码将修改 a 本身并且编码器知道它。

除非是特殊情况,否则应避免通过分配 ref 来保留这种更改方法。

于 2013-04-25T06:27:23.083 回答
1

C# 没有 C++ 指针的等价物并且适用于引用。ref增加了一层间接性。它使值类型参数成为引用,当与引用类型一起使用时,它使其成为对引用的引用。

简而言之,它允许在方法调用之外对值类型进行任何更改。对于引用类型,它允许将原始引用替换为完全不同的对象(而不仅仅是更改对象内容)。如果您想在方法中重新初始化对象,并且唯一的方法是重新创建它,则可以使用它。虽然我会尽量避免这种方法。

因此,回答您的问题ref就像 C++ 引用参考一样。

编辑

以上对于安全代码是正确的。指针确实存在于不安全的 C# 中,并且在一些非常特殊的情况下使用。

于 2013-04-25T06:56:05.993 回答
1

这似乎是一场处置/事件的噩梦。如果我有一个为其注册事件的对象并通过引用将其传递给函数,然后重新分配该引用,则应调用 dispose 或分配内存,直到程序关闭。如果 dispose 被调用,所有注册到对象事件的东西都将不再被注册,并且它所注册的所有东西都将不再被注册。有人会如何保持这一点?我想如果你不发疯的话,你可以比较内存地址并尝试让事情恢复正常。

于 2015-01-06T00:21:24.053 回答
-1

在c#中,您可以在项目属性中检查运行不安全,然后您可以运行此代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Exercise_01
{
    public struct Coords
    {
        public int X;
        public int Y;
        public override string ToString() => $"({X}, {Y})";
    }
    class Program
    {


        static unsafe void Main(string[] args)
        {
            int n = 0;
            SumCallByRefPointer(1, 2, &n);
            Console.Clear();
            Console.WriteLine("call by refrence {0}",n);

            n = 0;
            SumCallByValue(3, 4, n);
            Console.WriteLine("call by Value {0}", n);

            n = 0;
            SumCallByRef(5, 6, ref n);
            Console.WriteLine("call by refrence {0}", n);

            Pointer();
            Console.ReadLine();
        } 


        private static unsafe void SumCallByRefPointer(int a, int b, int* c)
        {
            *c = a + b;
        }

        private static unsafe void SumCallByValue(int a, int b, int c)
        {
            c = a + b;
        } 

        private static unsafe void SumCallByRef(int a, int b, ref int c)
        {
            c = a + b;
        }


        public static void Pointer()
        {
            unsafe
            {
                Coords coords;
                Coords* p = &coords;
                p->X = 3;
                p->Y = 4;
                Console.WriteLine(p->ToString());  // output: (3, 4)
            }
        }
    }
}
于 2019-08-05T06:24:52.243 回答