# 表达式

# 定义

An expression is a sequence of one or more operands and zero or more operators that can be evaluated to a sing value, object, method, or namespace.

// Single Value
int x = 100;
// Object
(new Form()).ShowDialog();
// Method
Action myActino = new Action(Console.WriteLine); // Console.WriteLine 就是方法,成员访问表达式
// Namespace
System.Windows.Forms.Form myForm = new Form(); // System.Windows.Forms 名称空间访问表达式
  • 算法逻辑的最基本(最小)单元,表达一定的算法意图
  • 因为操作符有优先级,所以表达式也就有了优先级

# 各类表达式概览

img

# C# 语言中表达式的分类

An event access

static void Main(string[] args)
{
    var myForm = new Form();
    // 访问属性
    myForm.Text = "Hello";
    // 访问事件
    myForm.Load += MyForm_Load;
    myForm.ShowDialog();
}
private static void MyForm_Load(object sender, EventArgs e)
{
  var form = sender as Form;
  if (form == null)
  {
    return;
  }
  form.Text = "New Title";
}

# 语句定义

img

img

# 语句详解

# 声明语句

局部变量声明与局部常量声明,详情参见 C# 语言定义文档

# 表达式语句

img

expression-statement 用于计算所给定的表达式。由此表达式计算出来的值(如果有)被丢弃

static void Main(string[] args)
{
  // Add 产生的 7.0,如果前面没有拿变量接收它,值被丢弃了
  Add(3.0, 4.0);
}
static double Add(double a,double b)
{
  return a + b;
}

Single Responsibility 单一职责原则:一个方法尽量只做一件事情

# 块语句

  • 块语句无论什么时候都被编译器当做一条语句来看待
  • 编译器认为块语句是一条完整的语句(即块语句最后不用加 ; 号)

img

变量的作用域:块之内声明的变量,作用域仅在块内

static void Main(string[] args)
{
  int x = 100;
  {
    Console.WriteLine(x);
    int y = 200;
    Console.WriteLine(y);
  }
  // Error CS0103  当前上下文中不存在名称 “y”
  Console.WriteLine(y);
}

🚩 Ctrl + } :跳转至该花括号对应的花括号处

# 选择(判断、分支)语句

根据表达式的值从若干个给定的语句中选择一个来执行

# if

img

if (条件)
{
  ...
}
else if ()
{
  ...
}
else
{
  ...
}

# switch

image.png

  • 表达式的类型为 sbyte、byte、short、ushort、int、uint、long、ulong、bool、char、string 或 enum-type,或者是对应于以上某种类型的可以为 null 的类型,则该类型就是 switch 语句的主导类型
  • 从 C# 7.0 开始 switch 表达式已支持任何非 null 表达式
switch (变量)
{
  case 条件1:
    ...
    break;
  case 条件2:
    ...
    break;
  default:
    ...
    break;
}

# try

img

可以通过 MSDN 查方法相应的异常

如 Int32.Parse 方法 (String) 就有以下异常

img

try
{
  ...
}
catch(OverflowException)
{
  throw;
}
finally
{
  ... // 总是会执行:应该写释放系统资源的语句 / 程序的 log(即程序的执行记录)
}
演示:加上标识符
public int Add(string arg1, string arg2)
{
  int a = 0;
  int b = 0;
  try {
    a = int.Parse(arg1);
    b = int.Parse(arg2);
  }
  catch (ArgumentNullException ane) {
    Console.WriteLine(ane.Message);
  }
  catch (FormatException fe) {
    Console.WriteLine(fe.Message);
  }
  catch (OverflowException oe) {
    Console.WriteLine(oe.Message);
  }
  int result = a + b;
  return result;
}

# 三元运算符

Exp1 ? Exp2 : Exp3;

# 迭代(循环)语句

