C#語言入門詳解(18)傳值、輸出、引用、數組、具名、可選參數、擴展方法
- 一、傳值參數
- 1. 值類型
- 2. 引用類型,并且創建對象
- 3. 引用類型,不創建對象
- 二、引用參數
- 1. 值類型
- 2. 引用類型,創建新對象
- 3. 引用類型,不創建新對象
- 三、輸出形參
- 1. 值類型
- 2. 引用類型
- 四、數組參數
- 五、具名參數
- 六、可選參數
- 七、擴展方法
- 總結
內容來自劉鐵猛C#語言入門詳解課程。
參考文檔:CSharp language specification 5.0 中文版
一、傳值參數
1. 值類型
- 聲明時不帶修飾符的形參是值形參。 一個值形參對應于一個局部變量, 只是它的初始值來自該方法調用所提供的相應實參。
當形參是值形參時, 方法調用中的對應實參必須是表達式, 并且它的類型可以隱式轉換為形參的類型。
允許方法將新值賦給值參數。 這樣的賦值只影響由該值形參表示的局部存儲位置, 而不會影響在方法調用時由調用方給出的實參。
namespace ParametersExample
{class Program{static void Main{string[] args)Student stu = new Student()int y= 100;stu.AddOne(y);Console.WriteLine(y);
}class Student{public void AddOne(int x)x = x+ 1;Console.WriteLine(x):}
2. 引用類型,并且創建對象
- 引用參數很少使用,一般情況都是傳進來引用它的值,而不是連接到新對象去(基本只有面試題會考這個)。
- 注:當參數類型為 string 時,在方法內部修改參數的值,對應的是此處創建對象這種情況。
因為 string 是 immutable 的,所以在方法內部對 string 賦值實際是“創建新的 string 實例再賦值”,最終方法外部的 string 并不會改變。 - Object.GetHashCode() 方法,用于獲取當前對象的哈希代碼,引每個對象的 Hash Code 都不一樣,可通過 Hash Code 來區分兩個 Name 相同的 stu 對象。
class Program
{static void Main(string[] args){var stu = new Student() { Name="Tim"};SomeMethod(stu);Console.WriteLine(stu.Name);Console.WriteLine(stu.GetHashCode());}static void SomeMethod(Student stu){//通過new修改指向地址stu = new Student { Name = "Tim" };Console.WriteLine(stu.Name);Console.WriteLine(stu.GetHashCode());}
}class Student
{public string Name { get; set; }
}
3. 引用類型,不創建對象
- 這種通過傳遞進來的參數修改其引用對象的值的情況,在使用中比較少見。因為作為方法,其主要輸出還是靠返回值。我們把這種修改參數所引用對象的值的操作叫做方法的副作用(side-effect),這種副作用平時編程時要盡量避免。
namespace ParametersExample
{class Program{static void Main(string[] args){Student stu = new Student() { Name = "Tim” };UpdateObject(stu);Console.WriteLine["HashCode={0}, Name-{1}", stu.GetHashCode(), stu.Name);}static void UpdateObject(Student stu){stu.Name="Tom";//副作用,side-effectConsole.WriteLine["HashCode-{0}, Name={1}", stu.GetHashCodel), stu.Name);}}
}
二、引用參數
1. 值類型
- 引用形參是用 ref 修飾符聲明的形參。 與值形參不同, 引用形參并不創建新的存儲位置。相反,引用形參表示的存儲位置恰是在方法調用中作為實參給出的那個變量所表示的存儲位置。
- 當形參為引用形參時,方法調用中的對應實參必須由關鍵字 ref 并后接一個與形參類型相同的 variablereference組成。 變量在可以作為引用形參傳遞之前, 必須先明確賦值。
- 在方法內部, 引用形參始終被認為是明確賦值的。
static void Main(string[] args)
{int y = 1;IWantSideEffect(ref y);Console.WriteLine(y);
}static void IWantSideEffect(ref int x)
{x += 100;
}
2. 引用類型,創建新對象
class Program
{static void Main(string[] args){var outterStu = new Student() { Name = "Tim" };Console.WriteLine("HashCode={0}, Name={1}", outterStu.GetHashCode(), outterStu.Name);Console.WriteLine("-----------------");IWantSideEffect(ref outterStu);Console.WriteLine("HashCode={0}, Name={1}", outterStu.GetHashCode(), outterStu.Name);}static void IWantSideEffect(ref Student stu){stu = new Student() { Name = "Tom" };Console.WriteLine("HashCode={0}, Name={1}",stu.GetHashCode(),stu.Name);}
}class Student
{public string Name { get; set; }
}
3. 引用類型,不創建新對象
class Program
{static void Main(string[] args){var outterStu = new Student() { Name = "Tim" };Console.WriteLine("HashCode={0}, Name={1}", outterStu.GetHashCode(), outterStu.Name);Console.WriteLine("-----------------");SomeSideEffect(ref outterStu);Console.WriteLine("HashCode={0}, Name={1}", outterStu.GetHashCode(), outterStu.Name);}static void SomeSideEffect(ref Student stu){stu.Name = "Tom";Console.WriteLine("HashCode={0}, Name={1}", stu.GetHashCode(), stu.Name);}
}class Student
{public string Name { get; set; }
}
三、輸出形參
- 用 out 修飾符聲明的形參是輸出形參。類似于引用形參,輸出形參不創建新的存儲位置。相反,輸出形參表示的存儲位置恰是在該方法調用中作為實參給出的那個變量所表示的存儲位置。
- 當形參為輸出形參時,方法調用中的相應實參必須由關鍵字 out 并后接一個與形參類型相同的 variablereference(第 5.3.3 節) 組成。變量在可以作為輸出形參傳遞之前不一定需要明確賦值,但是在將變量作為輸出形參傳遞的調用之后, 該變量被認為是明確賦值的。
- 在方法內部,與局部變量相同,輸出形參最初被認為是未賦值的,因而必須在使用它的值之前明確賦值。
- 在方法返回之前,該方法的每個輸出形參都必須明確賦值。聲明為分部方法或迭代器的方法不能有輸出形參。
1. 值類型
static void Main(string[] args)
{Console.WriteLine("Please input first number:");var arg1 = Console.ReadLine();double x = 0;if (double.TryParse(arg1, out x) == false){Console.WriteLine("Input error!");return;}Console.WriteLine("Please input second number:");var arg2 = Console.ReadLine();double y = 0;if (double.TryParse(arg2, out y) == false){Console.WriteLine("Input error!");return;}double z = x + y;Console.WriteLine(z);
}
- 實現帶有輸出參數的TryParse:
class Program
{static void Main(string[] args){double x = 0;if(DoubleParser.TryParse("aa",out x)){Console.WriteLine(x);}}
}class DoubleParser
{public static bool TryParse(string input,out double result){try{result = double.Parse(input);return true;}catch{result = 0;return false;}}
}
2. 引用類型
- 引用類型的輸出參數實例
class Program
{static void Main(string[] args){Student stu = null;if(StudentFactory.Create("Tim", 34, out stu)){Console.WriteLine("Student {0}, age is {1}",stu.Name,stu.Age);}}
}class Student
{public int Age { get; set; }public string Name { get; set; }
}class StudentFactory
{public static bool Create(string stuName,int stuAge,out Student result){result = null;if (string.IsNullOrEmpty(stuName)){return false;}if (stuAge < 20 || stuAge > 80){return false;}result = new Student() { Name = stuName, Age = stuAge };return true;}
}
四、數組參數
- params會自動聲明一個數組,將給出值傳入數組并傳給函數。常用的writeLine / Split即為數組函數,先將參數傳入object / char數組,再傳給writeLine / Split函數。
- 參數列表只允許有一個params參數,且必須放在最后的位置。
class Program
{static void Main(string[] args){var myIntArray = new int[] { 1, 2, 3 };int result = CalculateSum(myIntArray);Console.WriteLine(result);}static int CalculateSum(int[] intArray){int sum = 0;foreach (var item in intArray){sum += item;}return sum;}
}
等同于下面代碼
class Program
{static void Main(string[] args){int result = CalculateSum(1, 2, 3);Console.WriteLine(result);}static int CalculateSum(params int[] intArray){int sum = 0;foreach (var item in intArray){sum += item;}return sum;}
}
- WriteLine / Split示例
五、具名參數
- 具名參數能提高代碼的可讀性
- 不再受參數列表順序的限制
class Program
{static void Main(string[] args){PrintInfo("Tim", 34);PrintInfo(age: 24, name:"Wonder");}static void PrintInfo(string name, int age){Console.WriteLine("Helllo {0}, you are {1}.",name,age);}
}
六、可選參數
- 參數因為具有默認值而變得“可選”
- 不推薦使用可選參數
class Program
{static void Main{string[] args){Printinfo();}
}static void Printinfo(string name = "Tim", int age = 34){Console.WriteLine("Hello {O}, you are {1}.", name, age);}
七、擴展方法
當無法對原始類進行修改時,可通過擴展方法為目標數據類型追加方法。擴展方法參數列表里第一個由this修飾,即為該參數數據類型的擴展方法。
- 無擴展方法:
class Program
{static void Main(string[] args){double x = 3.14159;// double 類型本身沒有 Round 方法,只能使用 Math.Round。double y = Math.Round(x, 4);Console.WriteLine(y);}
}
- 有擴展方法:
class Program
{static void Main(string[] args){double x = 3.14159;// double 類型本身沒有 Round 方法,只能使用 Math.Round。double y = x.Round(4);Console.WriteLine(y);}
}static class DoubleExtension
{public static double Round(this double input,int digits){return Math.Round(input, digits);}
}
- LINQ實例
class Program
{static void Main(string[] args){var myList = new List<int>(){ 11, 12, 9, 14, 15 };//bool result = AllGreaterThanTen(myList);// 這里的 All 就是一個擴展方法bool result = myList.All(i => i > 10);Console.WriteLine(result);}static bool AllGreaterThanTen(List<int> intList){foreach (var item in intList){if (item <= 10){return false;}}return true;}
}
List所使用的All方法即為擴展方法,位于Linq命名空間下Enumrable靜態類中。