C#知识点全面总结

找了个U3D实习,把之前的笔记上传上来。

C#学习笔记

基本语法

常用数据类型

该初始化还是要初始化。

typeof得到数据类型,这个往往用于类

整型:int(Int32) ,uint ,long(int64),ulong,short,ushort

浮点数:float,double,decimal

其他: bool,string,char

常用转义序列(同c++):\\ , \n , \" 等等

注意事项:字符串是引用类型而其他类型都是值类型,可以通过@去包含一个完整字符串而避免转义

@"c:\pro\a.exe"
"c:\\pro\\a.exe"

数据转换

与c++不同:向上转换是允许的,向下转换必须Convert.XXX 显示转换

int i=10;
double j=10;
i=(int)j  //i=Convert.ToInt32(j);
注意unchecked可检查溢出

复杂数据类型

枚举

enum :{

​ n1=0;

​ …

}

enum color:int{
    red,blue,yellow
}
枚举当中的数字,字符,以及其对应的值是可以相互转换的
color c=0 //表示是red

结构体

运输优先级

类似c++

^异或

~按位取反

default运算符 可以用来生成类型的默认值 default T 可以调用T的默认构造函数返回一个对象

名称空间

类似java中的包,一个名称空间中可以有多个名称空间,

不同名称空间之间相互调用时要带上名称空间再通过引用符号.去使用其中内容。

同一名称空间(默认所有代码都在全局空间当中)直接通过名称引用

using的用法

  1. 对于链接到项目的类库,使用using来进行导入 (using后面只能跟类库)
  2. 可以对不同名称空间中的相同类进行别名定义 eg/ using timers=System.Timers(只能跟类)
  3. using static 静态类名 在模块中使用静态类方法时就无需再导入静态类名了
  4. 类似python当中的with(上下文管理器),可以隐式得释放某些资源(即调用Dispose函数)

分支循环

数组

矩形数组

[] =new [SIZE] 这里的SIZE必须是常量

常用的初始化技巧

const int size=5;
int[] arrays=new int[size]{1,2,3,4,5};
int[] arrays_2=new int[size];
int[] arrays_3={1,2,3,4,5};

多元矩形数组

[,…] name

常用的初始化技巧

int[,] arrays=new int[3,2]{
   
   {1,2},{3,4},{5,6}};
//其余两种同上

锯齿形数组

[][] name=new [size][] 永远只初始化第一个维度,接下来再一次初始化

int[][] a=new int[SIZE][]//如果要初始化接下来每个数组的行,则需要每次都new新的行
int[][] b={
	new int[]{1,2},
	new int[]{1,2,3},
	....
}

这些个数值型数组如果不初始化值只声明大小,那么默认为0

锯齿形数组当中存储的是数组,而矩形数组当中存储的就是元素,因此foreach的类型会不同

数组常用的接口

.Length

foreach( name in arrays){…} 注意锯齿形数组和矩形数组遍历不同,使用foreach遍历数组中内容时是无法对原来的内容进行修改的

字符串

c#当中的字符串视作为对象,也可以视作为char数组(只读的)引用类型

常用接口

Length
Replace
Split
Trim
Contains str1.Contains(str2)
PadLeft/Right
ToLower/Upper
两个复制函数
stk1.CopyTo(stk2,i) 从第i个元素将stk1复制到stk2中,这种方式复制不创建新的对象
stk.Clone()返回一个用于复制的object对象
str1.CompareTo(str2)
//字符串的格式化处理
//通用格式为string.Format("{index:type小数点位数}",1,2,...)其后可以接受任意数量参数
string.Format("{0:C2}",1)//表示对1这个数组保留两位小数点,作货币格式输出

常用接口

1.控制台输入输出
Console.WritenLine($"{变量名称}这之外的是一般字符串") //其中也可以进行运算 类似printf
Console.ReadLine();//与python类似 输入的数据一定是string要进行转换
2.数据类型转换
Convert.ToChar(int v)//这个v必须是对应的ascall值
Convert.ToCharArray(string str)
3.Object常用方法
.ToString()返回类名 类似于.GetType()

