c# - type - 'ref'和'out'關鍵字有什麼區別?




c# return reference (16)

我正在創建一個函數,我需要傳遞一個對像以便它可以被該函數修改。 有什麼區別:

public void myFunction(ref MyClass someClass)

public void myFunction(out MyClass someClass)

我應該使用哪個,為什麼?


出:

在C#中,一個方法只能返回一個值。 如果您想返回多個值,則可以使用out關鍵字。 out修飾符返回為引用返回值。 最簡單的答案是關鍵字“out”用於從方法中獲取值。

  1. 您不需要在調用函數中初始化該值。
  2. 您必須在調用的函數中分配值,否則編譯器會報告錯誤。

參考:

在C#中,當您傳遞值類型(如int,float,double等)作為參數傳遞給方法參數時,它將按值傳遞。 因此,如果修改參數值,它不會影響方法調用中的參數。 但是如果用“ref”關鍵字標記參數,它將反映在實際變量中。

  1. 在調用函數之前,您需要初始化變量。
  2. 在方法中為ref參數賦值並不是強制性的。 如果您不更改該值,那麼需要將其標記為“ref”?

“貝克”

這是因為第一個將你的字符串引用改為指向“Baker”。 因為您通過ref關鍵字(=>對字符串引用的引用)傳遞它,所以更改引用是可能的。 第二次調用獲取對該字符串的引用的副本。

字符串首先看起來有些特別。 但字符串只是一個引用類,如果你定義

string s = "Able";

那麼s是對包含文本“Able”的字符串類的引用! 另一個賦值給同一個變量的通過

s = "Baker";

不會更改原始字符串,但會創建一個新實例並讓它指向該實例!

您可以使用下面的代碼示例嘗試它:

string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);

你能指望什麼? 你會得到的仍然是“Able”,因為你只需將s中的引用設置為另一個實例,而s2指向原始實例。

編輯:字符串也是不可變的,這意味著根本沒有修改現有字符串實例的方法或屬性(您可以嘗試在文檔中找到一個,但不會放棄任何:-))。 所有的字符串操作方法都返回一個新的字符串 (這就是為什麼你在使用StringBuilder類時經常會獲得更好的性能)


ref是進出

只要滿足您的要求,您應該優先使用。


ref修飾符意味著:

  1. 該值已經設置好了
  2. 該方法可以讀取和修改它。

out修飾符意味著:

  1. 該值沒有設置, 直到它被設置時才能被該方法讀取。
  2. 該方法必須在返回之前設置它。

ref在這裡有很好的解釋,但請記住ref可能會被調用未初始化的對象。

看到這個例子 - 注意s1的範圍

public class Class1
{
    // uninitialized
    private string s1;

    public Class1()
    {
        // no issue here..
        RefEater(ref s1);

        // Error CS0165 Use of unassigned local variable 's2'
        //string s2;
        //RefEater(ref s2);
    }

    private void RefEater(ref string s)
    {

    }
}

Out:返回語句可用於從函數返回一個值。 但是,使用輸出參數,您可以從函數返回兩個值。 輸出參數與參考參數相似,不同之處在於它們將數據從方法中傳出而不是傳入數據。

以下示例說明了這一點:

using System;

namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void getValue(out int x )
      {
         int temp = 5;
         x = temp;
      }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;

         Console.WriteLine("Before method call, value of a : {0}", a);

         /* calling a function to get the value */
         n.getValue(out a);

         Console.WriteLine("After method call, value of a : {0}", a);
         Console.ReadLine();

      }
   }
}

ref:引用參數是對變量的內存位置的引用。 當通過引用傳遞參數時,與值參數不同,不會為這些參數創建新的存儲位置。 參考參數表示與提供給方法的實際參數相同的存儲位置。

在C#中,使用ref關鍵字聲明引用參數。 以下示例演示了這一點:

using System;
namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void swap(ref int x, ref int y)
      {
         int temp;

         temp = x; /* save the value of x */
         x = y;   /* put y into x */
         y = temp; /* put temp into y */
       }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;
         int b = 200;

         Console.WriteLine("Before swap, value of a : {0}", a);
         Console.WriteLine("Before swap, value of b : {0}", b);

         /* calling a function to swap the values */
         n.swap(ref a, ref b);

         Console.WriteLine("After swap, value of a : {0}", a);
         Console.WriteLine("After swap, value of b : {0}", b);

         Console.ReadLine();

      }
   }
}

創作時間:

(1)我們創建調用方法Main()

(2)它創建一個List對象(它是一個引用類型對象)並將其存儲在變量myList

