簡單來認識in、ref、out、params的差異

params 指定該參數可以接受可變數目的引數。

in 指定該參數是傳址方式傳遞,但只會由所呼叫的方法讀取。

ref 指定該參數是傳址方式傳遞,且可以由所呼叫的方法讀取或寫入。

out 指定該參數是傳址方式傳遞,且由所呼叫的方法寫入。

Method Parameters - in

in關鍵字會使參數以傳址方式傳遞,但可確保不會修改參數。 所以在傳遞in修飾的參數時,需要經過初始化才可以使用。 它就像 ref 或 out 關鍵字,不同的是 in 引數不能被呼叫的方法修改。

void InArgExample(in int number)
{
    // Uncomment the following line to see error CS8331
    //number = 19;
}

int readonlyArgument = 44;
InArgExample(readonlyArgument);
Console.WriteLine(readonlyArgument);     // value is still 44

上述為官方的範例, 若使用in修飾的參數其方法無法修改變數內容,若修改參數將會出現以下錯誤。

CS8331: Cannot assign to variable ‘in int’ because it is a readonly variable


使用時機

當您新增 in 修飾詞來利用參考傳遞引數時,即表明您的設計目的是利用參考傳遞引數,來避免不必要的複製。

其他應用

另外in還有以下幾種應用

  • 泛型介面和委派的泛型型別參數。
  • foreach 語句。
  • LINQ 查詢運算式中的 from 子句。
  • LINQ 查詢運算式中的 join 子句。

Method Parameters - ref

ref 指定該參數是傳址方式傳遞,且可以由所呼叫的方法讀取或寫入, 另外方法定義和呼叫方法都必須明確使用 ref 關鍵字,官方範例如下:

void Method(ref int refArgument)
{
    refArgument = refArgument + 44;
}

int number = 1;
Method(ref number);//呼叫時也要記得使用ref關鍵字
Console.WriteLine(number);
// Output: 45

使用時機

例如,假設呼叫端傳遞區域變數運算式或陣列元素存取運算式。 接著,呼叫的方法可以取代 ref 參數所參考的物件。 在此情況下,呼叫端的區域變數或陣列專案會在方法傳回時參考新的物件。

Method Parameters - out

out指定該參數是傳址方式傳遞,且由所呼叫的方法寫入,故不用先初始化,不過需要先指派值給被呼叫的方法,方法才能傳回

範例如下:

int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod);     // value is now 44

void OutArgExample(out int number)
{
    number = 44;
}

建議

使用 out 引數來宣告方法是傳回多個值的傳統因應措施。 從 C# 7.0 開始,官方考慮使用 Tuple

Method Parameters - params

使用 params 關鍵字的特性:

  • 可變長度的引數
  • 必須是一維陣列
  • params關鍵字後面不允許任何其他參數
  • 只允許一個 params 關鍵字
  • 如果參數的宣告型 params 別不是一維陣列,就會發生編譯器錯誤

當您使用 params 參數呼叫方法時,您可以傳入:

  • 陣列元素型別的引數清單(以逗號分隔)。
  • 指定之類型的引數陣列。
  • 無引數。 如果不傳送任何引數,params 清單的長度為零。

官方範例如下:

public class MyClass
{
    public static void UseParams(params int[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    public static void UseParams2(params object[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        // You can send a comma-separated list of arguments of the
        // specified type.
        UseParams(1, 2, 3, 4);
        UseParams2(1, 'a', "test");

        // A params parameter accepts zero or more arguments.
        // The following calling statement displays only a blank line.
        UseParams2();

        // An array argument can be passed, as long as the array
        // type matches the parameter type of the method being called.
        int[] myIntArray = { 5, 6, 7, 8, 9 };
        UseParams(myIntArray);

        object[] myObjArray = { 2, 'b', "test", "again" };
        UseParams2(myObjArray);

        // The following call causes a compiler error because the object
        // array cannot be converted into an integer array.
        //UseParams(myObjArray);

        // The following call does not cause an error, but the entire
        // integer array becomes the first element of the params array.
        UseParams2(myIntArray);
    }
}
/*
Output:
    1 2 3 4
    1 a test

    5 6 7 8 9
    2 b test again
    System.Int32[]
*/

差異整理

  1. in、ref的參數需初始化,out不用初始化。
  2. ref、out可以在呼叫時進行參數的寫入、in僅唯讀。
  3. 擴充方法 具有下列限制:

out關鍵字不能用在擴充方法的第一個引數上。 ref當引數不是struct,或是泛型型別不受限制為struct時,無法在擴充方法的第一個引數上使用 關鍵字。 in除非第一個引數是struct,否則無法使用 關鍵字。 in關鍵字不能用於任何泛型型別,即使限制為 struct也一樣。


該篇參考官方Method Parameters