【问题标题】:using ref with class C#将 ref 与 C# 类一起使用
【发布时间】:2010-11-01 13:16:15
【问题描述】:

我想为我正在制作的课程提供一个特定的链接列表。我希望班级写入该列表(例如通过 .addLast())。

我应该为此使用ref 关键字吗?

我对在 C# 中在何处使用 refout 关键字感到有些困惑,因为所有 都是在堆上动态分配的,而且我们实际上对大多数操作都使用指针。
当然,outref 关键字对于原语和结构是有意义的。

另外,如果我不直接发送列表,而是发送包含列表的类? (它是internal 并且需要),我还需要使用ref 吗?或者如果我在函数之间传递它,例如:

void A(ref LinkedList<int> list){
    B(list);
}

void B(ref LinkedList<int> list){
    _myList = list;
}

【问题讨论】:

    标签: c# pointers reference keyword


    【解决方案1】:

    这是在 C# 中使用 ref 关键字的常见误解。其目的是通过引用传递值或引用类型,并且仅在需要直接引用实际参数而不是参数的副本(无论是值还是引用本身)的特定情况下才需要它.在任何情况下都不要将引用类型按引用传递混淆。

    Jon Skeet 写过 an excellent article 关于 C# 中的参数传递,它比较和对比值类型、引用类型、按值传递、按引用传递 (ref) 和输出参数 (out)。我建议您花一些时间完整阅读本文,您的理解应该会变得更加清晰。

    引用该页面中最重要的部分:

    值参数:

    默认情况下,参数是值 参数。这意味着一个新的 存储位置是为 函数成员中的变量 声明,它开始于 您在 函数成员调用。如果你 改变那个值,那不会改变 涉及的任何变量 调用

    参考参数:

    引用参数不传递 中使用的变量的值 函数成员调用 - 他们使用 变量本身。而不是 创建一个新的存储位置 函数成员中的变量 声明,相同的存储位置 被使用,所以变量的值 在函数成员和值中 参考参数的 是相同的。参考参数需要 ref 修饰符作为两者的一部分 声明和调用 - 那 意味着当你在 通过引用传递一些东西。让我们 看看我们之前的例子,就 将参数更改为 参考参数:

    总结:在阅读了我的回复和 Jon Skeet 的文章后,我希望您会发现在您的问题的上下文中使用 ref 关键字是没有任何必要

    【讨论】:

    • 答案很好,但是却给了我相反的印象。我现在正在阅读 Jon Skeet 的文章,谢谢。
    • 给你相反的印象......你是什么意思,又是怎么回事?
    • 我的印象是按值发送类会调用复制构造函数,因为它是“按值”复制的。没有任何需要使用ref,这就更没有意义了,所以我最终得到了正确的答案。
    • 按值传递类确实复制值。这里的关键是“值”实际上是对对象的引用 - 所以你实际上只是在创建一个指向堆上相同对象(引用类型)的新引用。跨度>
    • 别担心,我会嫉妒你接受另一个答案! ;)
    【解决方案2】:

    对于你正在做的事情,你不需要使用 ref。如果您确实使用 ref 传递了列表,那么您将允许调用者更改您引用的列表,而不仅仅是更改列表的内容。

    【讨论】:

    • 哦,所以复制的值是引用,而不是像 .Clone() 这样的东西。这是有道理的。
    【解决方案3】:

    您需要将 ref 与引用类型一起使用的唯一情况是您要在函数内创建一个新对象。

    示例 #1ref 关键字不是必需的。

    // ...
       List myList = new List();
       PopulateList(myList);
    // ...
    void PopulateList(List AList)
    {
       AList.Add("Hello");
       AList.Add("World");
    }
    

    示例#2ref 关键字是必需的。

    // ...
       List myList;
       PopulateList(ref myList);
    // ...
    void PopulateList(ref List AList)
    {
       AList = new List();
       AList.Add("Hello");
       AList.Add("World");
    }
    

    【讨论】:

    • 示例 2,第 2 行:PopulateList(ref myList)
    • @SpencerRuport 你的解释对我来说是坚如磐石。谢谢
    【解决方案4】:

    我知道这是一个老问题,但在我看来,没有一个答案能直接说明原因。

    在这种情况下,您不需要使用 ref,原因如下。考虑这个函数:

    void Foo(MyClass a1, ref MyClass a2, out MyClass b1, int c1, MyStruct d1, ref MyStruct d2)
    {
    }
    

    现在调用这个函数

    MyClass  a = new MyClass();
    MyClass  b = null
    int      c = 3;
    MyStruct d = new MyStruct();
    
    Foo(a, ref a, b, c, d, ref d);
    

    这是你在函数内部得到的:

    void Foo(MyClass a1, ref MyClass a2, 
             out MyClass b1, 
             int c1, 
             MyStruct d1, ref MyStruct d2)
    {
       a1 is a copy in memory of the pointer to the instantiated class a;
       a2 is the pointer to the instantiated class a;
    
       b1 is the pointer to b, but has the additional check of having to be set within this function - and cannot be used before being set;
    
       c1 is a copy in memory of the variable c;
    
       d1 is a copy in memory of the struct d;
       d2 is the struct d;
    }
    

    要实现的重要事项:

    1. a1 设置为null不会a 设置为null
    2. a2 设置为null 会将a 设置为null
    3. 需要设置b1
    4. 设置c1不会更改c
    5. 设置d1不会更改d
    6. 设置d2 将更改d

    这允许一些像这样的怪异:

    void Foo(MyClass x, ref MyClass y)
    {
        x = null;
        y.Bar("hi");
    }
    

    调用如下:

    MyClass a = new MyClass();
    Foo(a, ref a);
    

    您正在使用一个类,因此您的情况更像是函数调用中的变量a1。这意味着 ref 不是严格要求的。

    Jon Skeet 文章对您的帮助不大,因为他的 IntHolder 示例是 struct 而不是 classStruct 是类似于 int 的值类型,必须以相同的方式处理。

    【讨论】:

      【解决方案5】:

      在您发布的两个 sn-ps 中,无需通过 ref 传递列表。引用 Jon Skeet 的话,对象引用是按值传递的。这意味着,当方法将或可能更改对象引用并且您希望此新引用带回调用方法时,您将希望引用引用类型。例如:

      void methodA(string test)
      {
          test = "Hello World";
      }
      
      void methodB(ref string test)
      {
          test = "Hello World";
      }
      
      void Runner()
      {
          string first= "string";
          methodA(first);
          string second= "string";
          methodB(ref second);
          Console.WriteLine((first == second).ToString()); //this would print false
      }
      

      【讨论】:

        【解决方案6】:

        我正在为像我这样习惯于 C++ 的程序员添加这个答案。

        类、接口、委托和数组是reference types,这意味着它们有一个底层指针。普通函数调用按值复制此指针(引用),而通过引用发送发送对该引用的引用:

        //C# code:
        void Foo(ClassA     input)
        void Bar(ClassA ref input)
        
        //equivalent C++ code:
        void Foo(ClassA*  input)
        void Bar(ClassA*& input)
        

        诸如 int、double 等结构和字符串(字符串是一个例外,但工作方式类似)等原语是在堆上分配的,所以工作方式有点不同:

        //C# code:
        void Foo(StructA     input)
        void Bar(StructA ref input)
        
        //equivalent C++ code:
        void Foo(StructA  input)
        void Bar(StructA& input)
        

        ref关键字需要在方法的声明和调用时都使用,所以很明显它被引用了:

        //C# code:
        void Foobar(ClassB ref input)
        ...
        ClassB instance = new ClassB();
        Foobar(ref instance);
        
        //equivalent C++ code:
        void Foobar(ClassB*& input)
        ...
        ClassB instance* = new ClassB();
        Foobar(instance);
        

        如前所述,请阅读this详细解释。它还解释了字符串。



        有趣的是,通过引用调用与底层指针一起工作,因此我们得到以下代码:

        //C# code:
        void Foo(ClassA input){
            input = input + 3;
        }
        void Bar(ClassA ref input){
            input = input + 3;
        }
        //equivalent C++ code:
        void Foo(ClassA&  input){
            input = input + 3;
        }
        void Bar(ClassA*&  input){
            *input = *input + 3;
        }
        //equivalent pure C code:
        void Fun(ClassA* input){
            *input = *input + 3;
        }
        void Fun(ClassA** input){
            *(*input) = *(*input) + 3;
        }
        

        这是一个粗略的等价物,但它有点真实。

        【讨论】:

          【解决方案7】:

          不,你不需要使用 ref。

          LinkedList 是一个对象,所以它已经是一个引用类型。参数list是对LinkedList对象的引用。

          有关值类型的说明,请参阅此MSDN article。值类型通常是您将使用 refout 关键字的参数。

          您可能还想通过ref 传递引用类型。这将允许您将引用指向另一个对象。

          任何时候你传递一个object o 你实际上是在传递一个对对象的引用。当你传递一个 `ref object o' 时,你传递的是对引用的引用。这允许您修改引用。

          Passing Reference-Type Parameters也可以帮助你理解。

          【讨论】:

          • 这有点误导,因为它暗示 ref 应该只与非引用类型(即值类型)一起使用。事实上,它可以与任何一个一起使用,而且通常是!
          • 这实际上有点误导。 “ref”关键字与是否为引用类型无关。它用于能够更改传入的引用。
          • 感谢您的注释,我将尝试在文本中澄清这一点。
          • @HVS:嗯,这取决于您如何解释该声明。它很模糊,所以范围很大。我可能对“稍微”很友善。事实上,ref 和引用类型的使用是完全独立的,正如我在回答中所解释的那样。
          • 确实,这是极度误导。 “不,你不需要使用 ref。它已经是一个引用类型。”意味着 ref 关键字在与引用类型一起使用时什么都不做,并且永远不应该使用。在我看来这是一个糟糕的答案。
          猜你喜欢
          • 2021-06-07
          • 1970-01-01
          • 2021-08-29
          • 2021-08-21
          • 1970-01-01
          • 2021-09-23
          • 2011-02-08
          • 2013-02-01
          • 2012-09-10
          相关资源
          最近更新 更多