天天看點

C#學習筆記-方法參數params、ref和in、outparamsrefinout其他

目錄

  • params
    • 用法
  • ref
    • 用法
      • 按引用傳遞參數
      • 引用傳回值
      • 引用局部變量
  • in
    • 用法
      • 特殊注意點
    • 優勢
  • out
    • 用法
    • 版本變遷資訊
  • 其他
    • ref、in和out三者異同
    • 方法重載

C#中ref和out關鍵字的應用以及差別。

官方文檔

編寫安全有效的 C# 代碼

  1. params 指定此參數采用可變數量的參數。
  2. in 指定此參數由引用傳遞,但隻由調用方法讀取。
  3. ref 指定此參數由引用傳遞,可能由調用方法讀取或寫入。
  4. out 指定此參數由引用傳遞,由調用方法寫入。

params

使用 params 關鍵字可以指定采用數目可變的參數的方法參數。

用法

  1. 參數類型必須是一維數組。
  2. 在方法聲明中的 params 關鍵字之後不允許有任何其他參數,并且在方法聲明中隻允許有一個 params 關鍵字。
  3. 果 params 參數的聲明類型不是一維數組,則會發生編譯器錯誤 CS0225。
  4. 使用 params 參數調用方法時,可以傳入:

    (1)數組元素類型的參數的逗号分隔清單;

    (2)指定類型的參數的數組;

    (3)無參數。 如果未發送任何參數,則 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()
    {
        UseParams(1, 2, 3, 4);
        UseParams2(1, 'a', "test");
        UseParams2();
        int[] myIntArray = { 5, 6, 7, 8, 9 };
        UseParams(myIntArray);
        object[] myObjArray = { 2, 'b', "test", "again" };
        UseParams2(myObjArray);
        UseParams2(myIntArray);
    }
}
/*
Output:
    1 2 3 4
    1 a test

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

上面的最後一個樣例中,myIntArray是int數組,也是一個object對象,是以輸出的是System.Int32[],相當于傳入參數的長度是1。

至于 UseParams(myObjArray);會引起編譯錯誤,因為object數組無法轉換為int數組。

ref

在方法的參數清單中使用 ref 關鍵字時,它訓示參數按引用傳遞,而非按值傳遞。 ref 關鍵字讓形參成為實參的别名,這必須是變量。 換而言之,對形參執行的任何操作都是對實參執行的。

ref就是reference(引用)的縮寫,關于引用傳遞和值傳遞就沒有必要說了。

用法

ref 關鍵字訓示按引用傳遞值。 它用在四種不同的上下文中:
  1. 在方法簽名和方法調用中,按引用将參數傳遞給方法;
  2. 在方法簽名中,按引用将值傳回給調用方;
  3. 在成員正文中,訓示引用傳回值是否作為調用方欲修改的引用被存儲在本地;
  4. 在 struct 聲明中,聲明 ref struct 或 readonly ref struct。

按引用傳遞參數

按引用傳遞引用類型使所調用方能夠替換調用方中引用參數引用的對象。 對象的存儲位置按引用參數的值傳遞到方法。

方法定義和調用方法均必須顯式使用 ref 關鍵字:

void Method(ref int refArgument)
{
    refArgument = refArgument + 44;
}
int number = 1;
Method(ref number);
Console.WriteLine(number); // Output: 45
           

屬性不是變量,是方法,不能傳遞 ref 參數。

引用傳回值

引用傳回值(或 ref 傳回值)是由方法按引用向調用方傳回的值。

即是說,調用方可以修改方法所傳回的值,此更改反映在調用方法中的對象的狀态中。

這個用法我感覺好詭異啊。。。

public class Book
{
    public string Author;
    public string Title;
}

public class BookCollection
{
    private Book[] books = { new Book { Title = "Call of the Wild, The", Author = "Jack London" },
                        new Book { Title = "Tale of Two Cities, A", Author = "Charles Dickens" }
                       };
    private Book nobook = null;

    public ref Book GetBookByTitle(string title)
    {
        for (int ctr = 0; ctr < books.Length; ctr++)
        {
            if (title == books[ctr].Title)
                return ref books[ctr];
        }
        return ref nobook;
    }

    public void ListBooks()
    {
        foreach (var book in books)
        {
            Console.WriteLine($"{book.Title}, by {book.Author}");
        }
        Console.WriteLine();
    }
}
           

調用上面的方法

var bc = new BookCollection();
bc.ListBooks();

ref var book = ref bc.GetBookByTitle("Call of the Wild, The");
if (book != null)
    book = new Book { Title = "Republic, The", Author = "Plato" };
bc.ListBooks();
// The example displays the following output:
//       Call of the Wild, The, by Jack London
//       Tale of Two Cities, A, by Charles Dickens
//
//       Republic, The, by Plato
//       Tale of Two Cities, A, by Charles Dickens
           

因為使用了ref,是以這裡的book就是books[0],就是bc.books[0],後面給book重新指派了,bc.books[0]也就改變了值。

如果以下兩個條件都成立,請考慮使用 ref readonly return傳回:
  1. 傳回值是大于 IntPtr.Size 的 struct。
  2. 存儲生存期大于傳回值的方法。
通過傳回 ref readonly 可以儲存複制較大的結構并保留内部資料成員的不變性。

引用局部變量

ref 局部變量用于指代使用 return ref 傳回的值。 無法将 ref 局部變量初始化為非 ref 傳回值。 在某些情況下,按引用通路值可避免潛在的高開銷複制操作,進而提高性能。

in

in 關鍵字會導緻按引用傳遞參數,但確定未修改參數。

添加 in 修飾符,按引用傳遞參數,并聲明設計意圖是為了按引用傳遞參數,避免不必要的複制操作。 你不打算修改用作該參數的對象。

用法

in 參數無法通過調用的方法進行修改。

int readonlyArgument = 44;
InArgExample(readonlyArgument);
Console.WriteLine(readonlyArgument);     // value is still 44
void InArgExample(in int number)
{
    // Uncomment the following line to see error CS8331
    //number = 19;
}
           

由于編譯器可為任何 in 參數建立臨時變量,是以還可指定任何 in 參數的預設值。 以下代碼指定原點(點 0,0,0)為第二個點的預設值:

private static double CalculateDistance2(in Point3D point1, in Point3D point2 = default)
{
    double xDifference = point1.X - point2.X;
    double yDifference = point1.Y - point2.Y;
    double zDifference = point1.Z - point2.Z;
    return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);
}
           

特殊注意點

有以下方法:

static void Method(in int argument)
{
    // implementation removed
}
           
  1. 臨時變量允許将編譯時常數作為 in 參數。
Method(5); // OK, temporary variable created.
Method(5L); // CS1503: no implicit conversion from long to int
           
  1. 存在從實參類型到形參類型的隐式轉換時,臨時變量允許使用實參。
short s = 0;
Method(s); // OK, temporary int created with the value 0
// 直接傳 s ,會新生成一個臨時的 int 變量
Method(in s); // CS1503: cannot convert from in short to in int
// 這裡 in s 的話會把s的引用位址傳過去,顯然不能往int上套
           
  1. 臨時變量允許使用屬性或 in 參數的其他表達式。(沒有看明白。。。)
  2. in關鍵字可以隐式使用。
int i = 42;
Method(i); // passed by readonly reference
Method(in i); // passed by readonly reference, explicitly using `in`
           

如果存在方法重載,in就不能隐式使用了

static void Method(int argument)
{
    // implementation removed
}

static void Method(in int argument)
{
    // implementation removed
}

Method(5); // Calls overload passed by value
Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // Calls overload passed by value.
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // Calls overload passed by value
Method(in i); // passed by readonly reference, explicitly using `in`
           

優勢

定義使用 in 參數的方法是一項潛在的性能優化。

某些 struct 類型參數可能很大,在緊湊的循環或關鍵代碼路徑中調用方法時,複制這些結構的成本就很高。

方法聲明 in 參數以指定參數可能按引用安全傳遞,因為所調用的方法不修改該參數的狀态, 按引用傳遞這些參數可以避免(可能産生的)高昂的複制成本。

C#學習筆記-方法參數params、ref和in、outparamsrefinout其他
【注】為了簡化操作,前面的代碼将 int 用作參數類型。 因為大多數新式計算機中的引用都比 int 大,是以将單個 int 作為隻讀引用傳遞沒有任何好處。

out

與 ref 關鍵字相似,隻不過 ref 要求在傳遞之前初始化變量;

也類似于 in 關鍵字,隻不過 in 不允許通過調用方法來修改參數值。

用法

不需要初始化,但是在方法傳回前必須指派,方法定義和調用方法均必須顯式使用 out 關鍵字。

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

void OutArgExample(out int number)
{
    number = 44; // 這句話必須存在,否則報錯
}
           
C#學習筆記-方法參數params、ref和in、outparamsrefinout其他

下面這樣也不行

C#學習筆記-方法參數params、ref和in、outparamsrefinout其他

out 關鍵字也可與泛型類型參數結合使用,以指定該類型參數是協變參數。

屬性不是變量,是以不能作為 out 參數傳遞。

版本變遷資訊

在 C# 6 及更早版本中,必須先在單獨的語句中聲明變量,然後才能将其作為 out 參數傳遞。

string numberAsString = "1640";
int number;
if (Int32.TryParse(numberAsString, out number))
    Console.WriteLine($"Converted '{numberAsString}' to {number}");
else
    Console.WriteLine($"Unable to convert '{numberAsString}'");
// The example displays the following output:
//       Converted '1640' to 1640
           

從 C# 7.0 開始,可以在方法調用的參數清單而不是單獨的變量聲明中聲明 out 變量。

string numberAsString = "1640";
if (Int32.TryParse(numberAsString, out int number))
    Console.WriteLine($"Converted '{numberAsString}' to {number}");
else
    Console.WriteLine($"Unable to convert '{numberAsString}'");
// The example displays the following output:
//       Converted '1640' to 1640
           

其他

ref、in和out三者異同

  1. 傳遞到 ref 或 in 形參的實參必須先經過初始化,然後才能傳遞。 out 形參在傳遞之前,不需要顯式初始化該形參的實參。
  2. in 參數無法通過調用的方法進行修改;out 參數必須由調用的方法進行修改,這些修改在調用上下文中是可觀察的;而 ref 參數是可以修改的。
  3. 在其他要求簽名比對的情況下(如隐藏或重寫),in、ref 和 out 是簽名的一部分,互相之間不比對。
  4. 不能将 ref、in 和 out 關鍵字用于以下幾種方法:

    (1)異步方法,通過使用 async 修飾符定義;

    (2)疊代器方法,包括 yield return 或 yield break 語句。

  5. 擴充方法還限制了對以下這些關鍵字的使用:

    (1)不能對擴充方法的第一個參數使用 out 關鍵字;

    (2)當參數不是結構或是不被限制為結構的泛型類型時,不能對擴充方法的第一個參數使用 ref 關鍵字;

    (3)除非第一個參數是結構,否則不能使用 in 關鍵字。 即使限制為結構,也不能對任何泛型類型使用 in 關鍵字。

方法重載

類的成員不能具有僅在 ref、in 或 out 方面不同的簽名。 如果類型的兩個成員之間的唯一差別在于其中一個具有 ref 參數,而另一個具有 out 或 in 參數,則會發生編譯器錯誤。
class CS0663_Example
{
    // Compiler error CS0663: "Cannot define overloaded
    // methods that differ only on ref and out".
    public void SampleMethod(out int i) { }
    public void SampleMethod(ref int i) { }
}
           
但是,當一個方法具有 ref、in 或 out 參數,另一個方法具有值傳遞的參數時,則可以重載方法,如下面的示例所示。
class RefOverloadExample
{
    public void SampleMethod(int i) { }
    public void SampleMethod(ref int i) { }
}
           
c#