【问题标题】:Passing by ref?通过 ref 传递?
【发布时间】:2009-07-20 05:38:01
【问题描述】:

我仍然对通过 ref 感​​到困惑。

如果我有一个 Cache 对象,我希望它可以被多个对象访问/可用,并且我使用构造函数注入来注入它。我希望它影响我创建的单个缓存对象。例如。

public class Cache {

   public void Remove(string fileToRemove) {
      ...
   }
}

public class ObjectLoader {

   private Cache _Cache;

   public ObjectLoader(Cache cache) {

   }

   public RemoveFromCacheFIleThatHasBeenDeletedOrSimilarOperation(string filename) {
      _Cache.Remove(fileName);
   }
}

当我将 Cache 传递给 ObjectLoader 构造函数时,我应该使用 ref 吗?

【问题讨论】:

    标签: c# .net


    【解决方案1】:

    不,在这种情况下您不需要使用 ref 关键字。

    Cache 是一个类,它是一个引用类型。将引用传递给方法时,会将引用的副本(而不是对象本身)放入您的参数中。方法的内部和外部的两个引用都指向堆上的同一个对象,使用一个对象对对象字段的修改将反映在另一个对象中。

    将 ref 添加到您的方法调用会传递原始引用。这在您要重新分配(即通过调用new)引用从调用方法中指向的位置的情况下很有用。

    【讨论】:

      【解决方案2】:

      当您需要修改引用所指向的内容时,请使用“ref”关键字。当您将引用类型传递给方法时,它按值传递的,但该值是传递给方法的那个引用的副本。这意味着您可以更改引用对象的一般状态(即属性/字段),但如果您尝试更改引用指向的内容,只会影响副本。

      例如,给定这个方法……

      private void Foo( MyClass obj )
      {
          obj = new MyClass( );
          obj.SomeProperty = true;
      }
      

      我们可以传入参数,然后看看是否受到影响:

      MyClass test = new MyClass( );
      test.SomeProperty = false;
      Foo( test );
      Console.WriteLine( test.SomeProperty );  // prints "False"
      

      现在,如果我们使用 'ref' 关键字定义方法...

      private void Foo( ref MyClass obj )
      {
          obj = new MyClass( );
          obj.SomeProperty = true;
      }
      

      输出将是“True”,因为实际引用被传递给方法,而不是副本。我们在函数中更改了该引用指向的内容,并且我们看到了这些更改的效果。

      当你省略'ref'关键字时,你只是在堆上创建一个指向对象的新指针。如果你改变一个指针,你不会改变另一个。

      ...

      所以,回答你的问题;不,您不需要使用 'ref' 关键字来更改单个 Cache 对象在传递给方法时的状态。

      【讨论】:

      • 我不同意 :) 但是对于 c/c++ 人来说,*(或 &)和 ** 之间的区别是 :)
      • nitpick:第二个示例 - 您正在访问静态属性。
      【解决方案3】:

      我想您想知道将创建多少个Cache 对象的副本。您只希望一个副本由多个客户端对象共享。嗯,在 C# 中有一条非常简单的规则,您可以记住,只要您想知道将创建多少个单独的对象副本。

      如果对象的类型被声明为 class 关键字,然后有 只有一种方法可以创建一个新的实例 它:使用 new 关键字。

      对此有一些小例外:您可以调用创建对象的 BCL 方法,但关键是它是显式的。你必须特别要求它发生。该语言不会自动复制class 对象。

      因此,在您的示例中,您有一个名为 Cacheclass,因此您确定可以随意传递 Cache 类型的变量,并且没有更多 Cache 的副本将被创建。分配有该对象的所有变量都将“指向”同一个原始对象。这是因为Cache 变量不存储对象本身,而只存储Cache 对象在内存中的位置。

      将此与声明 struct 类型而不是 class 的情况进行对比。现在,当您声明该类型的变量时,变量本身必须足够大以存储在struct 中声明的所有数据。每个变量都是一个单独的副本。每个参数都是一个单独的副本。

      您可以通过添加 ref 关键字来覆盖它,但在大多数程序中它是一个非常不寻常的关键字。 out 关键字更常见,最好将其视为一种为方法提供多个返回值的方法。

      refclass 类型的变量有什么影响?在您的示例中:

      public ObjectLoader(Cache cache) {
          // do stuff with cache (store it?)
      }
      

      我可以像这样构造两个对象加载器:

      Cache c = new Cache();
      ObjectLoader a = new ObjectLoader(c), 
      ObjectLoader b = new ObjectLoader(c);
      

      我们刚刚创建了多少对象?只需计算new 关键字。现在,假设我们添加了 ref 关键字:

      public ObjectLoader(ref Cache cache) {
      
          _cache = cache; // store        
      
          // do something very odd!
          cache = new Cache();
      }
      

      隐藏在该构造函数中,我创建了另一个缓存,并将其存储在我传递的参数中。因为是ref 参数,所以我影响了调用者的变量!所以在调用代码中:

      Cache c = new Cache();
      ObjectLoader a = new ObjectLoader(ref c), 
      ObjectLoader b = new ObjectLoader(ref c);
      

      现在我们有五个new 的用法:三个在上面的sn-p 中,加上两个对修改后的ObjectLoader 构造函数的调用。每次调用ObjectLoader 的构造函数时,我们都会传递c。我们必须输入ref 关键字,这是一件非常好的事情,因为它让阅读代码的人知道发生了一些奇怪的事情。在ObjectLoader 的构造函数返回后,变量c 指向不同的Cache。所以bObjectLoader 最终存储了一个指向不同Cachea 的指针!

      不用说,对于代码来说,这将是一个相当混乱的模式。如果我们不必将ref 关键字放在调用站点上,那就更糟了!

      【讨论】:

        【解决方案4】:

        在 .NET 框架中,即使您在函数参数中声明对象是按值传递的,对象也会自动通过引用传递。

        这是因为对象本身是一个引用类型,所以你可以修改对象的成员,即使你不能替换对象本身。

        http://msdn.microsoft.com/en-us/library/aa903253(VS.71).aspx

        【讨论】:

        • 对象是按值传递的,就像其他一切一样。与对象(读取:引用类型)的区别在于 REFERENCE 是按值传递的。
        • 所有参数在 C# 中都是按值传递的,除非您明确使用 ref 或 out。但是,在引用类型的情况下,复制的引用仍然指向同一个实例,因此您可以修改实例,但不能修改引用本身。
        猜你喜欢
        • 2011-02-03
        • 1970-01-01
        • 2012-09-16
        • 1970-01-01
        • 1970-01-01
        • 2012-06-28
        • 2011-04-29
        • 2011-03-25
        • 2012-07-29
        相关资源
        最近更新 更多