循环类型描述使用场景
while按不同条件执行一个嵌入语句零次或多次确定循环条件
do...while按不同条件执行一个嵌入语句一次或多次至少被执行一次
for计算一个初始化表达式序列,然后,当某个条件为真时,重复执行相关的嵌入语句并计算一个迭代表达式序列确定次数
foreach用于枚举一个集合的元素,并对该集合中的每个元素执行一次相关的嵌入语句
嵌套循环可以在 while、for 或 do..while 循环内使用一个或多个循环
int i = 0;
while(i < 10)
{
  Console.WriteLine(i);
  i++;
}
int i = 0;
do
{
  Console.WriteLine(i);
  i++;
} while (i < 10);
for (int i = 0; i < 10; i++)
{
  Console.WriteLine(i);
}
int i = 0;
for ( ; i < 10; )
{
  Console.WriteLine(i);
  i++;
}
for( ; ; ) // 死循环
int intArray = new int[] { 1, 3, 5 ,7};
foreach (var i in inArray) {
  Console.WriteLine(i);
}

迭代器

集合遍历的底层原理和迭代器,foreach 语句就是对集合遍历的简记法

static void Main(string[] args)
{
  var intArray = new int[] { 1, 3, 5 ,7};
  IEnumerator enumerator = intArray.GetEnumerator();
  while (enumerator.MoveNext())
  {
    Console.WriteLine(enumerator.Current);
  }
  enumerator.Reset();
  var intList = new List<int>() { 2, 4, 6, 8 };
  IEnumerator enumerator2 = intList.GetEnumerator();
  while (enumerator2.MoveNext())
  {
    Console.WriteLine(enumerator2.Current);
  }
}

# 跳转语句

控制语句描述
break结束整个循环
continue中止当前循环,继续下次循环
goto将控制转到由标签标记的语句
throw将引发一个异常,语法比较灵活,它后面可以什么都不跟
return控制返回到出现 return 语句的函数的当前调用方

提前 return 原则:

class Program
{
  static void Main(string[] args)
  {
    Greeting("Mr.Duan");
  }
  static void Greeting(string name)
  {
    if (string.IsNullOrEmpty(name))
    {
      // 通过尽早 return 可以让代码阅读者立刻就鉴别出来
      //name 参数在什么情况下是有问题的
      return;
    }
    Console.WriteLine("Hello, {0}", name);
  }
}

# 字段、属性、索引器、常量

这四种成员都是用来表示数据的

成员说明
常量与类关联的常量值
字段类的变量
方法类可执行的计算和操作
属性与读写类的命名属性相关联的操作
索引器与以数组方式索引类的实例相关联的操作
事件可由类生成的通知
运算符类所支持的转换和表达式运算符
构造函数初始化类的实例或类本身所需的操作
析构函数在永久丢弃类的实例之前执行的操作
类型类所声明的嵌套类型

# 字段

# 定义

字段(field)是一种表示与对象或类(类与结构体)关联的变量

  • 字段是类型的成员,旧称 “成员变量”
  • 实例字段:与对象关联的字段
  • 静态字段:与类型关联的字段,由 static 修饰
示例:实例字段与静态字段
class Program
{
  static void Main(string[] args)
  {
    List<Student> stuList = new List<Student>();
    for (int i = 0; i < 100; i++)
    {
      Student stu = new Student();
      stu.Age = 24,
      stu.Score = i,
      stuList.Add(stu);
    }
    int totalAge = 0;
    int totalScore = 0;
    foreach (var stu in stuList)
    {
      totalAge += stu.Age;
      totalScore += stu.Score;
    }
    Student.AverageAge = totalAge / Student.Amout;
    Student.AverageScore = totalScore / Student.Amout;
    Student.ReportAmount();
    Student.ReportAverageAge();
    Student.ReportScore();
  }
  class Student
  {
    public int Age; // 实例字段
    public int Score;
    public static int AverageAge; // 静态字段
    public static int AverageScore;
    public static int Amout;
    public Student()
    {
      Student.Amout++;
    }
    public static void ReportAmount()
    {
      Console.WriteLine(Student.Amout);
    }
    public static void ReportAverageAge()
    {
      Console.WriteLine(Student.AverageAge);
    }
    public static void ReportScore()
    {
      Console.WriteLine(Student.AverageScore);
    }
  }
}

# 字段声明

