iT邦幫忙

3

C# 如果class本身當參數傳遞是 call by reference , 那前面加上ref有何用呢?

作為IT邦的第一篇文章

就來開這個主題好了

public class ValueModel
{
    public int Value { get; set; }
}

public static void Assign(ValueModel model)
{
    model.Value = 10;
}

public static void Assign(int value)
{
    value = 10;
}

static void Main(string[] args)
{
    var model = new ValueModel();
    Assign(model);
    Console.WriteLine($"ValueModel.Value is {model.Value}");//ValueModel.Value is 10
    
    int value = 0;
    Assign(value);
    Console.WriteLine($"Value is {value}");//Value is 0

}

有點基本功的都能夠知道如果是用class傳入function,在function內修改的話,呼叫端的class也會一併修改

而 ValueType的 int則不會

所以如果希望int也有像reference的效果,加個ref就可以 如下

public static void Assign(ref int value)
{
    value = 10;
}

static void Main(string[] args)
{
    int value = 0;
    Assign(ref value);
    Console.WriteLine($"Value is {value}");//Value is 10
}

那今天的問題來了 如果ref用在ReferenceType上會怎樣?

public static void Assign(ref ValueModel model)
{
    model.Value = 20;
}

static void Main(string[] args)
{
    {
        var model = new ValueModel();
        Assign(model);
        Console.WriteLine($"ValueModel.Value is {model.Value}");//ValueModel.Value is 10
    }
    {
        var model = new ValueModel();
        Assign(ref model);
        Console.WriteLine($"ValueModel.Value is {model.Value}");//ValueModel.Value is 20
    }
}

恩 看起來好像效果一樣? 似乎沒差別

在這個例子裏面是這樣沒錯,因為我們沒有動到關鍵點 我們修改一下Assign的內容

public static void Assign(ValueModel model)
{
    model = new ValueModel();
    model.Value = 10;
}
public static void Assign(ref ValueModel model)
{
    model = new ValueModel();
    model.Value = 20;
}
static void Main(string[] args)
{
    {
        var model = new ValueModel();
        Assign(model);
        Console.WriteLine($"ValueModel.Value is {model.Value}");//ValueModel.Value is 0
    }
    {
        var model = new ValueModel();
        Assign(ref model);
        Console.WriteLine($"ValueModel.Value is {model.Value}");//ValueModel.Value is 20
    }
}

這樣應該可以很容易地看出差異了

想像一下

若是沒有加上ref的時候 其實方法呼叫時會將傳入的物件貼上一個標籤

修改資料是對這個標籤上的物件做修改 而model = new ValueModel(); 其實則是將標籤貼到new出來的物件上

所以去修改的時候 是修改新new出來的物件 而不是呼叫端傳入的

那加上ref呢?

他是將外面呼叫端的標籤傳入 所以model = new ValueModel();是將外面的標籤給貼到裡面new出來的物件上

一開始呼叫端new的物件早就消失不見了
(沒有任何方法可以存取他 因為它唯一的標籤已經被撕掉 然後貼到方法內的物件上了)

我們可以再做個實驗

static void Main(string[] args)
{
    {
        var model = new ValueModel();
        var oldmodel = model;
        Assign(ref model);
        Console.WriteLine($"ValueModel.Value is {model.Value}");//ValueModel.Value is 20
        Console.WriteLine($"oldmodel.Value is {oldmodel.Value}");//oldmodel.Value is 0
    }
}

在呼叫端的model被撕掉標籤以前 我先給它貼上一個oldModel的標籤

就可以清楚看見oldmodel其實根本沒有被修改值

其實也可以用GetHashCode來檢驗是否為同一物件

好了~ 今天分享就此結束!

本人並非本科系出身,也沒有受過什麼專業訓練

都是自己在寫Code過程中的感觸體會

如果一些專業術語使用不當或是觀念不正確,還請海涵指教


1 則留言

3
koro_michael
iT邦新手 4 級 ‧ 2021-03-04 11:08:17

嘗試用記憶體角度來解釋

var model = new ValueModel();

這一行會創造兩個記憶體空間

  1. 第一個記憶體空間為 new ValueModel(),起始記憶體地址假設為0001,內容為 ValueModel
  2. 第二個記憶體空間為 model,起始記憶體地址假設為0002,內容為記憶體地址0001

所以可以通過 model 這個變數來找到 ValueModel

今天如果是普通的呼叫

Assign(model);

public static void Assign(ValueModel _model)
{
    ...
}

程式會另外創造一個記憶體空間 _model,起始記憶體地址假設為0003,內容為 model 的內容,也就是記憶體地址0001

所以在 Method 中操作 _model 會影響到 ValueModel

但如果是創造另一個 ValueModel 賦值給 _model

_model = new ValueModel();

程式會另外創造一個記憶體空間 ValueModel,起始記憶體地址假設為0004

然後將 _model 的內容從 記憶體地址0001 更改成 記憶體地址0004

這樣 _model 就跟一開始的 ValueModel 沒有任何關係了

但是外面的變數 model 的內容還是記憶體地址 0001

======================================================

今天如果是 Ref 的呼叫

Assign(ref model);

public static void Assign(ref ValueModel _model)
{
    ...
}

程式並不會幫 _model 另外創造記憶體空間,直接將 _model 等於 model

換句話說就是 _model 本身的記憶體地址就是 0002

如果再執行一樣的動作

_model = new ValueModel();

程式會另外創造一個記憶體空間 ValueModel,起始記憶體地址假設為0004

然後將 _model 的內容從 記憶體地址0001 更改成 記憶體地址0004

但是 _model 等於外面的變數 model(本身的記憶體地址相同)

所以造成外面的 model 也會一起改變的現象

我要留言

立即登入留言