C to C#笔记

前言

该笔记是本人在大一暑假期间为了开发桌面软件,学习C#的笔记。写时已有些许Java和C++基础,所以部分知识点略过。仅作为个人留档使用
学习时基本参考视频 《C#语言入门详解》
https://youtube.com/playlist?list=PLZX6sKChTg8GQxnABqxYGX2zLs4Hfa4Ca

入门部分

表达式

  • 一个值

  • 一个变量

  • 一个命名空间

  • 一个类型

  • 一组方法,例如Console.WriteLine(重载决定具体调用哪一个)

  • 一个null

  • 一个匿名方法

    Action a = delegate(){Console.WriteLine("Hello,World!");};
    a();
    
  • 一个属性访问

  • 一个索引访问

  • 一个事件访问

    public static void Main(){
        Form myForm = new Form();
        myForm.Text = "Hello";
        myForm.Load += myForm_Load;
        myForm.ShowDialog();
    }
    
    static void myForm_Load(object sender, EventArgs e){
        Form form = sender as Form;
        if(Form == null) return;
        
    }
    

命名空间

命名空间就是将类组合在一起,树形结构,也可以避免同名的类冲突

using System; // 引用System名称空间
namespace HelloWorld{
    class Program{
        static void Main(string[] args){
            Console.WriteLine("Hello, World!");
        }
    }
}

放置了Program类在HelloWorld名称空间

类库的引用

类库的依赖需要重复添加引用,直到所有引用都被添加到reference中。

DLL引用(黑盒引用,无源代码)

NuGet可以避免填写递归依赖的过程

项目引用(白盒引用,有源代码)

耦合关系

即依赖关系,尽量做到“高内聚,低耦合”。

UML(通用建模语言)类图可以用图的方法展现类库之间的关系。

操作符

加号减号[]号重载

public struct Vector{
    public float x, y;
    public Vector(float x, float y){
        this.x = x;
        this.y = y;
    }
    public static Vector operator -(Vector a)
    	=> new Vector(-a.x, -a.y); // 重载负号
    public static Vector operator +(Vector a, Vector b)
        => new Vector(a.x + b.x, a.y + b.y); // 重载加号
    public float this[int index]{ // 重载[]号
        get{
            switch(index){
                case 0: return this.x;
                case 1: return this.y;
                default: throw new IndexOutOfRangeException("Invalid Vector Index!");
            }
        }
        set{
            switch(index){
                case 0: this.x = value; break;
                case 1: this.y = value; break;
                default: throw new IndexOutOfRangeException("Invalid Vector Index!");
            }
        }
    }
}

转换类型重载

class Monkey{
    public int Age;
}

class Stone{
    public int Age;
    public static explicit operator Monkey(Stone stone){ <--把explicit改成implicit就是隐式类型转换
        Monkey monkey = new Monkey();
        monkey.Age = stone.Age * 500;
        return monkey;
    }
}

public static void Main(){
    Stone stone = new Stone();
    stone.Age = 5000;
    Monkey sunwukong = (Monkey)stone;
}

is操作符

用来检验一个对象是不是某个类型的对象,返回boolean类型

as操作符

as将表达式结果显式转换为给定的引用货可以为null值的类型,如无法转换则返回null

object o = new Teacher();
Teacher t = o as Teacher;
if(t != null){
    t.Teach();
}

null合并操作符

int? x = null;
int y = x ?? 10; // 如果x为null,则返回10

类型

即类和结构体

可空类型

Nullable <int> x = nullint? x = null;

可空类型有一布尔属性 this.HasValue

字段

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

字段是类型的成员,旧称“成员变量”

只读的应用场景

  • 常量——提高程序的可读和效率

  • 只读字段——防止值被改变

  • 只读属性——向外暴露不允许修改的数据

  • 静态只读字段——希望成为常量的值,但不能被常量声明接受的时候(比如常量是类或者结构体)

    class School{
    	public static readonly Location Building = new Location("Some Address");
    }
    
    class Location{
        public Location(string address){
            this.Address = addresss;
        }
    }
    
    

属性