字段声明带有分号,但它不是语句,字段的名字一定是名词

  • 实例字段:初始化的时机是在实例创建时
    • 声明实例字段时初始化值与在实例构造器里面初识化实例字段是一样的
  • 静态字段:初始化的时机是在运行环境加载该数据类型时
    • 即静态构造器初始化时
    • 声明静态字段时设置初始化值与在静态构造器里面初始化静态字段其实是一样的

数据类型被运行环境加载时,它的静态构造器将会被调用,且只被调用一次

public static int Amout = 100;
// 静态构造器
static Student()
{
  Amout = 200;
}

# 字段的初始值

  • 无显式初始化时,字段获得其类型的默认值,所以 “字段永远都不会未被初始化”
  • 实例字段初始化的时机 —— 对象创建时
  • 静态字段初始化的时机 —— 类型或被加载(load)时,即静态构造器被加载时

# 只读字段

只读字段为实例或类型保存一旦初始化后就不希望再改变的值

  • 实例只读字段
  • 静态只读字段

🚩 ctor + 2 * TAB:插入构造函数代码片段

示例
class Student()
{
  public readonly int ID; // 实例只读字段
  public Student(int id)
  {
    this.ID = id;
  }
}
class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine(Brush.DefaultColor.Red);
  }
}
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 }
  }
}

# 属性

# 定义

  • 属性(property)是一种用于访问对象或类型的特征的成员,特征反映了状态
  • 属性是字段的自然扩展
    • 从命名上看,字段更偏向于实例对象在内存中的布局,属性更偏向于反映现实世界对象的特征
    • 对外:暴露数据,数据可以是存储在字段里的,也可以是动态计算出来的
    • 对内:保护字段不被非法值 “污染”
  • 属性由 Get/Set 方法对进化而来
  • 又一个 “语法糖”—— 属性背后的秘密
使用 Get/Set 保护字段
class Program
{
  static void Main(string[] args)
  {
    try
    {
      Student stu1 = new Student();
      stu1.SetAge(20);
      Student stu2 = new Student();
      stu2.SetAge(20);
      Student stu3 = new Student();
      stu3.SetAge(20);
      int avgAge = (stu1.GetAge() + stu2.GetAge() + stu3.GetAge()) / 3;
      Console.WriteLine(avgAge);
     }
     catch (Exception ex)
     {
       Console.WriteLine(ex);
     }
   }
}
class Student
{
  private int age;
  public int GetAge()
  {
    return this.age;
  }
  public void SetAge(int age)
  {
    if (age >= 0 && age <= 120)
    {
      this.age = age;
    }
    else
    {
      throw new Exception("Age value has error!");
    }
  }
}
Get/Set 写起来冗长,C# 引入了属性
class Program
{
  static void Main(string[] args)
  {
    try
    {
      Student stu1 = new Student();
      stu1.Age=20;
      Student stu2 = new Student();
      stu2.Age = 20;
      Student stu3 = new Student();
      stu3.Age = 200;
      int avgAge = (stu1.Age + stu2.Age + stu3.Age) / 3;
      Console.WriteLine(avgAge);
    }
    catch (Exception ex)
    {
      Console.WriteLine(ex.Message);
    }
  }
}
class Student
{
  private int age;
  public int Age
  {
    get
    {
      return age;
    }
    set
    {
      if (value >= 0 && value <= 120)
      {
        age = value;
      }
      else
      {
        throw new Exception("Age value has error.");
      }
    }
  }
}

# 属性声明

  • 完整声明 —— 后台(back)成员变量与访问器(注意使用 code snippet 和 refactor 工具)
  • 简略声明 —— 只有访问器(查看 IL 代码)
  • 只读属性 —— 只有 getter 没有 setter

🚩 prop + 2 _ tab:属性的简略声明,propfull + 2 _ tab:属性的完整声明

# 动态计算值的属性

属性与字段的关系

  • 都用于表示实体(对象或类型)的状态
  • 属性大多数情况下是字段的包装器
  • 建议:永远使用属性(而不是字段)来暴露数据,即字段永远都是 private 或 protected
