该笔记是本人在大一暑假期间为了开发桌面软件,学习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中。
即依赖关系,尽量做到“高内聚,低耦合”。
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;
}
用来检验一个对象是不是某个类型的对象,返回boolean类型
as
将表达式结果显式转换为给定的引用
货可以为null
值的类型,如无法转换则返回null
object o = new Teacher();
Teacher t = o as Teacher;
if(t != null){
t.Teach();
}
int? x = null;
int y = x ?? 10; // 如果x为null,则返回10
即类和结构体
Nullable
或int? 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();
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-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;
}
用来枚举一个集合中的元素,并对该集合中的每一个元素执行一次相关的嵌入语句。查看源代码,所有继承了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);
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;
}
}
List myList = new List() {11, 12, 9, 14, 15};
bool result = myList.All(i => i > 10); // 判断是否每一个都 > 10, All即为扩展方法
delegate
是函数指针的升级版,Java并没有类似的功能实体。
在C/C++中
#include
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 = ⋐ // 赋值函数指针
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;
}
static void Main(string[] args){
Calculator calculator = new Calculator();
Action action = new Action(calculato.Report);// Action就像一个小提包,装着方法
calculator.Report(); // 直接调用
action.Invoke(); // 间接调用
action(); // 简介调用的简洁写法
}
static void Main(string[] args){
int x = 100, y = 200, z = 0;
Calculator calculator = new Calculator();
// Func<传入类型,返回类型>
Func func = new Func(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);
}
}
}
将一个委托传入方法中,让方法调用。
相当于填空题,常见于代码中部,委托有返回值
相当于流水线,常见于代码后部,委托无返回值
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();
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。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!");
}
}
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;
}
决定是否能跨项目访问
只能在项目内部访问
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();
tank.Fire();
用注册的类型创建实例注入到构造器中去:
var sc = new ServiceCollection();
sc.AddScoped(typeof(ITank), typeof(Tank)); // 放入一队Type,分别是接口和继承接口的类
sc.AddScoped(typeof(IVehicle), typeof(Car));
sc.AddScpoed();
var sp = sc.BuildServiceProvider();
var driver = sp.GetService();
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();
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
,可避免成员膨胀和类型膨胀。泛型和类、接口、委托、方法、属性、字段都有交叉,有正交性。泛型必须要先经过特化后才能正常使用。泛型可以拥有多个例如
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{ // 里面写类型参数,代表一个泛化的类型
public TCargo Cargo{get; set;}
}
Box box = new Box(){Cargo = apple}; // 将泛型Box特化成一个装苹果的类型
Console.WriteLine(box.Cargo.color);
有时候不用显式地调用,编译器会自动推断。
如果一个类继承一个泛型接口,那么该类也是泛型。
interface IUnique{
TId ID{get;set;}
}
class Student : IUnique{ // <-泛型类继承泛型接口
public TId ID {get;set;}
public string Name{get;set;}
}
Student stu = new Student();
stu.ID = 101;
stu.Name = "Timothy";
适用于无返回值的委托
static void Say(string str){
Console.WriteLine($"Hello, {str}");
}
Action a = Say(); // 引用的参数类型是什么
a.Invoke("World");
适用于有返回值的委托
static int Add(int a, int b)
return a + b;
static double Add(double a, double b)
return a + b;
Func func1 = Add; // 最后一个用于指示返回值类型是什么
var result = fun1;
Console.WriteLine(result);
Func func2 = Add;
result = fun2;
对于简单函数,不比单独声明,而是随调用随写。
static double Add(double a, double b)
return a + b;
Func func = Add;
其实貌似只是去掉了函数名而已:
Func func = (double a, double b) => {return a + b;};
但是泛型委托已经认识到两个输入参数是double,所以进一步简化:
Func func = (a, b) => {return a + b;};
把一个类的代码分成多部分编写。可有效减少类的派生。
文件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);