属性(property)是一种用于访问成员或类的特征的成员,特征反映了状态。他是字段的自然拓展,是字段的一种语法糖。

对外:暴露数据,数据可以是储存在字段里的,也可以是动态计算出来的;对内:保护字段不被非法值污染

由get/set方法进化而来,本来是这样的:

class Student{
    private int _age; // 命名private或internal字段时,使用_+小驼峰
    public int GetAge(){
        return this.age;
    }
    public void SetAge(int value){
        if(value >= 0 && value <= 120){
            this.age = value;
        }else{
            throw new Exception("Age value is Error.");
        }
    }
}

属性的应用:

class Student{
    private int _age;
    public int Age{
        get{
            return this.age;
        }
        set{
            if(value >= 0 && value <= 120) // value 为默认变量,不能改变,是上下文关键字
                this.age = value;
            else throw new Exception("Age value has Error.");
        }
    }
}

Student stu = new Student();
stu.age = 20;

属性的完整声明:

快速:propfull + TAB + TAB

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.");
            }
        }
    }
}

属性的简略声明:

class Student{
    private int _age;
    public int Age {get; set;}
    private bool _canWork
    public bool CanWork{
        get{
        	return this.age >= 16;
        }
    }
}

索引器

索引器(indexer)是一个能使对象用数组相同方式(即下表)进行索引的成员。没有静态索引器。一般拥有索引器的类型为集合类型。

快捷键:index -> TAB -> TAB

class Student{
    private var scoreDictionary = new Dictionary<string, int>();
    public int? this[string subject]{ // 一个索引器
        get{
            if(this.scoreDictionary.ContainsKey(subject)) // 先去找有没有Key
                return this.scoreDictionary[subject];
            else return null;
        }
        set{
            if(!value.Hasvalue){ // 传进来的value是一个null
                throw new Exception("Score can't be null.");
            }
            if(this.scoreDictionary.ContainsKey(subject)) // 如果有key则更新value
                this.scoreDictionary[subject] = value.Value; // 传入的若是一个空值,则也能以value.Value赋值
            else this.scoreDictionary.Add(subject, value.Value);
        }
    }
}

常量

一般来说不会为实例准备常量,只能由只读实例字段来代替,但是性能还是不比常量高。

语句

try语句

在谨慎的场景下,try-catch语句会频繁出现,以增加程序的鲁棒性

捕捉通用异常:

 class Calculator{
     public int Add(string args1, string args2){
         int a = 0;
         int b = 0;
         try{
             int a = int.Parse(args1);
 	       	 int b = int.Parse(args2);
         }
         catch{
             Console.WriteLine("Your argument(s) was error!")
         }
     }
}

捕捉特定异常:

 class Calculator{
     public int Add(string args1, string args2){
         int a = 0;
         int b = 0;
         try{
             int a = int.Parse(args1);
 	       	 int b = int.Parse(args2);
         }
         catch(ArgumentNullException ane) // 将捕获的异常赋值给ane
             Console.WriteLine(ane.Message);
         catch(FormatException)
             Console.WriteLine("Your argument(s) are not number.");
         catch(OverflowException)
             Console.WriteLine("Out of range."); 
         finally{
             // 无论是否有异常都会执行,一般会用释放一些系统资源
         }
     }
}

throw语句:

我不想处理这个语句,抛出异常,能被调用者的try-catch捕捉到

try{
    a+b;
}catch(OverflowException oe){
    throw; // throw oe;
}

foreach语句

用来枚举一个集合中的元素,并对该集合中的每一个元素执行一次相关的嵌入语句。查看源代码,所有继承了IEnumerable接口的类都是可以被迭代的类

using System.Collections;
int[] intArray = new int[] {1,2,3,4,5,6};
IEnumberator enumerator = intArray.GetEnumerator();
foreach(var current in intArray)
    Console.WriteLine(current); 

while+迭代器

using System.Collections;
int[] intArray = new int[] {1,2,3,4,5,6};
IEnumberator enumerator = intArray.GetEnumerator();
while(enumerator.MoveNext()){ // bool值,是否已经成功地推进到下一个元素
    Console.WriteLine(enumerator.Current);
}
enumerator.Reset(); // 将迭代器拨回到最初状态