函数

基础

fun_name( par)

参数列表

可以接受任意参数的参数,需要使用关键字params

<type> fun(<type> name,params<ype>[] name)
void show(int bz,params int[] a)
//params的关键字必须放在函数签名的最后

C#当中默认使用值参数(数组除外,数组可以视为是使用引用类型的),值参数也可以赋予默认值

两种引用参数关键字ref(必须在函数外初始化),out(必须在函数内初始化并且丢弃原本的值)这两个关键字无论在函数定义还是使用时都要标明

C#变量作用域:取决于其初始化的位置而不取决于其声明的位置,C#中的变量在声明的时候就要进行初始化操作

C#中的全局数据,设置一个静态类,当中给出静态变量即可

重载与结构函数同其他

Main作为入口其签名为static void Main(string[] args)这个args作为命令行参数可以在debug选项中设置

委托

类似函数指针,关键字delegate

delegate type function(type par…) 声明后该委托变成了一个函数类型,所有符合该委托签名的函数都可以赋给该委托变量,往往可作函数参数,在向上转型过程中很方便使用(eg.c++ stl sort)

以排序为例,创建一个可以自定义关键字方法的排序

delegate bool compare(int a,int b);
static void sort(int[] arrays,compare cp)
{
    int temp = 0;
    int j = 0;
    for (int i = 1; i < arrays.Length; i++)
    {
        if(!cp(arrays[i-1],arrays[i]))
        {
            temp = arrays[i];
            for (j = i; j > 0 && !cp(arrays[j-1],temp); j--)
                arrays[j] = arrays[j-1];
            arrays[j] = temp;
        }  
    }
}
static bool upper(int a,int b)=>a < b ? true : false; 
static bool lower(int a, int b) => a > b ? true : false;
//这是一种快捷的函数表述法

delegate还可以用来实现匿名方法
delegate(type1 a,type2 b){

​ … 其中可以有返回值(类似js当中的function(){})

}

面向对象

基础

C#中的属性与字段

属性不同于字段,属性不提供对数据的直接访问需要依赖一定的接口,字段是可以直接访问的(如果设置为了public的话)

权限:public,private,protected,internal(内部成员,表示只有该项目可以使用的成员,可以与protected连用,同时也可以修饰类),static

方法(成员函数)

对象的生命周期

构造函数-使用中-析构函数(与C++类似),

调用构造函数是先调用基类构造函数从上到下依次初始化完成

调用析构函数后会依次向上调用,直至到Object对象的Finalize()函数

public class student{}
student std=new student();//这个括号不能省去

静态类与静态成员

静态成员为该类所有实例共享

静态构造函数(不能有访问限定符与参数)专门用于处理类的静态成员并且只调用一次,往往可以用来处理单例模式

如果有需要在一个类中只包含静态成员(比如Console)可以使用静态类,其只能包含静态成员,并且构造函数只能有一个静态构造函数,静态类是无法实例化的

静态函数中只能调用静态函数而不能调用其他成员函数

类与结构体的区别:声明结构体是值类型的而声明对象是引用类型的,两者在进行复制时会出现问题。引用类型在进行复制时使用的是浅复制,值类型则是深度复制。

深浅复制问题的解决

引用而导致浅度复制而产生的问题

public class school
{
    public string name;
}
public class student
{
    public school sch;
    public string name;
    public student()
    {
        sch = new school();
    }
}
class Program
{
    static void Main(string[] args)
    {
        student std1 = new student();
        std1.name = "hjh";
        std1.sch.name = "smu";
        Console.WriteLine("修改前" + std1.name + " " + std1.sch.name);

        student std2 = std1;
        std2.sch.name = "fdu";
        Console.WriteLine("修改后" + std1.name + " " + std1.sch.name);//修改std2却修改了std1
    }
}

值类型默认深复制,引用类型是浅复制(ps 数组)

字段定义

public class name{
	public typename name;
}

常用的定义字段关键字:const,static(这两个关键字均可将字段定义成只读的,静态字段只能通过类名访问),readonly(使得字段只能在构造函数中初始化赋值)

方法定义