public sealed class Program 
{
    public static Main() 
    {
        List<int> myList = new List<int>();

運行期間:

(3)運行時在#00的堆棧上分配一個內存,足夠寬以存儲一個地址(#00 = myList ,因為變量名實際上只是內存位置的別名)

(4)運行時在內存位置#FF的堆上創建一個列表對象(例如所有這些地址都是)

(5)運行時然後將對象的起始地址#FF存儲在#00(或者以字的形式,將List對象的引用存儲在指針myList

回到創作時間:

(6)然後我們將List對像作為參數myParamList給被調用的方法modifyMyList並為它分配一個新的List對象

List<int> myList = new List<int>();

List<int> newList = ModifyMyList(myList)

public List<int> ModifyMyList(List<int> myParamList){
     myParamList = new List<int>();
     return myParamList;
}

運行期間:

(7)運行時為被調用的方法啟動調用例程,並作為其一部分檢查參數的類型。

(8)在找到引用類型後,它在#04的棧上分配一個內存以混淆參數變量myParamList

(9)然後它存儲值#FF。

(10)運行時在內存位置#004的堆上創建一個列表對象,並用此值替換#04中的#FF(或者取消引用原始List對象,並指向此方法中的新List對象)

#00中的地址未被更改,並保留對myList的引用(或者原始myList指針不受干擾)。

ref關鍵字是一個編譯器指令,用於跳過(8)和(9)的運行時代碼生成,這意味著不會為方法參數分配堆。 它將使用原始的#00指針在#FF處的對像上操作。 如果原始指針未初始化,運行時將停止抱怨,因為該變量未初始化,所以無法繼續

out關鍵字是一個編譯器指令,與(9)和(10)稍微修改的ref相差無幾。 編譯器期望該參數未初始化,並將繼續(8),(4)和(5)在堆上創建對象並將其起始地址存儲在參數變量中。 沒有未初始化的錯誤將被拋出,並且任何以前存儲的參考都將丟失。


下面我展示了一個使用RefOut的例子。 現在,你們都將被清除關於裁判和出局。

在下面提到的例子中,當我評論// myRefObj = new myClass {Name =“ref outside called !!”}; 行,會得到一個錯誤,說“使用未分配的局部變量'myRefObj'” ,但沒有這樣的錯誤輸出

使用地點參考 :當我們使用in參數調用過程時,將使用相同的參數來存儲該過程的輸出。

在哪裡使用Out:當我們正在調用一個沒有參數的過程時,將使用相同的參數來返回該過程中的值。 另請注意輸出

public partial class refAndOutUse : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        myClass myRefObj;
        myRefObj = new myClass { Name = "ref outside called!!  <br/>" };
        myRefFunction(ref myRefObj);
        Response.Write(myRefObj.Name); //ref inside function

        myClass myOutObj;
        myOutFunction(out myOutObj);
        Response.Write(myOutObj.Name); //out inside function
    }

    void myRefFunction(ref myClass refObj)
    {
        refObj.Name = "ref inside function <br/>";
        Response.Write(refObj.Name); //ref inside function
    }
    void myOutFunction(out myClass outObj)
    {
        outObj = new myClass { Name = "out inside function <br/>" }; 
        Response.Write(outObj.Name); //out inside function
    }
}

public class myClass
{
    public string Name { get; set; }
} 

參考:ref關鍵字用於傳遞參數作為參考。 這意味著當該方法中該參數的值發生變化時,它將反映在調用方法中。 在傳遞給被調用的方法之前,使用ref關鍵字傳遞的參數必須在調用方法中初始化。

Out:out關鍵字也用於傳遞像ref關鍵字這樣的參數,但可以傳遞參數而不向其分配任何值。 使用out關鍵字傳遞的參數必須在被調用的方法中初始化,然後再返回到調用方法。

public class Example
{
 public static void Main() 
 {
 int val1 = 0; //must be initialized 
 int val2; //optional

 Example1(ref val1);
 Console.WriteLine(val1); 

 Example2(out val2);
 Console.WriteLine(val2); 
 }

 static void Example1(ref int value) 
 {
 value = 1;
 }
 static void Example2(out int value) 
 {
 value = 2; 
 }
}