参数

值参数

不带修饰符,一个值行参对应一个局部变量

可以用Object.GetHashCode()方法来判断是不是同一个实例

只要没有声明ref,无论是否为引用类型都会创建副本

Student outterStu;
func(outterStu);
func(Student stu){
    函数实现;
}

函数内会创建新的Student指针,指向outterStu的内存地址,内部实现与ref不一样

引用参数

添加ref声明,且确保变量在被引用之前已经拥有明确的赋值。ref不会在函数内部开辟新变量。

输出参数

out修饰符声明的参数,输出参数不创建新的存储位置。变量被作为输出参数之前不一定需要明确赋值,但在方法返回前,该方法每个输出行参都必须明确赋值。

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};
    }
}

static void Main(string[] args){
    Student stu = null;
    bool b = StudentFactory.Create("Tim", 34, out stu);
    if(b){
        Console.WriteLine($"Student {stu.Name}, age is {stu.Age}.");
    }
}

数组参数

数组参数必须是行参列表中的最后一个,由params修饰,也只能有一个

static void Main(string[] args){
    int result = CalculateSum(1,2,3) // 简化语法,不用再声明数组了
}
static int CalculateSum(params int[] intArray){
    int sum = 0;
    foreach(var item in intArray) sum += item;
    return sum;
}

具名参数

参数的位置不受约束

static void Main(string[] args){
	PrintInfo(age:10, name:"hhh");
}
static void PrintInfo(string name, int age){
    ;
}

可选参数

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

static void PrintInfo(string name = "heihei", int age = 34)

扩展方法

this参数 ,为目标数据类型追加方法,必须是静态的类中添加静态方法,类名一般为xxxxExtension作为统一收纳

static void Main(string[] args){
    double x = 3.14159;
    double y = x.Round(4); <--this修饰的参数变为了.之前的
}

static class DoubleExtension{
    public static double Round(this double input, int digits){ <--
        double result = Math.Round(input, digits);
        return result;
    }
}
LINQ扩展方法
List<int> myList = new List<int>() {11, 12, 9, 14, 15};
bool result = myList.All(i => i > 10); // 判断是否每一个都 > 10, All即为扩展方法

委托

什么是委托

delegate是函数指针的升级版,Java并没有类似的功能实体。

什么是函数指针

在C/C++中

#include <stdio.h>
typedef int (*Calc)(int a, int b); // 定义一个函数指针
int Add(int a, int b)
    return a + b;
int Sub(int a, int b)
   	return a - b;
int main(){
    int x = 100, y = 200, z = 0;
   	z = Add(x,y); // 直接调用
    Calc funcPoint1 = &Sub; // 赋值函数指针
    z = funcPoint1(x, y); // 间接调用
}

委托实例

class Calculator{
    public void Report()
        Console.WriteLine("I have 3 methods.");
    public int Add (int a, int b)
        return a + b;
    public int Sub (int a, int b)
        return a - b;
}
Action委托
static void Main(string[] args){
    Calculator calculator = new Calculator();
    Action action = new Action(calculato.Report);// Action就像一个小提包,装着方法
    calculator.Report(); // 直接调用
    action.Invoke(); // 间接调用
    action(); // 简介调用的简洁写法
}
Func 泛型委托
static void Main(string[] args){
    int x = 100, y = 200, z = 0;
    Calculator calculator = new Calculator();
    // Func<传入类型,返回类型>
	Func<int, int, int> func = new Func<int, int, int>(calculator.Add);
    z = func.Invoke(x, y);
    z = func(x,y); // 简洁写法
}

自定义委托

委托这种类需要按照函数的声明格式来声明

class Calculator{
	public double Add(double x, double y) return x + y;
    public double Sub(double x, double y) return x - y;
    public double Mul(double x, double y) return x * y;
    public double Div(double x, double y) return x / y;
}
namespace Whatever{
    public delegate double Calc(double a, double b); // 声明一个自定义委托类型
    class Program{
        static void Main(string[] args){
            Calculator calculator = new Calculator();
            Calc calc = new Calc(calculator.Add);
        }
    }
}