常用的定义字段关键字:virtual,abstract(纯虚函数),override(如果要重写对象就必须用这个关键字),extern,sealed(这个关键字可以跟在override后面表示不能进一步重写)

public abstract class people{
	public abstract void show();//在抽象类中只有抽象函数可以只有定义
}
public sealed class student:people 
{
	public override void show(){}
}

c#中的字段可以在定义时给出初始值

定义属性

相当于给了类内部private的字段提供了一个于外界交互的接口

权限 type name{

​ [权限] get{return 一个私有字段}

​ [权限] set{一个私有字段=value} value作为一个关键字表示用户给到的值

}

属性必须有get或者set中的一个

private string name
public int Name{
	get{
		return name;
	}
	set{
		name=value;
	}
	//对于简单的字段可以直接用=>表达式eg.get=>name set set=>name=value
	//c#提供了快速重构方法
}

属性也可以使用方法的修饰符包括了virtual,abstract,override

访问器的权限不能高于属性本身

自动属性,只定义属性接口而不定义其所操作的private数据,实质上是隐藏了其操作的数据,自动属性必须要有get,set(不能给出实现但可以给出权限),并且可以初始化

public string MySchool{get;set;}="smu";

重写

//使用这种重写操作后可以通过父类进行多态
public class food{
	public virtual void show()=>Console.WritenLine("food");
}
public class apple:food{
    public override void show()=>=>Console.WritenLine("apple");
}

//下面这种则是隐藏,通过子类当中的new关键字明确标目是隐藏
public class food{
	public void show()=>Console.WritenLine("food");
}
public class apple:food{
    new public void show()=>=>Console.WritenLine("apple");
}

可以通过base关键字来访问父类成员,this关键字来访问当前实例成员,往往用在限定字段引用或者以当前实例为参数当中

类的嵌套

在类中定义类,可以根据需要去设定嵌套类的权限,嵌套类可以访问其外面类的私有成员

接口

方法和数据的封装,基于依赖倒转原则,接口中的方法只能有抽象不能有实现,也无法实例化

接口与类的区别

  1. 默认都是public的,也只能是public
  2. 成员不能有代码体,其成员的权限只能是public(默认),protected
  3. 不能用virtual,sealed,abstract,static定义其成员
  4. 类型定义成员不行,但是可以用new操作符去隐藏其继承接口的方法

一个类可以实现多个接口,一个接口可以被多个类实现,接口也能用于多态和继承,接口可以被包含在其他类中

使用方法

interface ICar:ITool{
    public void GetSize();
    public double GetSpeed();
    int Time{get;set;}//虽然接口中可以定义属性,但是这样的并不是自动实现的属性,因为接口中没有能够给他操作的类型定义变量,也就不能赋初值,可以只给出set/get,接口中的属性后面没有分号
}

接口是没有abstract与sealed定义的

常用的系统接口

//IDisposable接口
Dispose()//通过实现这个接口可以做到手动释放资源(注,可通过using关键字划定变量使用范围结束后自动dispose)

接口的实现(以之前的ICar为例)

一个派生类也继承其父类的接口,可以在父类实现时使用virtual关键字,使子类将其重写。子类实现的接口,其定义可以出现在父类中(但不能仅仅出现在子类中,因为子类可以被视为父类,反之不行)

public class Vehicle{
    public void GetSize(){}//实现方法可仅在父类中
}
public class Car:Vehicle,ICar
{
	public double GetSpeed(){} //这两种都是隐式实现,即可通过接口量也可通过对象去调用实现完了后的方法,还有显示实现,只能通过接口量去调用
	public int Time{get;set;}
}

继承

C#的继承是单一继承(类似java),所有的类都继承自object(元类)

继承写法

public class a:class_1,interface_1{} //需要注意接口必须在类名后面
public class a:b{
    a(int i,int j):base(i){}//其调用基类构造函数的方法类似于c++,基类名为base,不显示指明的话,就调用默认构造函数。在其后使用this关键字指明可以使调用构造函数前先调用其他的
}

抽象类:无法被实例化的类,与接口区别在于其中的方法是可以实现的

