文章目錄
- 一、字段、屬性、索引器、常量
- 1.字段
- 2.屬性
- 2.1 什么是屬性
- 2.2 屬性的聲明
- 2.3 屬性與字段的關系
- 3 索引器
- 4. 常量
- 二、傳值 輸出 引用 數組 具名 可選參數,擴展方法
- 2.1 傳值參數
- 2.1.1 值類型 傳參
- 2.1.2 引用類型 傳參
- 2.2 引用參數
- 2.2.1 引用參數-值類型 傳參
- 2.2.2 引用參數 - 引用類型 傳參
- 2.3 輸出參數
- 2.3.1 輸出參數-值類型
- 2.3.2 輸出參數-引用類型
- 2.4 數組參數 (params)
- 2.5 具名參數
- 2.6 可選參數
- 2.7 擴展方法(this參數)
- 擴展方法
- LINQ 方法
- 2.8 各種參數的使用場景總結
一、字段、屬性、索引器、常量
1.字段
-
什么是字段
- 字段(field)是一種表示與對象或類型(類與結構體)關聯的變量"字段是類型的成員,舊稱“成員變量
- 與對象關聯的字段亦稱“實例字段”
- 與類型關聯的字段稱為“靜態字段””,由static修飾
-
字段的聲明
- 參見C#語言定義文檔
- 盡管字段聲明帶有分號但它不是語句
- 字段的名字一定是名詞
-
字段的初始值
- 無顯式初始化時,字段獲得其類型的默認值,所以字段“永遠都不會未被初始化”
- 實例字段初始化的時機–對象創建時
- 靜態字段初始化的時機–類型被加載(load )時
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace ConsoleApplication1 {class Program{static void Main(string[] args){//實例化一個學生Student student = new Student();student.Age = 10;student.Score = 100;Console.WriteLine(student.Score);//實例化100個學生,求平均年齡和平均分List<Student> studentList = new List<Student>();for (int i = 0; i < 100; i++) { Student stu = new Student();stu.Age = 25;stu.Score = i+10;studentList.Add(stu);}int totalAge = 0;int totalScore = 0;foreach (var stu in studentList){totalAge += stu.Age;totalScore += stu.Score;}Student.AverageAge = totalAge/Student.Amount;Student.AverageScore = totalScore/Student.Amount;Student.ReportAverageScore();Student.ReportAverageAge();}}class Student{public int Age;public int Score;// 字段初始化器public int AgeInit = 30;//平均年齡public static int AverageAge;//平均分public static int AverageScore;//靜態字段的初始化器public static int Amount = 0;public Student(){Student.Amount++;}public static void ReportAverageAge(){Console.WriteLine(Student.AverageAge);}public static void ReportAverageScore(){Console.WriteLine(Student.AverageScore);}public void ReportAmount(int score){Console.WriteLine(Student.Amount);}} }
-
只讀字段
- 實例只讀字段
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace ConsoleApplication1 {class Program{static void Main(string[] args){StudentOne studentOne = new StudentOne(35);Console.WriteLine( studentOne.ID);//錯誤:給只讀字段賦值將會報錯studentOne.ID = 100;}}class StudentOne{//學生id設置為只讀字段public readonly int ID;public string Name;public int Age;//只讀字段實例化構造器public StudentOne(int Id){//只有在實例化的時候才能賦值this.ID = Id;}} }
- 靜態只讀字段
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace ConsoleApplication1 {class Program{static void Main(string[] args){Console.WriteLine(Brush.DefaultColor.Red);Console.WriteLine(Brush.DefaultColor.Green);}}struct Color{public int Red;public int Green;public int Blue;}class Brush{//聲明一個靜態只讀字段// public static readonly Color DefaultColor = new Color() { Red = 0, Green = 0, Blue = 0 };//靜態只讀字段構造器public static readonly Color DefaultColor;static Brush(){Brush.DefaultColor = new Color() { Red = 0, Green = 0, Blue = 0 };}} }
2.屬性
2.1 什么是屬性
- 屬性(property)是一種用于訪問對象或類型的特征的成員,特征反映了狀態
- 屬性是字段的自然擴展,
- 從命名上看,field更偏向于實例對象在內存中的布局,property更偏向于反映現實世界對象的特征
- 對外:暴露數據,數據可以是存儲在字段里的,也可以是動態計算出來的
- 對內:保護字段不被非法值“污染”屬性由Get/Set方法對進化而來
- 又一個“語法糖”–屬性背后的秘密
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace PropertyExample{class PropertyExample{static void Main(string[] args){//字段的定義使用try{Student student = new Student();student.setAge(30);Student student2 = new Student();student2.setAge(50);Student student1 = new Student();student1.setAge(80);int avgAge = (student.getAge() + student2.getAge() + student1.getAge()) / 3;Console.WriteLine(avgAge);}catch (Exception ex){Console.WriteLine(ex.Message);}//屬性的使用try{Student student = new Student();student.Age = 25;Student student2 = new Student();student2.Age = 40;Student student1 = new Student();student1.Age = 50;int avgAge = (student.Age + student2.Age + student1.Age) / 3;Console.WriteLine(avgAge);}catch (Exception ex){Console.WriteLine(ex.Message);}}}class Student{private int age;//原始的字段獲取和賦值public int getAge(){return this.age;}public void setAge(int age){if (age > 100 || age < 0){throw new Exception("Age has Error");}else{this.age = age;}}//演變成屬性public int Age{get{return this.age;}set{if (value >= 0 && value < 120){this.age = value;}else{throw new Exception("Age is Error");}}}}}
反編譯分析:
輸入: ildasm
, 打開反編譯器
打開文件,找到對應的dll文件打開,如下:
2.2 屬性的聲明
- 完整聲明–后臺(back)成員變量與訪問器(注意使用code snippet和refactor工具)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PropertyExample
{class PropertyExample{static void Main(string[] args){//實例屬性try{Student student = new Student();student.Age = 25;Console.WriteLine(student.Age );}catch (Exception ex){Console.WriteLine(ex.Message);}//靜態屬性try{Student.Amount = 80;Console.WriteLine(Student.Amount);}catch (Exception ex){Console.WriteLine(ex.Message);}}}class Student{//實例屬性public int Age{get{return this.age;}set{if (value >= 0 && value < 120){this.age = value;}else{throw new Exception("Age is Error");}}}//靜態屬性private static int amount;public static int Amount{get { return amount; }set{if (value > 0){amount = value;}else{throw new Exception("amount is error");}}}}
}
- 簡略聲明–只有訪問器(查看IL代碼 )
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PropertyExample
{class PropertyExample{static void Main(string[] args){Student student3 = new Student();student3.ageInit = 50;Console.WriteLine(student3.ageInit);}}class Student{public int ageInit { get; set; }}
}
- 動態計算值的屬性
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PropertyExample
{class PropertyExample{static void Main(string[] args){Student student5 = new Student();student5.Age = 50;Console.WriteLine( student5.CanWork);}}class Student{//年齡屬性public int Age{get{return this.age;}set{if (value >= 0 && value < 120){this.age = value;}else{throw new Exception("Age is Error");}}}//能否工作屬性public bool CanWork{get{if (this.age > 18){return true;}else{return false;}}}}
}
-
注意實例屬性和靜態屬性
-
屬性的名字一定是名詞
-
只讀屬性–只有getter沒有setter
- 盡管語法上正確,幾乎沒有人使用“只寫屬性”,因為屬性的主要目的是通過向外暴露數據而表示對象/類型的狀態
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PropertyExample
{class PropertyExample{static void Main(string[] args){//ageInit 是只讀屬性,不能賦值Student student3 = new Student();Console.WriteLine(student3.ageInit);//score 屬性不允許外部賦值,只能內部賦值Student student4 = new Student();student4.setScore();Console.WriteLine(student4.score);}}class Student{//只讀屬性,沒有set方法public int ageInit { get; }//有set方法,但是是私有的,只能內部進行賦值public int score { get;private set;}// 內部賦值public void setScore(){this.score = 100;}}
}
2.3 屬性與字段的關系
- 一般情況下,它們都用于表示實體(對象或類型)的狀態
- 屬性大多數情況下是字段的包裝器(wrapper)
- 建議:永遠使用屬性(而不是字段)來暴露數據,即字段永遠都是private或protected的
3 索引器
- 什么是索引器
- 索引器 (indexer) 是這樣一種成員:它使對象能夠用與數組相同的方式(即使用下標)進行索引
- 索引器的聲明
- 參見C#語言定義文檔
- 注意:沒有靜態索引器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PropertyExample
{class PropertyExample{static void Main(string[] args){Studentcj student6 = new Studentcj();var aa = student6["xiaoming"];Console.WriteLine(aa==null);student6["xiaoming"] = 60;Console.WriteLine(student6["xiaoming"]);}}class Studentcj{//定義一個集合private Dictionary<string,int> scoreDictionary = new Dictionary<string,int>();public int? this[string subject]{get {// 檢測是否存在,存在則返回具體值,不存在返回nullif (this.scoreDictionary.ContainsKey(subject)){return this.scoreDictionary[subject];}else{return null;}}set{// 設置的值為null,直接異常if (value.HasValue == false){throw new Exception("值不能為空");}//檢測是否有值,有值則覆蓋,沒有則新增if (this.scoreDictionary.ContainsKey(subject)){this.scoreDictionary[subject] = value.Value;}else{this.scoreDictionary.Add(subject, value.Value);}}}}
}
4. 常量
-
什么是常量
-
常量(constant)是表示常量值(即,可以在編譯時計算的值)的類成員
-
常量隸屬于類型而不是對象,即沒有“
實例常量
”- “實例常量”的角色由只讀實例字段來擔當
-
注意區分成員常量與局部常量
namespace consetExample {class Program{static void Main(string[] args){// 局部常量const int Age = 35; Console.WriteLine(baidu.url);}}class baidu{//成員常量public const string url = "www.baidu.com";} }
-
-
常量的聲明
-
各種“只讀”的應用場景
- 為了提高程序可讀性和執行效率–常量
- 為了防止對象的值被改變一-只讀字段
- 向外暴露不允許修改的數據–只讀屬性(靜態或非靜態),功能與常量有一些重疊
- 當希望成為常量的值
其類型
不能被常量聲明接受時(類/自定義結構體
)–靜態只讀字段- 常量的類型只能是普通的值類型,不能是類或者自定義結構體
二、傳值 輸出 引用 數組 具名 可選參數,擴展方法
2.1 傳值參數
2.1.1 值類型 傳參
值類型傳參 是指將值類型(如 int、struct、enum 等)的變量作為參數傳遞給方法。值類型傳參的核心特點是 傳遞的是變量值的副本,因此方法內部對參數的修改不會影響原始變量。
- 副本創建:當值類型變量作為參數傳遞時,系統會創建一個與原始值相同的副本。
- 獨立作用域:方法內部操作的是副本,原始變量不受影響。
- 內存分配:副本在方法調用棧上分配獨立內存。
值類型
傳參 流程如圖:
- 代碼實例:
namespace ParametersExample
{class Program{static void Main(string[] args){int num = 100;Student student = new Student();student.AddOne(num);Console.WriteLine(num);}}class Student{public void AddOne(int num){num ++;Console.WriteLine(num);}}
}
-
定義一個值類型的變量 num
-
傳值進方法后,方法內的變量 num 會變更
-
方法外的變量 num 不會隨著值參數的變化而變化
-
調試結果:
2.1.2 引用類型 傳參
引用類型傳參 是指將引用類型(如 class、array、string、interface 等)的變量作為參數傳遞給方法。引用類型傳參的核心特點是 傳遞的是對象引用的副本
,而非對象本身
- 傳遞時創建引用的副本(內存地址的復制),而非對象的副本。
- 原始引用和副本引用指向同一個對象實例。
-
引用類型傳參,
創建新對象
重設引用副本:僅影響副本,不影響原始引用。
namespace ParametersExample
{class Program{static void Main(string[] args){StudentOne studentOne = new StudentOne();studentOne.Id = 100;studentOne.Name = "張三";SomeMethod(studentOne);Console.WriteLine(studentOne.Id);//打印對象的 HashCode,以及屬性Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);}static void SomeMethod(StudentOne student){student = new StudentOne();student.Id = 1;studentOne.Name = "張三";Console.WriteLine(studentOne.Id);//打印對象的 HashCode,以及屬性Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);}}class StudentOne{public int Id { get; set; }public string Name { get; set; }}
}
- 定義StudentOne 類,屬性ID,Name
- 初始化一個studentOne 對象,屬性賦值,將對象傳進SomeMethod 方法
- SomeMethod 方法內會初始化一個新對象 student ,并賦值
- 打印方法內外變量的值,雖然名稱都是張三,但是實例卻不是同一個
-
引用類型傳參,
操作對象,不創建新對象
修改對象屬性 / 狀態:影響原始對象。
namespace ParametersExample
{class Program{static void Main(string[] args){StudentOne studentOne = new StudentOne();studentOne.Id = 100;studentOne.Name = "張三";SomeMethodOne(studentOne);Console.WriteLine(studentOne.Id);Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);}static void SomeMethodOne(StudentOne studentOne){studentOne.Id = 2; // 方法的副作用studentOne.Name = "李四";Console.WriteLine(studentOne.Id);Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);}}class StudentOne{public int Id { get; set; }public string Name { get; set; }}
}
SomeMethodOne
方法內操作對象,重新賦值,方法內外的變量會同時變化.- 方法內進行更新屬性的操作,稱為方法的副作用,較少見,需要
避免使用
。
2.2 引用參數
引用參數(Reference Parameters) 允許方法直接操作調用者提供的變量,而非創建參數的副本。通過 ref
關鍵字聲明引用參數,使方法能夠修改原始變量的值或重設其引用。
這是一種強大的參數傳遞機制,適用于需要雙向數據交換的場景。
- 在方法定義和調用時均需顯式使用 ref 關鍵字。
- 傳遞變量的內存地址(而非值或引用的副本)。
- 方法內部對參數的任何修改直接反映到原始變量
- 實參必須是已初始化的變量,不能是常量或表達式
2.2.1 引用參數-值類型 傳參
引用參數(ref)與值類型傳參
的組合允許方法直接操作調用者提供的值類型變量
,而非創建其副本。
這種方式結合了 ref 關鍵字的雙向修改能力和值類型的內存特性,常用于需要高效修改值類型變量的場景。
namespace ParametersExample
{class Program{static void Main(string[] args){int x = 100;addNum(ref x);Console.WriteLine(x);}public static void addNum(ref int x){x++;Console.WriteLine(x);}}
}
2.2.2 引用參數 - 引用類型 傳參
引用參數(ref)與引用類型
傳參 的組合允許方法直接操作調用者提供的引用類型變量
,包括修改對象狀態或重設引用本身。
這種方式結合了 ref 關鍵字的雙向修改能力和引用類型的內存特性,常用于需要更靈活控制對象引用的場景。
- 引用參數 - 引用類型 , 創建新對象
namespace ParametersExample
{class Program{static void Main(string[] args){StudentOne studentOne = new StudentOne() { Id=1,Name="李四"};Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);Console.WriteLine("=============================");IWantSideEffect(ref studentOne);Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);}static void IWantSideEffect(ref StudentOne stu){stu = new StudentOne(){Id = 1, Name="張三"};Console.WriteLine("{0},{1}",stu.GetHashCode(),stu.Name);}}class StudentOne{public int Id { get; set; }public string Name { get; set; }}
}
- 引用參數 - 引用類型 , 不創建新對象,改變對象值
namespace ParametersExample
{class Program{static void Main(string[] args){StudentOne studentOne = new StudentOne() { Name="李四"};Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);Console.WriteLine("=============================");IWantSideEffect(ref studentOne);Console.WriteLine("{0},{1}", studentOne.GetHashCode(), studentOne.Name);}static void IWantSideEffect(ref StudentOne stu){stu.Name = "李四";Console.WriteLine("{0},{1}",stu.GetHashCode(),stu.Name);}}class StudentOne{public string Name { get; set; }}
}
2.3 輸出參數
輸出參數(Output Parameters) 允許方法通過參數返回多個值,而不僅僅依賴于返回值。通過 out
關鍵字聲明輸出參數,使方法能夠將計算結果賦值給調用者提供的變量。這是一種簡潔且類型安全的多返回值
解決方案。
- 在方法定義和調用時均需顯式使用
out
關鍵字。 - 傳遞變量的引用(類似 ref),但
無需初始化
- 方法
必須
在返回前為所有 out
參數賦值 - out 參數在方法
調用后
才被視為已初始化。
2.3.1 輸出參數-值類型
namespace ParametersExample
{class Program{static void Main(string[] args){double y = 0;string x = "100";DoubleParser doubleParser = new DoubleParser();bool b = doubleParser.tryParser(x, out y);if (b){Console.WriteLine(y+1);}else{Console.WriteLine(y);}}}class DoubleParser{public bool tryParser(string x, out double y){try{y = double.Parse(x);return true;}catch (Exception e){y = 0;return false;}}}
}
2.3.2 輸出參數-引用類型
namespace ParametersExample
{class Program{static void Main(string[] args){StudentFactory studentFactory = new StudentFactory();StudentOne studentOne = new StudentOne();bool res = studentFactory.CreateStu("張三", 35, out studentOne);if (res){Console.WriteLine("{0},{1}", studentOne.Age, studentOne.Name);}}}class StudentOne{public string Name { get; set; }public int Age { get; set; }}class StudentFactory(){public bool CreateStu(string Name, int Age, out StudentOne student){student = null;if (string.IsNullOrEmpty(Name)){return false;}if (Age > 100){return false;}student = new StudentOne();student.Name = Name;student.Age = Age;return true;}}
}
2.4 數組參數 (params)
使用 params 關鍵字允許方法接收任意數量的參數(包括零個),并在方法內部將其視為數組處理。
- params 類型[] 參數名
- 必須是形參列表中最后一個,有params 修飾
- 可傳遞數組,或直接傳遞多個參數值
namespace ParametersExample
{class Program{static void Main(string[] args){int[] ints = new int[] { 1, 2, 3 };//傳遞數組int num = countSum(ints);//方法的數組參數用params修飾后,傳值變簡單int num2 = countSum(1, 2, 3);Console.WriteLine("{0},{1}", num, num2);}// 使用 params 關鍵字的方法static int countSum(params int[] array){int sum = 0;foreach (int i in array){sum += i;}return sum;}}
}
- string.Split 方法演示:
namespace ParametersExample
{class Program{static void Main(string[] args){string aa = "張三, 李四; 王五# 趙六";string[] sss = aa.Split(',', ';', '#');foreach (var item in sss){Console.WriteLine(item);}}}
}
2.5 具名參數
具名參數(Named Arguments) 允許你在調用方法時通過參數名稱而不是位置來指定參數值。這使得代碼更具可讀性,特別是在處理具有多個參數的方法時,還可以跳過可選參數或按任意順序傳遞參數。
- 通過 參數名: 值 的語法指定參數值。
- 可以不按方法定義的參數順序傳遞參數
- 可以選擇性地為可選參數賦值,未指定的參數使用默認值。
- 參數位置不再受約束
namespace ParametersExample
{class Program{static void Main(string[] args){// 使用具名參數(順序可任意)printInfo(age: 35,name:"張三");}static void printInfo(string name,int age){Console.WriteLine("{0},{1}",name,age);}}
}
2.6 可選參數
可選參數是方法定義里能夠設定默認值的參數。調用方法時,這些參數可傳值也能不傳。
- 定義方法時,通過賦值運算符(=)給參數賦予默認值
- 可選參數必須放在必選參數的后面
- 調用方法時,可以不傳入可選參數的值,此時會使用默認值;也可以按順序傳值或者通過命名參數傳值。
namespace ParamsExample
{internal class Program{static void Main(string[] args){showParams();}static void showParams(string Name="張三",int Age=35){Console.WriteLine("{0},{1}",Name,Age);}}
}
2.7 擴展方法(this參數)
擴展方法
擴展方法(Extension Methods) 允許你向現有類型 “添加” 方法,而無需創建新的派生類型、修改原始類型或使用復雜的裝飾器模式。擴展方法使用 this 關鍵字修飾第一個參數,使其看起來像被擴展類型的實例方法。這是一種強大的語法糖,常用于增強框架類型或第三方庫的功能。
- 語法
- 必須定義在靜態類中。
- 第一個參數必須使用 this 關鍵字修飾。
- 通常為公共靜態方法。
- 調用方式
- 像實例方法一樣調用(無需顯式傳入第一個參數)。
- 需要引入擴展方法所在的命名空間。
- 優先級
- 實例方法優先于擴展方法。
- 多個擴展方法沖突時需顯式指定類型調用。
namespace ParamsExample
{internal class Program{static void Main(string[] args){string str = "";Console.WriteLine(str.IsNullOrWhitespace());}}// 靜態類:包含擴展方法public static class StringExtensions{//擴展方法:判斷字符串是否為空或空白public static bool IsNullOrWhitespace(this string str){if (string.IsNullOrEmpty(str)){return true;}if (str.Trim().Length == 0){return true;}return false;}}
}
LINQ 方法
LINQ(Language Integrated Query) 提供了一組標準查詢方法,用于對各種數據源(如集合、數組、數據庫等)進行高效的查詢、篩選、投影和聚合操作。
這些方法既可以通過 ** 方法語法(Method Syntax)直接調用,也可以通過查詢語法(Query Syntax)** 以類似 SQL 的形式表達。
namespace ParamsExample
{internal class Program{static void Main(string[] args){List<int> list = new List<int>() { 11, 19, 20, 30,15, 19 };//訪問靜態方法bool x = AllGreaterThanTen(list);Console.WriteLine(x);//篩選大于10的bool y = list.All(i => i > 10);Console.WriteLine(y);}public static bool AllGreaterThanTen(List<int> ints){foreach (var item in ints){if (item > 10){continue;}else{return false;}}return true;}}
}
2.8 各種參數的使用場景總結
- 傳值參數:參數的默認傳遞方式
- 輸出參數:用于除返回值外還需要輸出的場景
- 引用參數:用于需要修改實際參數值的場景
- 數組參數:用于簡化方法的調用
- 具名參數:提高可讀性
- 可選參數:參數擁有默認值
- 擴展方法(this參數):為目標數據類型“追加”方法