委托的一般使用方法

将一个委托传入方法中,让方法调用。

模板方法

相当于填空题,常见于代码中部,委托有返回值

回调方法

相当于流水线,常见于代码后部,委托无返回值

对于滥用委托的警告

  • 这是一种方法级别的紧耦合
  • 可读性下降,debug难度增加
  • 委托回调、异步调用和多线程纠缠在一起,会让代码难以阅读和维护
  • 委托使用不当可能造成内存泄漏和程序性能下降

委托的高级使用方法

多播委托

multicast一个委托内部封装的不止一个委托方法

Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
action1 += action2;
action1 += action3;
action1.Invoke(); // 执行的顺序是按照封装方法的先后顺序
隐式异步调用

异步调用可能在争抢同一资源的时候发生线程安全问题。

action1.BeginInvoke(null, null); // 第一个参数是回调方法
action1.BeginInvoke(null, null);
action1.BeginInvoke(null, null); 
显式异步调用

自己来动手声明多线程

  • 使用原始方法
using System.Threading;
Thread thread = new Thread(new ThreadStart(stu.DoHomework));
thread.Start();
  • 使用Task
using System.Threading.Tasks;
Task task = new Task(new Action(stu.DoHomework));
task.Start();
接口取代委托
interface IProductFactory{
    Product Make();
}

class PizzaFactory:IProductFactory{
    public Product Make(){
        Product product = new Product();
        product.Name = "Pizza";
        return product;
    }
}

class WrapFactory{
    public Box WrapProduct(IProductFactory productFactory){
        Box box = new Box();
        Product product = productFactory.Make();
    }
}

事件

初步了解事件

Event,让对象或类具备通知能力的成员。对象O拥有事件E成员所表达的思想是:当事件E发生的时候,O有能力通知别的对象。Java语言里没有事件这类成员,也没有委托这种数据类型。Java事件是使用接口来实现的。日常开发的时候,使用已有事件的机会比较多,自己声明事件的机会比较少,所以先学习使用。更高级更有效的玩法是:MVC、MVP、MVVM。多用于开发客户端程序。

事件处理器是方法成员,挂接事件处理器的时候,可以使用委托实例,也可以直接使用方法名。这是一个语法糖。事件处理器对事件的订阅不是随意的,匹配与否由声明事件时所使用的委托类型来检测。事件可以同步调用也可以异步调用。

事件的本质是委托字段的一个包装器。只向外界暴露添加、一处事件处理器的功能;

事件详解

事件模型的五个组成部分
  • 事件的拥有者(event source,对象)

  • 事件的成员(event,成员)

  • 事件的响应者(event subscriber,对象)

  • 事件处理器(event handler,成员)——本质上是一个回调方法

  • 事件订阅——把事件处理器和事件关联在一起,本质上是一种以委托类型为基础的约定

应用
class Boy{
    internal void Action(object sender, ElapsedEventArgs e){
        Console.WriteLine("Jump!");
    }
}

Timer timer = new Timer();
timer.Interval = 1000;
Boy boy = new Boy();
timer.Elapsed += boy.Action; // elapsed流逝,将timer的Elapsed委托添加Action代码
timer.Start();

实例A调用实例B

using System.Windows.Forms;
Form form = new Form();
Controller controller = new Controller(form);
form.ShowDialog();


class Controller{
    private Form _form;
    public Controller(Form form){
        if(form != null){
            this._form = form;
            this.form.Click += this.FormClicked;
        }
    }
    private void FormClicked(object sender, EventArgs e){ // 事件处理器
        this.form.Text = DataTime.Now.ToString();
    }
}

实例A自己调用自己

using System.Windows.Forms;
MyForm form = new MyForm();
form.Click += form.FormClicked;
form.ShowDialog();

class MyForm : Form{ // 自己声明一个Form
    internal void FormClicked(object sender, EventArgs e){
        this.Text = DateTime.Now.ToString();
    }
}