public abstract class name{}
//抽象函数不能同时修饰virtual,同时抽象函数是不能有实现的

派生类的访问权限不能高于基类

sealed关键字可以使类无法被继承

多态

子类可以向上转型成其父类或者其接口,在子类重写或者实现了对应方法的情况下,在调用其父类或者接口中对应的方法时其实调用的是其子类的方法。

重写:子类重新实现父类方法,不过由此可能会覆盖父类方法

对象之间的关系

包含关系(依赖)

依赖关系下的对象在实例化时是不会自动调用其包含对象的构造函数的,要主动声明。

集合关系(对象数组)

事件

常常运用于wpf中,组件(对象)在某触发条件下触发其绑定的事件函数(类似qt信号与槽机制)

动态链接库的使用

部分类与部分方法

partial关键字使得类可以定义在不同文件中,往往用于先定义部分方法,并在另一个部分类当中给出实现

public partial class MyClass{
    partial void do_it();//部分类不能用static,以及其他涉及oop的修饰符,不能out参数,他们必须是私有的,并且不能有返回值
    public void make_it(){do_it();}//如果do_it未实现,那么编译器会忽视这个函数
}
public partial class MyClass{
    partial void do_it(){}
}

部分类和部分方法可以在不同的.cs中文件中调用

动态链接库调用

通过ctril+b来创建dll文件

通过using来导入已经引入项目的动态库文件

集合

集合类基于名称空间

System.Collections

其中的几个核心接口

ICollection

IList

IDictionarty

IEnumerable

Collection

通过继承CollectionBase抽象类来去定义自己定制类型的集合(这个类中实现了上述四种接口)

public class Animals:CollectionBase{
	public void Add(Animal new_animal)
    {
        List.Add(new_animal);
    }
    public Animal this[int index] //索引器,可以通过index直接得到对应索引中的数据(属性)
    {
        set{List[index]=value;}
        get{return (Animal)List[index];}
    }
    //定义完索引器后可以直接在后面的代码中通过this去使用List对应索引的值
}

常用的集合类

ArrayList 类似c++的vector,其中的所有元素都被存储为了object对象,所以在使用时,需要进行强制转换后才能调用对应类型的方法

ArrayList array=new ArrayList();
array.Add()
AddRange()
RemoveAt()
InsertRange()
.Count

Dictionary

C#有两种键值对存储结构

  1. HashTable,其中所有的内容也都被存储为了object,使用foreach遍历哈希表时的入口要用DictionaryEntry类型通过Key,Value(这里的Value返回后也要强制转换先)访问对应值(类似C++中pair),foreach遍历字典的顺序是逆序的,其中元素没法直接排序,只能按照排序顺序取出,多线程中推荐。这个容器的键不是唯一
  2. Dictionary<type,type>,支持泛型,通过KeyValuePair进行foreach循环,字典插入时会对数据进行顺序排序而哈希表不会,适合单线程使用。这个容器的键是唯一的。通过Key,Value去遍历到对应属性

两者共同的常用方法/属性

Count
Add()//哈希表与字典的参数不同
Clear()
Keys()
Values()//这两个均返回集合
ContainsKey()
ContainsValue()

提供了一个类似于Collection的基本抽象类DictionaryBase,可以类似CollectionBase一样通过自己实现Add(),Remove以及索引器(eg.public string this[string index]{get…set…})

public class People:DictionaryBase{
    public void Add(string name,Person person)
    {
        InnerHashtable.Add(name,person);
    }
    public Person this[string key]{
        set{ InnerHashtable[key]=value;}
        get{ return InnerHashtable[key];}
    }
}

foreach

foreach执行流程

得到IEnumerator接口调用MoveNext方法,如果为true,那就返回当前引用,并且再次调用MoveNext直到为false(相当于有一个头指针)

迭代器

yield return 值

可以迭代得返回代码块(比如方法中的值)

返回类型为

  1. IEnumerator (当迭代类时)

  2. IEnumerable (当迭代方法时)

    实际上返回的都是object类型

使用yield表达式可以在一个代码块中多次返回值