/* Output     1     2     

在方法重載時重新引用

ref和out都不能同時用於方法重載。 但是,ref和out在運行時會有不同的處理方式,但在編譯時它們會被視為相同(CLR在創建ref和out時不區分這兩者)。


如果你想將你的參數作為參數傳遞,那麼你應該在將參數傳遞給函數之前初始化它,否則編譯器本身會顯示錯誤。但是在out參數的情況下,你不需要在將對象參數傳遞給方法。您可以在調用方法本身中初始化對象。


對於那些以身作則(如我)來學習的人來說,這就是安東尼科列索夫所說的話 。

我創建了一些關於ref,out和其他的簡單例子來說明這一點。 我沒有介紹最佳實踐,只是用來了解差異的例子。

https://gist.github.com/2upmedia/6d98a57b68d849ee7091


從接受參數的方法來看, refout之間的區別在於C#要求方法必須在返回之前寫入每個out參數,並且除了將其作為out參數傳遞外,不得對此類參數執行任何操作或寫入它,直到它作為一個out參數傳遞給另一個方法或直接寫入。 請注意,其他一些語言不會強加這些要求; 在C#中用out參數聲明的虛擬或接口方法可能會被另一種語言覆蓋,這種語言不會對這些參數施加任何特殊的限制。

從調用者的角度來看,在許多情況下,C#在調用具有out參數的方法時會導致寫入的變量未被首先讀取。 調用以其他語言編寫的方法時,此假設可能不正確。 例如:

struct MyStruct
{
   ...
   myStruct(IDictionary<int, MyStruct> d)
   {
     d.TryGetValue(23, out this);
   }
}

如果myDictionary標識用C#以外的語言編寫的IDictionary<TKey,TValue>實現,即使MyStruct s = new MyStruct(myDictionary); 看起來像一個任務,它可能會保持不變。

請注意,用VB.NET編寫的構造函數與C#不同,不會假定被調用的方法是否會修改任何out參數,並無條件地清除所有字段。 上面提到的奇怪行為不會發生在完全用VB編寫的代碼中,或者完全用C#編寫,但是當用C#編寫的代碼調用VB.NET編寫的方法時可能會出現這種情況。


我會試著解釋一下:

我認為我們理解價值類型是如何運作的? 值類型是(int,long,struct等)。 當您將它們發送到沒有ref命令的函數時,它會復制數據 。 您對該功能中的數據所做的任何操作只會影響副本,而不會影響原始數據。 ref命令發送ACTUAL數據,任何更改都會影響函數外部的數據。

對於令人困惑的部分,請參考參考類型:

讓我們創建一個引用類型:

List<string> someobject = new List<string>()

當您更新某個對象時 ,會創建兩個部分:

  1. 某個對象保存數據的內存塊。
  2. 對該數據塊的引用(指針)。

現在,當您將某個對象發送到沒有引用的方法時,它會復制引用指針,而不是數據。 所以你現在有這個:

(outside method) reference1 => someobject
(inside method)  reference2 => someobject

兩個引用指向同一個對象。 如果使用reference2修改某個對象的屬性,它將影響reference1所指向的相同數據。

 (inside method)  reference2.Add("SomeString");
 (outside method) reference1[0] == "SomeString"   //this is true

如果您將reference2清空或將其指向新數據,它不會影響reference1,也不會影響reference1指向的數據。

(inside method) reference2 = new List<string>();
(outside method) reference1 != null; reference1[0] == "SomeString" //this is true

The references are now pointing like this:
reference2 => new List<string>()
reference1 => someobject

現在當你通過引用一個方法發送某個對象時會發生什麼? 對某個對象實際引用被發送到該方法。 所以你現在只有一個參考數據:

(outside method) reference1 => someobject;
(inside method)  reference1 => someobject;

但是,這是什麼意思? 它的行為與發送某個對象的行為完全相同,除了兩個主要事件:

1)當你在方法內部刪除引用時,它將使方法外部的引用為空。

 (inside method)  reference1 = null;
 (outside method) reference1 == null;  //true

2)現在您可以將引用指向完全不同的數據位置,並且函數外部的引用現在將指向新的數據位置。

 (inside method)  reference1 = new List<string>();
 (outside method) reference1.Count == 0; //this is true

擴展狗,貓的例子。 ref的第二個方法改變了調用者引用的對象。 因此“貓”!

    public static void Foo()
    {
        MyClass myObject = new MyClass();
        myObject.Name = "Dog";
        Bar(myObject);
        Console.WriteLine(myObject.Name); // Writes "Dog". 
        Bar(ref myObject);
        Console.WriteLine(myObject.Name); // Writes "Cat". 
    }

    public static void Bar(MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

    public static void Bar(ref MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

由於您傳入引用類型(類),因此不需要使用ref因為默認情況下只傳遞對實際對象的引用 ,因此您總是更改引用後面的對象。

例:

public void Foo()
{
    MyClass myObject = new MyClass();
    myObject.Name = "Dog";
    Bar(myObject);
    Console.WriteLine(myObject.Name); // Writes "Cat".
}

public void Bar(MyClass someObject)
{
    someObject.Name = "Cat";
}

只要你通過一個類,你不必使用ref如果你想改變你的方法內的對象。


除了以下差異之外, refout行為相似。

  • ref變量必須在使用前初始化。 out變量可以不用分配就可以使用
  • out參數必須被使用它的函數視為未分配的值。 所以,我們可以在調用代碼中使用初始化的參數,但是當函數執行時該值會丟失。






ref