主动计算
// 每次获取 CanWork 时都计算,适用于 CanWork 属性使用频率低的情况
class Program
{
    static void Main(string[] args)
    {
        try
        {
            var stu1 = new Student();
            stu1.Age = 12;
            Console.WriteLine(stu1.CanWork);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}
class Student
{
    private int age;
    public int Age
    {
        get { return age; }
        set
        {
            age = value;
        }
    }
    public bool CanWork
    {
        get
        {
            return age > 16;
        }
    }
}
被动计算
// 只在 Age 赋值时计算一次,适用于 Age 属性使用频率低,CanWork 使用频率高的情况
class Program
{
    static void Main(string[] args)
    {
        try
        {
            var stu1 = new Student();
            stu1.Age = 12;
            Console.WriteLine(stu1.CanWork);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}
class Student
{
    private int age;
    public int Age
    {
        get { return age; }
        set
        {
            age = value;
            CalculateCanWork();
        }
    }
    private bool canWork;
    public bool CanWork
    {
        get { return canWork; }
    }
    private void CalculateCanWork()
    {
        canWork = age > 16;
    }
}

# 索引器

使对象能够用与数组相同的方式(使用下标)进行索引

⚠️ 注:没有静态索引器

🚩 index + 2 * tab:快速声明索引器

索引器一般都是用在集合上面,像如下示例这样用是很少见的(只是为了讲解方便)

示例
class Program
{
    static void Main(string[] args)
    {
        Student stu = new Student();
        stu["Math"] = 90;
        var mathScore = stu["Math"];
        Console.WriteLine(mathScore);
    }
}
class Student
{
    private Dictionary<string, int> scoreDictionary = new Dictionary<string, int>();
    public int? this[string subject] {
        get {
            if (this.scoreDictionary.ContainsKey(subject)) {
                return this.scoreDictionary[subject];
            }
            else {
                return null;
            }
        }
        set {
            if (value.HasValue == false) {
                throw new Exception("Score cannot be null.");
            }
            if (this.scoreDictionary.ContainsKey(subject)) {
                this.scoreDictionary[subject] = value.Value;
            }
            else {
                this.scoreDictionary.Add(subject, value.Value);
            }
        }
    }
}

# 常量

# 定义

  • 常量(constant)是表示常量值(即,可以在编译时计算的值)的类成员
  • 常量隶属于类型而不是对象,即没有 “实例常量”
    • “实例常量” 的角色由只读实例字段来担当
  • 注意区分成员常量与局部常量
// 成员常量
int.MaxValue
Math.PI

# 声明

static void Main(string[] args)
{
    Console.WriteLine(WASPEC.WebsiteURL);
}
class WASPEC {
    public const string WebsiteURL = "http://wwww.waspec.org";
}

# 各种 “只读” 的应用场景

  • 常量:为了提高程序可读性和执行效率
  • 只读字段:为了防止对象的值被改变,只有一次初始化机会,就是在声明它时初始化(等价于在构造函数中初始化)
  • 只读属性(静态或非静态):向外暴露不允许修改的数据,功能与常量有一些重叠
    • 对于类使用静态只读属性,对于对象使用实例只读属性
    • 要分清没有 Set,与 private Set 的区别
    • 常量比静态只读属性性能高,因为编译时,编译器将用常量的值代替常量标识符
  • 静态只读字段:当希望成为常量的值其类型不能被常量声明接受时(类 / 自定义结构体)
    • 字段没有类型局限,但常量只能是简单类型,不能是类 / 自定义结构体类型

# 参数

# 值参数(传值参数)

声明时不带修饰符的形参是值形参。一个值形参对应于一个局部变量,只是它的初始值来自该方法调用所提供的相应实参

# 传值参数 ➡ 值参数

img

值类型的传值参数
class Program
{
    static void Main(string[] args)
    {
        Student stu = new Student();
        int y = 100;
        stu.AddOne(y); // 101
        Console.WriteLine(y); // 100
    }
}
class Student
{
    public void AddOne(int x)
    {
        x = x + 1;
        Console.WriteLine(x); // 101
    }
}

# 传值参数 ➡ 引用类型

img

引用类型的传值参数,并且新创建对象
class Program
{
    static void Main(string[] args)
    {
        Student stu = new Student() { Name = "Tom" };
        SomeMethod(stu); // daidai
        Console.WriteLine(stu.Name); // Tom
    }
    static void SomeMethod(Student stu)
    {
        stu = new Student() { Name = "daidai" };
        Console.WriteLine(stu.Name);
    }
}
class Student
{
    public string Name { get; set; }
}

⚠️ 注:这种状况很少见,一般情况都是传进来引用它的值,而不是连接到新对象去(基本只有面试题会考这个)

# GetHashCode()

Object.GetHashCode() 方法,用于获取当前对象的哈希代码,每个对象的 Hash Code 都不一样

通过 Hash Code 来区分两个 Name 相同的 stu 对象
class Program
{
    static void Main(string[] args)
    {
        Student stu = new Student() { Name = "Tom" };
        SomeMethod(stu); // 46104728 - Tom
        Console.WriteLine($"{stu.GetHashCode()} - {stu.Name}"); // 12289376 - Tom
    }
    static void SomeMethod(Student stu)
    {
        stu = new Student() { Name = "Tom" };
        Console.WriteLine($"{stu.GetHashCode()} - {stu.Name}");
    }
}
class Student
{
    public string Name { get; set; }
}

# 传值参数 ➡ 引用类型,只操作对象,不创建新对象

img

示例
class Program
{
    static void Main(string[] args)
    {
        Student stu = new Student() { Name = "daidai" };
        UpdateObject(stu);
        Console.WriteLine($"{stu.GetHashCode()} - {stu.Name}"); // 46104728 - Tom
    }
    static void UpdateObject(Student stu)
    {
        stu.Name = "Tom";
        Console.WriteLine($"{stu.GetHashCode()} - {stu.Name}"); // 46104728 - Tom
    }
}
class Student
{
    public string Name { get; set; }
}

# 引用参数 ref

ref 修饰符声明的形参。与值形参不同,引用形参并不创建新的存储位置。相反,引用形参表示的存储位置恰是在方法调用中作为实参给出的那个变量所表示的存储位置

变量在可以作为引用形参传递之前,必须先明确赋值

# 引用参数 ➡ 值类型

img

示例
static void Main(string[] args)
{
    int y = 1;
    IWantSideEffect(ref y);
    Console.WriteLine(y); // 101
}
static void IWantSideEffect(ref int x)
{
    x += 100;
}

# 引用参数 ➡ 引用类型,创建新对象

img

示例
class Program
{
    static void Main(string[] args)
    {
        Student outterStu = new Student() { Name = "daidai" };
        Console.WriteLine($"{outterStu.GetHashCode()} - {outterStu.Name}"); // 46104728 - daidai
        IWantSideEffect(ref outterStu); // 12289376 - Tom
        Console.WriteLine($"{outterStu.GetHashCode()} - {outterStu.Name}"); // 12289376 - Tom
    }
    static void IWantSideEffect(ref Student stu)
    {
        stu = new Student() { Name = "Tom" };
        Console.WriteLine($"{stu.GetHashCode()} - {stu.Name}");
    }
}
class Student
{
    public string Name { get; set; }
}

# 引用参数 ➡ 引用类型,不创建新对象只改变对象值

img

示例:对象的 HashCode 没有改变过
class Program
{
    static void Main(string[] args)
    {
        Student outterStu = new Student() { Name = "daidai" };
        Console.WriteLine($"{outterStu.GetHashCode()} - {outterStu.Name}"); // 46104728 - daidai
        SomeSideEffect(ref outterStu); // 46104728 - Tom
        Console.WriteLine($"{outterStu.GetHashCode()} - {outterStu.Name}"); // 46104728 - Tom
    }
    static void SomeSideEffect(ref Student stu)
    {
        stu.Name = "Tom";
        Console.WriteLine($"{stu.GetHashCode()} - {stu.Name}");
    }
}
class Student
{
    public string Name { get; set; }
}

⚠️ 注:上面示例中使用传值参数(不用 ref)结果也将一样,但内部机理不同

  • 传值参数创建了副本,方法里面的 stu 和 outterStu 不是一个对象,所指向的内存地址不一样,但是存储的地址是相同的,都存储的是 Student 实例在堆内存中的地址
  • 引用参数 stu 和 outterStu 指向的是同一个内存地址,这个内存地址里面存储的就是 Student 实例在堆内存中的地址

# 输出参数

out 修饰符声明的形参。类似于引用形参,输出形参不创建新的存储位置

变量在可以作为输出形参传递之前不一定需要明确赋值

在方法返回之前,该方法的每个输出形参都必须明确赋值

# 输出参数 ➡ 值类型

img

示例:c# 中自带输出参数的方法
static void Main(string[] args)
{
    Console.WriteLine("Please input first number:");
    string arg1 = Console.ReadLine();
    double x = 0;
    if (double.TryParse(arg1, out x) == false)
    {
        Console.WriteLine("Input error!");
        return;
    }
    Console.WriteLine("Please input second number:");
    string 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("789", out x))
        {
            Console.WriteLine(x + 1); // 790
        }
        else
        {
            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;
        }
    }
}

# 输出参数 ➡ 引用类型

img

示例
class Program
{
    static void Main(string[] args)
    {
        Student stu = null;
        if (StudentFactory.Create("Tim", 34, out stu))
        {
            Console.WriteLine($"Student {stu.Name}, age is {stu.Age}"); // Student Tim, age is 34
        }
    }
}
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 修饰
  • 举列:String.Format 方法和 String.Split 方法
示例
// 使用 params 关键字前
class Program
{
    static void Main(string[] args)
    {
        int[] 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;
    }
}
// 使用 params 后,不再需要单独声明数组
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;
    }
}