public static IEnumerable SimpleList()
{
    yield return 1;
    yield return 2;
    ...
}
foreach(int i in SimpleList)
{...}
//可以通过foreach去访问迭代器
class Primes{
    ...
    public IEnumerator GetEnumerator(){//如果想迭代类必须是这个函数名称
        yield return ...
    }  
}
//可以用过foreach去迭代这个类得到迭代器不断通过yield返回的值

迭代器往往用于一个模块产生多个数据的情况下,或者简化自定义字典的迭代操作(继承自DictionaryBase),迭代器的yield返回不需要出现在对应代码块的最后

IEnumerator IEnumerable两者都是接口,不过后者当中只存在一个返回IEnumerator的函数,前者中定义了MoveNext等用于处理遍历的函数定义。往往用后者

再谈复制

假如类只包含值类型的话,对于类对象可以直接使用浅复制。如果类中包含引用类型,那么就意味着浅复制时只会复制引用类型属性的引用,必须要使用深复制。

通过实现ICloneable中object Clone()方法创建一个新的本身类对象去返回来实现深度复制

class People:DictionaryBase,ICloneable
{
	...
	public object Clone()
	{
		People newp=new People();
		foreach(DictionaryEntry en in InnerHashTable )
			newp.Add((string)en.Key,new Person((Person)en.Value));
			//一定要创建新对象才能避免浅复制的问题
		return newp;
	}
}
...
//由于Clone函数返回的是object对象,所以在使用时还需要强制转换
People tp_people=(People)people.Clone();

浅拷贝可以通过直接调用MemberwiseClone()去实现

比较

类型比较

通过typeof和GetType连用 (eg. obj.GetType()==typeof(class))

封箱拆箱

封箱:将值类型封装成object类型

拆箱:将object类型拆箱成值类型