响应者包含事件拥有者

using System.Windows.Forms;
MyForm form = new MyForm();
form.ShowDialog();

class MyForm : Form{
    private TextBox _textBox;
    private Button _button;
    public MyForm(){
        this._textBox = new TextBox();
        this._button = new Button();
        this.Controls.Add(this._button);
        this.Controls.Add(this._textBox);
        this._button.Click += this.ButtonClicked;
        this._button.Text = "SayHello";
    }
    private void ButtonClicked(object sender, EventArgs e){
        this.textBox.Text = "Hello, World!";
    }
}
补充
public partial class Form1 : Form{
	public Form1(){
        InitializeComponent();
        this.button3.Click += (sender, e) => { // lambda表达式
            this.textBox1.Text = "haha!";
        }
    }
	private void ButtonClicked(object sender, EventArgs e){
        if(sender == this.button1) this.textBox1.Text = "Hello!";
        else if(sender == this.button2) this.textBox2.Text = "World!";
    }
}

自定义事件的声明

  • 用于声明Foo事件的委托,一般命名为FooEventHandler
  • FooEventHandler委托的参数一般有两个,第一个是object类型,名字为sender,实际上就是事件的拥有者和来源。第二个是EventArgs的派生类,一般类名为FooEventArgs,名字为e
  • 虽然没有官方的说法, 我们可以吧委托的参数列表看作是事件发生后发送给事件响应者的“事件消息”
  • 触发Foo事件的方法一般命名为OnFoo,即“因何而发”,访问级别为protected,不能为public。
  • 为什么要用委托类型来声明事件?站在source角度来看,是为了表明source能传递哪些信息。站在subscriber的角度来看,他是一种约定,是为了约束能够使用什么样的签名的方法来处理事件。委托类型的实力将用于储存(引用)事件处理器。
完整版
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Think += waiter.Action;
customer.Action();
customer.Pay();

public class Customer {
    public double Bill{get; set;}
    public void Pay() Console.WriteLine($"I will pay ${this.Bill}");
    private OrderEventHandler orderEventHandler;
    public event OrderEventHandler Order{ // 事件的声明
        add{
            this.orderEventHandler += value;
        }
        remove{
            this.orderEventHandler -= value;
        }
    }
    public void Think(){
        if(this.orderEventHandler != null){
            OrderEventArgs e = new OrderEventArgs();
            e.DishName = "Kongpao Chicken";
            this.orderEventHandler.Invoke(this, e);
        }
    }
    public void Action(){
        this.Think();
    }
}
public class OrderEventArgs : EventArgs { // 用来传递事件参数的类
    public string DishName{get; set;}
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e); // 声明一个委托

public class Waiter{
    public void Action(Customer customer, OrderEventArgs e){
        Console.WriteLine("I will serve you the dish -{e.DishName}");
        customer.Bill += 10;
    }
}
简略版
public class Customer{
    public event OrderEventHandler Order; // 不是委托类型的字段,而是委托的声明
    public void Think(){
        if(this.Order != null){ // 如果该顾客的
            OrderEventArgs e = new OrderEventArgs();
            e.DishName = "Kongpao Chicken";
            this.Order.Invoke(this, e);
        }
    }
}

析构器

class Student{
    ~Student(){ // 析构函数
        Console.WriteLine("Bye bye!");
    }
}

如何不用new

Type t = typeof(Student); // ->将Student的类型存储在t类型变量中
object o = Activator.CreateInstance(t); // 或者object -> dynamic
Student stu = o as Student;

静态构造器

