【问题标题】:C# ref is it like a pointer in C/C++ or a reference in C++?C# ref 是像 C/C++ 中的指针还是 C++ 中的引用?
【发布时间】:2023-04-05 02:47:01
【问题描述】:

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

我为什么要问你想了一会儿的这么弱的问题? 因为,当我阅读 C#/.NET 书籍、msdn 或与 C# 开发人员交谈时,我会被以下原因弄糊涂:

  • C# 开发人员建议不要在函数的参数中使用refe.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 中那样为外部指针分配内存。那么 @ 是什么987654334@ 在 C# 中? .NET VM 是否像普通 C/C++ 中的指针一样处理它,并且它的 GC 为指针分配临时空间,或者它是否像 C++ 中的引用一样工作? ref 仅适用于托管类型,还是适用于 bool, int 之类的值类型?最好切换 unsafe 代码并以非托管样式传递指针?

【问题讨论】:

  • 有趣的问题。我每天都在 C 中使用指针/引用,并且以前在 C# 中也使用过 ref,但从未真正考虑过详细的相似之处。作为旁注,指针大小当然取决于平台,并不总是如您所说的 4 字节。
  • 由于您来自 C++,您可能最好将 C# 引用类型变量视为 C++ 指针,而将 C# ref 参数视为指向指针的 C++ 指针,至少就 C#“引用”类型而言(即所有类)。对于 C# 值类型(例如结构和整数),它们类似于 C++ 值类型,而 C# 中值类型的 ref 参数类似于 C++ 中的指针。
  • Jon Skeet 写了一篇关于参数传递的好文章,详细描述了不同之处。你可以阅读它here

标签: c# pointers memory-management reference ref


【解决方案1】:

在 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);
    }
}

【讨论】:

  • 感谢您的出色回答 :) 所以您想说,它类似于双指针引用?
  • @Oleg:是的。和不。如果 C# 中的项目实际上是一个值类型(即,被声明为 struct),那么 ref 只是一种间接方式。否则是两个。
  • 值得指出的是,ref(10 次中有 9 次)对类参数没有用 - 但在使用值类型时,ref int x 确实会有所不同。
  • ref int 更多的是一种不同的语义(例如,您可以改变调用者对值是什么的看法)而不是效率,因为如果通过,我会感到惊讶引用比按值传递 32 位整数更快。无论如何,引用可能是 32 位或 64 位值。
  • 我特意说的是ref int。当然,效率问题确实会随着更大的价值类型而改变。而且我不确定“直接使用调用者的整数”与“编译器将指针传递给调用者的整数”有何不同,因为被调用者必须能够find它。
【解决方案2】:

C# 中的ref 等同于 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);

【讨论】:

  • +1 因为它包含一个代码示例,说明值类型在 C# 中的作用。
  • 这个答案不完整 - 关于引用类型的 ref 怎么样?
  • 引用类型上的ref 没有什么特别之处。 C++ 等价物是对指针的引用:T*&amp;
【解决方案3】:

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

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

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

在这种情况下,我相信 a 保持不变。在数学中,变化应该被看作是一个作业,视觉上告诉我们b是在程序员同意的情况下在这里改变的。

a = a.Trim();

此代码将修改 a 本身,并且编码人员知道这一点。

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

【讨论】:

  • 因此,为了防止数据丢失,您可以使用信号量之类的锁柜模型,或者使用类似于 C++ 中具有正确模式的可变对象的东西,如果这是唯一的恐惧原因。跨度>
  • “C# 中的所有内容都是指向堆上对象的指针,就像 C++ 中的指针一样” 这不是真的。 reference 类型是这样,但 value 类型不是这样。
  • 另外,我不同意永远不应该改变传递参数的状态。当然,不可变设计很棒,但大多数 C# API 都不是不可变的。例如,考虑任何采用流的 API。将数据写入流当然需要对其进行变异。
  • @BillyONeal 在特殊情况下用于第三方集成和兼容性,否则请向我展示具有 ref 参数的纯 C# API。
  • @Akash:就 CLR 而言,Out 和 ref 是一回事。无论如何,我上面的观点与 out 或 ref 参数无关。引用类型的普通旧正常传递并不意味着接受此类引用类型的函数不会在传递的引用类型上调用任何变异方法。
【解决方案4】:

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

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

所以回答你的问题ref 就像 C++ 引用参考一样。

编辑

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

【讨论】:

  • 感谢您的链接。当然,你几乎可以在不安全的代码中做任何事情,但这不是 C# 的重点。
  • 指针不一定是“用于互操作的”。说“C# 没有 C++ 指针”是完全错误的。
【解决方案5】:

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

【讨论】:

    【解决方案6】:

    在 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)
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-24
      • 2011-06-04
      • 1970-01-01
      • 2011-05-25
      • 1970-01-01
      • 2020-04-04
      • 2012-05-06
      • 1970-01-01
      相关资源
      最近更新 更多