# 具名参数

参数的位置不再受约束

优点:

  • 提高代码可读性

  • 参数的位置不再受参数列表约束

class Program
{
    static void Main(string[] args)
    {
        PrintInfo("Tom", 24); // 不具名
        PrintInfo(age: 24, name: "Wonder"); // 具名
    }
    static void PrintInfo(string name, int age)
    {
        Console.WriteLine("Hello {0}, you are {1}.", name, age);
    }
}

# 可选参数

参数因为具有默认值而变得 “可选”

不推荐使用可选参数

示例
class Program
{
    static void Main(string[] args)
    {
        PrintInfo(); // Hello Tom, you are 24.
    }
    static void PrintInfo(string name = "Tom", int age = 24)
    {
        Console.WriteLine("Hello {0}, you are {1}.", name, age);
    }
}

# 扩展方法(this 参数)

img

示例
// 无扩展方法
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 y = x.Round(4);
        Console.WriteLine(y);
    }
}
static class DoubleExtension
{
    public static double Round(this double input, int digits)
    {
        return Math.Round(input, digits);
    }
}

img

当我们无法修改类型源码时,可以通过扩展方法为目标数据类型追加方法

LINQ 也是扩展方法的一大体现

# LINQ 实例

示例
using System.Linq; // 引入命名空间
class Program
{
    static void Main(string[] args)
    {
        List<int> myList = new List<int>(){ 11, 12, 9, 14, 15 };
        // bool result = AllGreaterThanTen(myList);
        
        bool result = myList.All(i => i > 10); // 这里的 All 就是一个扩展方法
        Console.WriteLine(result);
    }
    static bool AllGreaterThanTen(List<int> intList)
    {
        foreach (var item in intList)
        {
            if (item <= 10)
            {
                return false;
            }
        }
        return true;
    }
}

# 总结

各种参数的使用场景

  • 传值参数:参数的默认传递方法
  • 输出参数:用于除返回值外还需要输出的场景
  • 引用参数:用于需要修改实际参数值的场景
  • 数组参数:用于简化方法的调用
  • 具名参数:提高可读性
  • 可选参数:参数拥有默认值
  • 扩展方法(this 参数):为目标数据类型 “追加” 方法