class Student{
    public static int Amount;
    static Student(){ // 只能用来构造静态字段
        Amount = 100;
    }

public类修饰符

决定是否能跨项目访问

Internal类修饰符

只能在项目内部访问

类的继承

C#中,一个类只能有一个基类,子类的访问级别不能超过基类的访问级别。sealed类不能被继承。横向扩展:对类成员数量扩充,纵向扩展:对类成员的更新。

正常继承:

class Vehicle{
    public Vehicle(){
        this.Owner = "N/A"; // 构造Car时,此构造函数仍会被调用
    }
    public string Owner{ get; set; }
}
class Car : Vehicle{
    public Car(){
        this.Owner = "Car Owner";
    }
}

使用base关键字,可访问作为上一级基类的对象。

但如果Vehicle的声明如下,Car的声明必须更改:

class Vehicle{
    public Vehicle(string owner){
        this.Owner = "owner"; // 构造Car时,此构造函数仍会被调用
    }
    public string Owner{ get; set; }
}
class Car : Vehicle{
    public Car() : base("N/A")[
        this.Owner = "Car Owner";
    ]
}

或者可以让子类也声明一个相同参数的构造器:

public Car(string owner) : base(owner){
    ;
}

类成员的访问级别

  • 类成员的访问级别以类的访问级别为上限

  • protected:所有子类

重写

基于纵向扩展,需要在基类的成员上修饰virtual,同时在子类的成员上修饰override。否则不加,则是一种错误。三次重写之后都用override。重写和隐藏的发生条件:可见、函数成员、标签一致

多态

父类实例引用子类对象。

抽象类

具体类—>抽象类—>接口:越来越抽象。抽象类是未完全实现的逻辑的类。抽象类为了复用而生,专门做为积累来使用,也具有解藕功能。抽象类不能被构造。

abstract class Student{ // 不能是private,不然怎么继承?
    abstract public void Study(); // 拥有抽象方法的类必定为抽象类,或者叫纯虚方法
}
class Vehicle{
    public void Stop(){
        Console.WriteLine("Stopped!");
    }
    public abstract void Run(); // 不作定义的同时规定子类必须定义
}
class Car:Vehicle{
    public override void Run(){
        Console.WriteLine("Car is running...");
    }
}
class Truck:Vehicle{
    public override void Run(){
        Console.WriteLine("Truck is running...");
    }
}

或者将VehicleBase做成纯抽象类:

abstract class VehicleBase{
    abstract public void Stop();
    abstract public void Run();
}
class Vehicle:VehicleBase{
    public override void Stop(){
        Console.WriteLine("Stopped!");
    }
    abstract public void Run(); // 这一步还不能实现,保留该抽象方法
}
class Car:Vehicle{
    public override void Run(){
        Console.WriteLine("Car is running...");
    }
}
class Truck:Vehicle{
    public override void Run(){
        Console.WriteLine("Truck is running...");
    }
}

或者可以把VehicleBase换成接口,这时子类的override可以省略:

interface IVehicle{
	void Stop(); // 皆为public
    void Run();
}
class Vehicle : IVehicle{
    public void Stop(){
        Console.WriteLine("Stopped!");
    }
    abstract public void Run(); // 这一步还不能实现,保留该抽象方法
}
class Car : Vehicle{
    public void Run(){
        Console.WriteLine("Car is running...");
    }
}
class Truck : Vehicle{
    public void Run(){
        Console.WriteLine("Truck is running...");
    }
}

接口

接口中的抽象方法必须是public,接口即契约:contract。感觉有些类似定义。每个继承接口的类必须实现所有的函数。一个接口可以继承多个接口。

interface IPhone{
    void Dail();
    void PickUp();
    void Send();
    void Receive();
}

class NokiaPhone:IPhone{
    public void Dail(){
        Console.WriteLine("Dailing...");
    }
    public void PickUp(){
        Console.WriteLine("Picking up...");
    }
    public void Send(){
        Console.WriteLine("Sending...");
    }
    public void Receive(){
        Console.WriteLine("Receiving...");
    }
}

class PhoneUser{
    private IPhone _phone;
    public PhoneUser(IPhone phone){
        _phone = phone;
    }
   	public void UsePhone(){
        _phone.Dail();
        _phone.PickUp();
        _phone.Send();
        _phone.Receive();
    }
}

为借口变量提供特殊方法,接口的显示实现

I2 a = new A();
a.foo2();
var combine = a as A;
a.foo1();
interface I1{
    void foo1();
}
interface I2{
    void foo2();
}

class A : I1, I2{
    public void foo1(){
        Console.WriteLine("Hello world");
    }
    void I2.fool2(){
        Console.WriteLine("Hello world");
    }
}

进阶部分

反射

反射机制为.NET框架所拥有。可以理解为镜像,在不知道对象的类型情况下,不用new创建一个同类型的对象,可以解耦。

var t = tank.GetType(); // GetType返回Type类型的对象
object o = Activator.CreateInstance(t); // Activator激活器,使用t来返回object类型的对象
MethodInfo fireMi = t.GetMethod("Fire"); // using System.Reflection,Mi即MethodInfo
fireMi.Invoke(o, null); // 如果函数有参数则在o后添加参数

依赖注入

Dependence injection,使用前需要安装NuGet上的:Microsoft.Extensions.DependencyInjection。

基本用法:

using Microsoft.Extensions.DependencyInjection;
var sc = new ServiceCollection();
sc.AddScoped(typeof(ITank), typeof(Tank)); // 放入一队Type,分别是接口和继承接口的类
var sp = sc.BuildServiceProvider();

ITank tank = sp.GetService<ITank>();
tank.Fire();

用注册的类型创建实例注入到构造器中去:

var sc = new ServiceCollection();
sc.AddScoped(typeof(ITank), typeof(Tank)); // 放入一队Type,分别是接口和继承接口的类
sc.AddScoped(typeof(IVehicle), typeof(Car));
sc.AddScpoed<Driver>();
var sp = sc.BuildServiceProvider();

var driver = sp.GetService<Driver>();
driver.Drive();

插件式编程

一个小动物叫声程序

主程序:

using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Loader;

namespace BabyStroller.App{
    class Program{
        static void Main(string[] args){
            // 将所有的AnimalType载入内存
            var folder = Path.Combine(Environment.CurrentDictory, "Animals");
            var files = Directory.GetFile(folder);
            var animalTypes = new List<Type>();
            foreach(var file in files){
                var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file);
                var types = assembly.GetTypes();
                foreach(var t in types){
                    if(t.GetMethod("Voice") != null){
                        animalTypes.Add(t);
                    }
                }
            }
            