(封箱复制值类型时会复制值类型副本的引用,而不是直接对值类型的引用

几个比较方法的对比:== CompareTo Equal

==是比较引用而不是内容

Equal是比较内容而不是引用

str1.CompareTo(str2) 专门正对字符串比较 返回0则内容相等 小于0 则str1<str2

is运算符

is

用来比较对象是否是给定类型或者给定类型的派生类型或者实现了给定接口

void check(object obj)
{
	if(obj is classa) //这里obj可以自行转换成其他类
        ...
}

运算符重载

IComparable与IComparer接口

前者用于比较的对象,后者用于让两个对象比较的比较器

class num:IComparable
{
	public int val;
	public int CompareTo(object obj)//可以接受任意类型的参数,需要注意给出类型处理
	{...}//只要返回负数,那么this的对象就放在obj对象的前面
	// public int Compare(object a,object b ){...} 实现IComparer
}

在Collections下有提供Comparer.Default来默认实现比较功能

这个比较类往往用于比较直接用大小于比较的内容(比如字符串)Comparer.Default.Compare()(要先类型转换)

上述两个接口往往用于自定义排序中

集合排序

对于ArrayList可以使用Sort进行排序(普通数组是没有这个成员函数的),对于数值型默认是递增排序,字符串默认是字典顺序排序的

使用sort进行自定义排序,两种方法

  1. 使用IComparable接口在要比较的对象类内实现CompareTo函数,CompareTo函数返回负数,那么this项就放在obj项前面,否则就放在后面

    class Unit:IComparable{
        public int attack;
        public Unit(int v):attack(v){} 
        public int Comparable(object obj)
        {
            if(obj is Unit)
            {
                Unit tp=obj as Unit;
                if(attack>tp.attack)
                    return -1;
                else
                    return 1;
            }
            else
                throw new Exception();
        }
    }
    

    2.还可以通过实现接口IComparer来实现一个用于比较类,直接将这个类传递给Sort函数,往往通过单例模式类去实现

    class Compare:IComparer{
        public static IComparer comparer=new Compare();
        private Compare(){}//单例模式下含有一个静态成员,构造函数为私有
        public int Compare(object a,object b)
        {//注意需要进行类型判断
            if(...)
                return -1;
            else
                return 1;
        }
    }
    List<int> list=new List<int>();
    list.Sort(Compare.comparer);
    //泛型实现方法
    class Comparer:IComparer<Type>
    {
    	...
    	public int Compare(Type t1,Type t2)
    	{
    	
    	}
    }
    

字典排序

字典排序只能把字典中的数据全部转换成ArrayList再根据Sort排序后倒回原来的字典中

转换

重载转换运算符

as运算符 typea as typeb 表示可以将a转换为类型b,但是有一定的条件

  1. typea是typeb的类型
  2. typea可以隐式转换为typeb
  3. typea可以封箱到typeb中
  4. typeb一定是引用类型或者是可以为空的类型

无效转换比如有些父类强制转换到子类的情况,则会返回Null

可以通过检查null来做异常检查

泛型

可空类型

可空的值类型:Nullable t 可以简写为T? t(可以赋值为null),有一个计算值属性为Value,T?之间的运算与T类似(一般的值类型是不能赋值为Null)

用于可空类型的运算符常常用来判空

a ?? b 返回非空的那个对象的值
?.在对象调用方法返回数值时如果为空那么就返回null,往往和??联用
obj?.count()??SIZE

可空类型,如果含有空作运算,那么返回null

泛型容器

需要名称空间

System.Collections.Generic

常用容器
List<T> t=new List<T>;
Dictionary<t k,t v> dict;

List的排序

List<T> t=new List<T>;
t.Sort()//默认排序;可以接受一个参数名为int fun(T x,T y);作为判断函数
//因为Sort有一个重载类型,其中参数为委托int Comparison<T,T>(T x,T y);

或者可以类似非泛型方法,让T去实现IComparable接口当中的int CompareTo(T x)

泛型类

class classname<T1,T2…>

在不确定T1具体类型的情况下,默认只能只读访问。

泛型类关键字

default //可以根据类型把默认值赋值给变量(0/null)
where //对泛型类作约束
class name<t> c where t:C1,C2,C3 表示继承自c,约束限制为C1,C2,C3如果有多个T那么可以使用多个where来限制(where要在继承和实现后面)
class name<t1,t2> where t1:c1,c2 where t2:c3,c4{}
泛型类的构造函数签名依然是name(){}

泛型继承

对于子类而言其泛型类型的约束不能超过父类的泛型约束

泛型接口:interface Interface_name 其规则与泛型继承一致

泛型方法

返回类型 function(参数列表) 这里的T可以用于返回类型,参数类型的使用。同样可以使用约束去约束泛型方法的参数类型

如果在泛型类中使用泛型方法需要注意方法的泛型参数字母需要和类的泛型参数不同

对于泛型编程,在未知类型的情况下只能将其视为object的子类,只能使用object的四个方法(GetType,ToString),所以要事先做一步类型判断

泛型委托

delegate T function(对应参数列表) 其中委托后也可以再跟约束

变体

作用是将接口泛型类型参数作为变体参数,用于处理泛型接口泛型参数的继承问题,使得可以相互赋值

协变:

super实现了interface,base实现了interface

class super:base{}
interface<base> b;
interface<super> s=new super();
b=s;//通过协变使得如此的接口赋值能够成立

使得子类参数泛型接口的量能够赋值给父类参数泛型接口的量,定义方法:

interface 协变参数只能用作接口方法的返回值或者属性的get访问器

抗变:

处理与协变类似的问题,区别在于

interface 抗变参数只能用作参数列表类型

异常处理

try{其中包含发生异常的代码}

catch(包含对应的异常类型)when(异常过滤器,一个判断条件){处理异常}

finally{最终都会执行的代码块}

发生异常后后面的语句就都不执行了,直到遇到第一个能够处理的catch位置

抛出异常(通用做法)

throw new Exception(string? str/Exception exp)

C#高级技术

Var关键字

Var类型变量可以通过你赋给他的值来识别类型,必须初始化,必须是局部量,不能修改类型,同样可以声明var数组,但是该数组必须是同一类型下的

反射与特性

特性是指可以让编译器在编译时知道关于该类的一些信息并且给出措施。通过反射机制可以在运行时动态检查类型信息,由此得到特性信息。特性本身可以用作于类的装饰器

使用方法时[特性名称()] 可选择参数,可以自定义特性

[AttributeUsage(AttributeTargets.Class|ttributeTargets.Method,AllowMultiple=false)]

表示目标为类和方法,不允许多次应用的特性,接下来下面跟着一个自定义构造函数的类即可

[AttributeUsage(AttributeTargets.Class|ttributeTargets.Method,AllowMultiple=false)
public class Range{
    public int min{set;get;}
    public int max{set;get;}
}
//使用方法如下
 [Range(min=10,max=30)]
 public class People(){}
//通过转换成Type方法得到对应的特性信息
Type classType=typeof(People) 
object[] Attributes=classType.GetCustomAttrbutes(true);//得到自定义特性

并发相关

lock(instance){ … }

对涉及同步问题的实例需要加锁处理

多线程

线程的五大生命周期 未启动 就绪 挂起 运行 死亡

System.Threading 名称空间

Thread类

基本使用方法

Thread thread=new Thread(function);
thread.Start();//启动线程

向线程中传递多个参数 线程中返回某一个值 均需要一个线程辅助类来实现

class ThreadFz{
	public int info;
	public string id;
	public ThreadFz(int f,string i)
	{
		info=f;
		id=i;
	}
    //用于处理线程执行的函数
	public void ThreadPro()
    {
        //do something
    }
}

//在线程中返回值,利用委托,在线程辅助类的构造函数中传递一个用于返回/打印特定值的函数
public delegate void getValue(int t);
//在类中
public getValue getval;
public ThreadFz(getValue getv)
{
    getval=getv;
}
//在主线程中
ThreadFZ fz=new ThreadFz(delegate(int t){
    Console.WriteLine(t);
})
//调用这个类对象的进程执行函数
Thread thread=new Thread(fz.ThreadPro());

由于线程之间是相互独立的,所以线程之间是无法直接传递数据的,思路有:

设一个全局哈希表,进程号是key,对应数据结构是value(这个value是引用类型的),从而主进程在修改哈希表中元素时相当于就修改了线程中的元素。(因为修改的是引用)

线程池

由于线程的内存开销很大,所以采用线程池可以限制并行线程的数量从而控制内存开销。线程池中的线程执行完后不会销毁,而是在线程池中挂起,如果程序再次申请线程,会从线程池中激活挂起的线程,而不是创建新线程,从而减少了很多内存开销。

当线程达到最大线程数,可以把线程交给系统管理,从而集中精力处理其他任务。

通过ThreadPool 这一静态类来处理进程池

ThreadPool.SetMaxThreads(n,m); //设置最大工作数量
ThreadPool.QueueUserWorkItem(function,pars)//再直接把函数和参数放入池中即可(每加入依次会从线程池中尝试使用一个线程)
    
for(int i=0;i<n;i++)
    ThreadPool.QueueUserWorkItem(function(),pars)//跑n个线程

ThreadPool

多进程

Process

Process p=new Process();
p.StartInfo.FileName = @"D:\Python2\python.exe"; //执行的文件绝对路径,注意这里要给出的是.exe文件
p.StartInfo.Arguments = sArguments;//执行文件的参数
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.Start();//启动进程

C#调用Python脚本

指定Python脚本,名称,命令行参数,python.exe路局,通过多进程的方式,去调用Python的脚本。通过这种方法可以用来执行Python文件

using System;
using System.Collections;
using System.Diagnostics;

namespace Test
{
  class Program
  {
    static void Main(string[] args)
    {
      Process p = new Process();
      string path = "reset_ipc.py";//待处理python文件的路径,本例中放在debug文件夹下
      string sArguments = path;
      ArrayList arrayList = new ArrayList();
      arrayList.Add("com4");
      arrayList.Add(57600);
      arrayList.Add("password");
      foreach (var param in arrayList)//添加参数
      {
        sArguments += " " + sigstr;
      }

      //这段代码的意思相当于表示给定命令参数情况下,运行绝对路径指明的进程。
      p.StartInfo.FileName = @"D:\Python2\python.exe"; //python2.7的安装路径
      p.StartInfo.Arguments = sArguments;//python命令的参数
      p.StartInfo.UseShellExecute = false;
      p.StartInfo.RedirectStandardOutput = true;
      p.StartInfo.RedirectStandardInput = true;
      p.StartInfo.RedirectStandardError = true;
      p.StartInfo.CreateNoWindow = true;
      p.Start();//启动进程

      Console.WriteLine("执行完毕!");

      Console.ReadKey();
    }
  }
}

C# 常用的IO内容

均需要IO包

File

该类型提供了用于文件读写的静态函数

Exists
Create
Open
ReadAllText
ReadAllLines
ReadAllBytes
WriteAllText
WriteAllLines
WriteAllBytes
AppendAllLines
AppendAllText

Directory

该类型提供了用于目录读写的静态函数

Exists
Delete
CreateDirectory
GetDirectories

MemoryStream

用来对内存进行读写操作,往往用来作为流读写的中间件(相当于将内存作为读写的缓冲,因为快?),常用方法与例子如下:

MemoryStream ms=new MemoryStream(int size);//声明一块可扩展大小的用来读写的内存区域
ms.Write(str,0,str.Lenght);//将str(byte数组)中,从0开始长度为Length的数据内容写入内存
ms.Seek(offset,loc);//可以设定目前内存流要读写的位置,loc是可以是起点,终点或者当前位置,offset是loc的偏移量
ms.Read(buffer,0,size);//ms当前流中长度为size的内容会写入到buffer中0到size-1的位置
ms.ToArray();//目前ms中的内容会全部被返回成一个字节数组;

BinaryReader

用来读取二进制对象,通过传递FileStream(文件流对象)用来读取。常用函数与例子如下:

//二进制文件读写处理
BinaryReader reader=new BinaryReader();
reader.Read(byte[] buffer,int start,int count);
ReadBoolean()
ReadByte(int)
ReadChar()
ReadDouble()
ReadString()
...
//例子,利用MemoryStream作读写文件的中间状态来读写
//相当于把文件中的所有内容读入内存当中后,再返回一个大的bytes数组,从而得到文件中的内容
//之所以要经过MemoryStream是因为内存读写速度足够快,并且可以节省一些中间临时文件的开销
const int bufferSize = 4096;
using (var ms = new MemoryStream())
{
    byte[] buffer = new byte[bufferSize];
    int count;
    while ((count = reader.Read(buffer, 0, buffer.Length)) != 0)
        ms.Write(buffer, 0, count);
    return ms.ToArray();
}

BitConverter

StreamReader

通过一种特定的编码方式,从字节流中读取char信息,常用于网络通信中(比如流式传输的TCP连接)

常用方法与例子如下:

StreamReader reader=new StreamReader(Stream,Encoding);//构造函数为目标流以及编码方式(可选)
ReadLine();//从当前流中读取数据并且返回一行内容

StreamWriter

# C# 常用数学处理

C#时间处理

## DateTime

对于超高精度计时来说,可以使用.net自带的DateTime.Now.Ticks,1 ticks等于100纳秒,相当于10^4 ticks 等于1毫秒,从而5000ticks单位就相当于是0.5毫秒,可以使用ticks之差来做超高精度的计时。具体如下所示:

while (DateTime.Now.Ticks - current <= 6000) ;
current = DateTime.Now.Ticks;

Timer

C#中常用的计时器模块,最高进度可到毫秒,更小的精度要通过DateTime.Now.Ticks或者系统调用来实现。具体的使用方法如下:

Timer timer = new Timer();
timer.Elapsed += delegate (Object source, ElapsedEventArgs e) { Console.WriteLine("Signal"); };
timer.Interval = 2000;
timer.Enabled = true;
timer.AutoReset = true;
timer.Start();
Console.WriteLine("Start_Timer");
while (true) {; }

对于硬件与硬件间需要超高精度的响应时间往往使用DateTime.Now.Ticks,一般的用户级程序往往直接使用TImer即可

猜你喜欢

转载自blog.csdn.net/qq_38069320/article/details/106390602