            // 主程序
            while(1){
                for(int i = 0; i < animalTypes.Count; i++){
                    Console.WriteLine($"{i+1}, {animalTypes[i].Name}");
                }
                Console.WriteLine("==============");
                Console.WriteLine("choose:");
                int index = int.Parse(Console.ReadLine());
                if(index > animalTypes.Count || index < 1){
                    Console.WriteLine("Wrong!");
                    continue;
                }
                Console.WriteLine("How much times:");
                int times = int.Parse(Console.ReadLine());
                var t = animalTypes(index - 1);
                var m = t.GetMethod("Voice");
                var o = Activator.CreateInstance(t);
                m.Invoke(o, new object[]{times}); // 在实例中,里面放入times作为参数
            }
        }
    }
}

插件,写完后放到Animals/:

namespace Animals.Lib{
    public class Cat{
        public void Voice(int times){
            for(int i = 0; i < times; i++){
                Console.WriteLine("Meow!");
            }
        }
    }
}

怎么做到Voice规范呢,可以开放一个接口:

using System;
using System.Collections.Generic;
using System.Text;

namespace BabyStroller.SDK{
    public interface IAnimal{
        void Voice(int times);
    }
}

如果有一个插件,未开发完成,不希望被添加到列表里可以使用Attribute

// UnfinishedAttribute.cs
using System;
namespace BabyStroller.SDK{
    public class UnfinishedAttribute : Attribute{} // 声明了一个Attribute
}
using BabyStroller.SDK;
namespace Animals.Lib{
    [Unfinished] // 标注
    public class Cat : IAnimal{
        public void Voice(int times){
            for(int i = 0; i < times; i++){
                Console.WriteLine("Meow!");
            }
        }
    }
}

主程序中修改:

foreach(var t in types){
    if(t.GetInterfaces().Contains(typeof(IAnimal))){ // 一个类型的基接口包含IAnimal
        var isUnfinished = t.GetCustomAttributes(false).Any(a => a.GetType() == typeof(UnfinishedAttribute));
        if(isUnfinished) continue;
        animalTypes.Add(t);
    }
}

...
    
var t = animalTypes[index - 1];
var m = t.GetMethod("Voice");
var o = Activator.CreateInstance(t);
var a = o as IAnimal;  // <--
a.Voice(times);

泛型

generic,可避免成员膨胀和类型膨胀。泛型和类、接口、委托、方法、属性、字段都有交叉,有正交性。泛型必须要先经过特化后才能正常使用。泛型可以拥有多个例如<TKey, TValue>。

泛型类

class Apple{
    public string Color{ get; set;}
}

class Box{ // 包装盒类
    public Apple Cargo { get; set;}
}

如果不确定Box里面会装什么类型:

class Box{ // 包装盒类
    public Object Cargo { get; set;}
}

Box box = new Box(){Cargo = apple};
Console.WriteLine((box.Cargo as Apple)?.Color); // ? -> 不是null值时打印color

使用泛型:

class Box<TCargo>{ // 里面写类型参数,代表一个泛化的类型
    public TCargo Cargo{get; set;}
}

Box<Apple> box = new Box<Apple>(){Cargo = apple}; // 将泛型Box特化成一个装苹果的类型
Console.WriteLine(box.Cargo.color);

有时候不用显式地调用,编译器会自动推断。

泛型接口

如果一个类继承一个泛型接口,那么该类也是泛型。

interface IUnique<TId>{
    TId ID{get;set;}
}

class Student<TId> : IUnique<TId>{ // <-泛型类继承泛型接口
    public TId ID {get;set;}
    public string Name{get;set;}
}

Student<int> stu = new Student<int>();
stu.ID = 101;
stu.Name = "Timothy";

泛型委托

Action泛型委托

适用于无返回值的委托

static void Say(string str){
    Console.WriteLine($"Hello, {str}");
}

Action<string> a = Say(); // 引用的参数类型是什么
a.Invoke("World");
Function泛型委托

适用于有返回值的委托

static int Add(int a, int b)
    return a + b;
static double Add(double a, double b)
    return a + b;

Func<double, double, double> func1 = Add; // 最后一个用于指示返回值类型是什么
var result = fun1;
Console.WriteLine(result);
Func<int, int, int> func2 = Add;
result = fun2;

lambda表达式(匿名函数)

对于简单函数,不比单独声明,而是随调用随写。

static double Add(double a, double b)
    return a + b;

Func<double, double, double> func = Add;

其实貌似只是去掉了函数名而已:

Func<double, double, double> func = (double a, double b) => {return a + b;};

但是泛型委托已经认识到两个输入参数是double,所以进一步简化:

Func<double, double, double> func = (a, b) => {return a + b;};

partial类

把一个类的代码分成多部分编写。可有效减少类的派生。

文件A:

namespace Bookstore.Client{
    public partial class Book{
        public int ID{get; set;}
        public string Name{get; set;}
        public double Price{get; set;}
        public string Author{get; set;}
    }
}

文件B

namespace Bookstore.Client{
    public partial class Book{
        public string Report(){
            return $"#{this.ID} Name:{this.Name} Price:{this.Price}";
        }
    }
}

枚举类型

人为约束取值范围的整数。

enum Level{
    Employee, // 0
    Manager, // 1
    Boss = 100,
    BigBoss, // 101
}
class Person{
    public int ID{get; set;}
    public string Name{get; set;}
    public Level Level{get; set;}
}

Person employee = new Person();
employee.Level = Level.Employee;
Person boss = new Person();
boss.Level = Level.Boss;
Console.WriteLine(boss.Level > person.Level);
--> True

比特位用法

enum Skill{ // 在二进制按位设置
    Drive = 1,
    Cook = 2,
    Program = 4,
    Teach = 8,
}
person.Skill = Skill.Drive | Skill.Cook; // 这个人又会开车又会做饭
Console.WtireLine((person.Skill & Skill.Cook) > 0); // 这个人会做饭吗--> True
// 或者写成:
Console.WtireLine((person.Skill & Skill.Cook) == Skill.Cook); 
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值