前言

关于C#高级语法在重新拾起后发现很多有趣以及好用的特性与语法糖

本篇关于高级语法包括: 属性 常量 字段 泛型 委托 事件 匿名函数 lambda 协变逆变 预处理指令 反射 特性 多线程 迭代器

属性 get set

定义 :属性Property—-看起来像变量的函数 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

partial class Human{
public int Age{
get{
Log($“getAge{age}”);
return age;
}
set{
Log($”setAge{age}=>{value}”);
age=value;
}
}
}
//main
Human human =new human();
human.Age=32;//属性读写

这边定义了一个Person的类

1
2
3
4
5
6
class Person {
private string name;
private int age;
private int money;
private bool sex;
}

对于get set 语句块是可以在返回之前添加一点逻辑规则的(毕竟是
语句块可以类似函数来执行一些操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public string Name{
get
{
return name;
}
set
{
//可以添加代码逻辑
if(value==null){
Console.WriteLine(“你不能名字为空”);
}
else{

//value 关键字用于表示外部传入的值
name=value;
}
}
}

对于get set 的语法块的特性可以将数据进行加密和解密处理
这边可以使用加密解密的算法

1
2
3
4
5
6
7
8
9
10
11
12
public int Money{
get
{
//解密处理
return money-10;
}
set
{
//加密处理
money =value+10;
}
}

自动属性,如果代码中没有特殊处理可以使用自动属性

1
public int age { get; set;}

索引器

可以像数组一样用索引访问其中元素,使程序看起来更直观,更容易编写,也写在get set 语句块中。

定义类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Person {
private string name;
private Person[] friends;
private int[,] array;


public Person this[int index]{
get
{
if(friends == null || friends.Length-1<index)
return friends[index];
}
set
{
if(friends ==null){
friends =new Person[]{ value; }
}else if(index> friends-1){
//定义了一个规则,如果索引越界那么就把最后一个顶掉
friends[friends.Length-1]=value;
}
friends[index]=value;
}
}
//重载索引器
public Person this[int i,int j]{
get{ return array[i,j];}
set{ array[i,j]=value ;}
}
//重载类型也可以使用string
public string this[string str]{
get{
switch(str){
case “name”:
return this.name;
default:
return “”;
}
}
}
}

其他概念与总结

  1. 成员属性概念:一般是用来保护成员变量的
  2. 成员属性的使用和变量一样 外部用对蒙点出
  3. get中需要return内容;set中用value表示传入的内容
  4. get和set语句块中可以加逻辑处理
  5. get和set可以加访问修饰符,但是要按照一定的规则进行添加
  6. get和set可以只有一个
  7. 自动属性是属性语句块中只有get和set,一般用于外部能得不能政这种情况

常量 Const static

常量的基本概念

常量的特点

  • 常量的值必须在编译时确定,简单说就是在定义是设置值,以后都不会被改变了,她是编译常量。

  • 常量只能用于简单的类型,因为常量值是要被编译然后保存到程序集的元数据中,只支持基元类型,如int、char、string、bool、double等。

  • 常量在使用时,是把常量的值内联到IL代码中的,常量类似一个占位符,在编译时被替换掉了。正是这个特点导致常量的一个风险,就是不支持跨程序集版本更新

partial让类的定义放在多个地方

字段

在C#中,字段(Field)是类、结构体或枚举的一个成员,它用于存储某种类型的数据。字段可以是任何有效的C#数据类型,包括基本数据类型(如int、float、bool等)、用户定义的类型(如类、结构体等)、枚举类型或数组等。字段是类的状态的一部分,它们用于存储与类的实例或类型本身相关的信息。

字段通常定义在类的主体内部,作为类的成员。它们可以是静态的(static),这意味着字段属于类本身而不是类的任何特定实例;或者它们可以是实例字段,这意味着每个类的实例都有其自己的字段副本。

下面是一个简单的C#类,其中包含两个字段的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Person
{
// 实例字段
private string name;
private int age;

// 静态字段
public static int totalPersons;

// 构造函数
public Person(string name, int age)
{
this.name = name;
this.age = age;
totalPersons++; // 每次创建Person实例时,增加总人数
}

// 属性,用于访问和修改字段
public string Name
{
get { return name; }
set { name = value; }
}

public int Age
{
get { return age; }
set { age = value; }
}
}

在这个例子中,Person 类有两个实例字段:nameage,它们分别存储人的名字和年龄。还有一个静态字段 totalPersons,用于跟踪创建的 Person 对象总数。

字段通常是私有的(private),这意味着它们只能在类的内部被访问。如果需要从类的外部访问字段的值,通常会使用公共属性(public property)来提供对字段的访问,如上例中的 NameAge 属性。

字段的初始化通常发生在构造函数中,或者通过字段声明时的初始值来完成。例如:

1
2
3
4
5
public class Example
{
// 带有初始值的字段
public int exampleField = 42;
}

在这个例子中,exampleField 字段在声明时被初始化为42。

字段是面向对象编程中封装概念的一部分,它们允许将数据与操作这些数据的方法一起封装在类中,从而隐藏实现细节并暴露有限的接口给类的用户。

泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class TestClass<T,U>
{
public T t;
public U u;

public U TestFun(T t){
return default (U);
}

public V TestFun<K,V>(K k){
return default (V);
}
}

//Main.cs
TestClass<string,int>tc=new TestClass<string,int>;
tc.t=“str”;
tc.u=10;

泛型约束

让泛型的类型有一定的限制
关键字:where

1
2
3
4
5
6
7
8
9
class Test<T> where T: ‘不同约束的替换区域 ’{
public T value;

public void TestFun<K>(K k)where k: ‘不同约束的替换区域 ’{

}

}

泛型约束 语法 其他代码块 Main.cs 注释
值类型 class Test1 where T : struct{ } Test1= new Test1();
Test1=new Test();
int float 是值类型sturct
引用类型 class Test2 where T: class{ } Test2=new Test2(); Random是引用类型class
公共无参构造约束:存在无参公共构造函数非抽象类型 class Test3 where T :new(){ } class Test0{ public Test0(){}}
class Test1{ public Test1(int a){ }}
class Test2 { private Test2(){}}
正确:Test3=new Test3();
错误: Test3=new Test3()
Test0-2只有Test0满足构造函数是公共且无参,这边Test0 也可以省略写构造函数
int作为值类型的构造函数都是公共无参所以可以使用
注意这边不能实例化虚类
类约束:某个类本身或者其派生类(子类) class Test4 where T: Test1{} class child : parent{} 正确:Test 4=new Test4();
错误:Test4=new Test4();
对于Test 1来说 Test2是它的子类可以使用,object是它的父类不能使用
某个接口的派生类型 class Test5 where T: IFly{} interface IFly{}
class Fly:IFly{}
interface IFlyChild :IFly{}
Test 5t=new Test5();
对于value的实例化:
错误:t.value=new Test5();
正确t.value=new Test5 ();
在IFly接口中可以使用本身,子接口,子类,但是在实例化中,接口不能被实例化所以用里式替换,子类Fly代替了IFly;
另一个泛型类型本身或者派生类型 class Test6 <T,U> where T :U{ } class child : parent{} Test 6<parent,child>=new Test6<parent,child>();
Test6<parent,parent>=new Test6<parent,parent>();
在约束中只能使用本身或者子类
多个约束 代码
约束的组合使用 class Test 7 where T:class,new(){} 一起使用的排列组合有可能会报错
多个泛型有约束 class Test8<T,K> where T: class , new() where K: new(){}

多个约束:
可以通过在 where 子句中使用逗号来组合多个约束。例如,可以要求泛型类型参数既实现一个接口又继承一个基类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public class BaseClass { }

public interface IInterface { }

public class DerivedClassAndInterface : BaseClass, IInterface { }

public class GenericClass<T> where T : BaseClass, IInterface
{
public void DoSomething(T instance)
{
// 在这里可以安全地假设 T 是 BaseClass 的派生类,并且实现了 IInterface 接口
}
}

委托Delegate

什么是委托?简单来说,委托类似于 C或 C++中的函数指针,允许将方法作为参数进行传递。

C#中的委托都继承自System.Delegate类型;
委托类型的声明与方法签名类似,有返回值和参数;
委托是一种可以封装命名(或匿名)方法的引用类型,把方法当做指针传递,但委托是面向对象、类型安全的

  • delegate :可以保存多个函数指针 用+= -=进行操作
  • Action :无返回值的delegate
  • Func: 有返回值的delegate
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
            //定义委托类CalcFunc
    delegate float CalcFunc(float val1 ,float val2);
    // 初始化listenerFunc为 Add 函数
    CalcFunc listenerFunc = Add;
    // 调用 listenerFunc 并打印结果
    listenerFunc(3, 4);

    // 3+4=7(Add函数)

    // 将 listenerFunc 加一个 Sub 函数
    listenerFunc += Sub;
    // 调用 listenerFunc 并打印结果
    listenerFunc(3, 4);

    //3+4=7 (Add函数)
    //3-4=-1(Sub函数被加入)

    // 将 listenerFunc 减去一个 Sub 函数
    listenerFunc -= Sub;
    // 调用 listenerFunc 并打印结果
    listenerFunc(3, 4);

    //3+4=7 (此时只运行了Add函数)

    // Add 函数
    static int Add(int a, int b) =>Console.WriteLine($“{a}+{b}={a+b}”);

    // Sub 函数
    static int Sub(int a, int b) => Console.WriteLine($”{a}-{b}={a-b}”);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Action<string>myPrint= (info)=>{ 
Console.WriteLine(info);
}
// 打印 "Hello, 世界"
myPrint("Hello, 世界");

//此时Action 为void 只有运行函数中的语句

// 定义一个返回两个浮点数乘积的函数
Func<float, float, float> funcMul = (a, b) => a * b;
// 打印乘积函数的调用结果
myPrint(funcMul(3, 4).ToString());

// 12

事件event

  • 事件是基于委托的存在
  • 事件的委托的安全包裹
  • 让委托更具有安全性
  • 事件是一种特殊的变量类型

申明语法:
访问修饰符 event 委托类型 事件名;
事件的使用:

  1. 事件是作为 成员变量存在于类中
  2. 委托怎么用 事件就怎么用
    事件相对于委托的区别:
  3. 不能在类外部 赋值
  4. 不能在类外部 调用
    注意:
    只能作为成员存在与类和接口以及结构体中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Test {
//委托成员变量,用于存储函数的
public Action myFun;
//事件成员变量,用于存储函数的
public event Action myEvent;

public Test(){
//事件使用和委托一模一样的 只是有些细微差别
myFun=TestFun;
myFun+=TestFun;
myFun-=TestFun;
myFun();
myFun.Invoke();
myFun=null;

myEvent=TestFun;
myEvent+=TestFun;
myEvent-=TestFun;
myEvent();
myEvent.Invoke();
myEvent=null;
}
public void TestFun(){
Console.WriteLine(“hello world”);
}
public void DoEvent(){
if(myEvent==null) return
myEvent();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Program{
static void Main(string[] args){
Test t =new Test();
//委托可以在外部赋值
t.myFun()=null;
t.myFun()=TestFun;

//事件是不能在外部赋值的 (以下俩个会报错)
//t.myEvent=null;
//t.myEvent=TestFun;

//虽然不能直接复制但是可以使用加减 来添加移除记录的函数
t.myEvent+=TestFun;
t.myEvent-=TestFun;


//委托可以在外部调用
t.myFun();
t.myFun.Invoke();
//事件不能在外部调用(报错)
//t.myEvent();

//需要在类中封装 调用
t.DoEvent();

}
}

为什么有事件

  1. 防止外部随意调用委托
  2. 防止外部随意置空委托
  3. 事件相当于对委托进行了一次封装,让其更安全

我自己认为事件是一种类似私有类的委托

匿名函数

顾名思义,就是没有名字的函数
匿名函数的使用主要是配合委托和事件进行使用
//脱离委托和事件 是不会使用匿名函数的

知识点二 基本语法
//delegate(参数列表)
//
//函数逻辑
//};
何时使用?

  1. 函数中传递委托参数时
  2. 委托或事件赋值时

这里声明了一个委托Action(无返回值的委托) 然后赋值(初始化)为匿名函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 //Action.cs
//无参无返回
Action a =delegate(){
Console.WriteLine(“ 匿名函数逻辑”);
}
//有参无返回
Action<int,string>b=delegate(int a ,string b){
Console.WriteLine(a);
Console.WriteLine(b);
}
//无参有返回
Func<string>c=delegate(){
return “2333”;
}

//Test.cs
class Test{
public Action action;

public void Dosomething(int a,Action fun){
Console.WriteLine(a);
fun();
}
public Action GetFun(){
if(action== null) return null;
return delegate(){
Console.WriteLine(“返回匿名函数的逻辑”);
};
}
}

//Main.cs
a(); //调用委托
Test t=new Test();
t.Dosomething(100,delegate(){
Console.WriteLine(“随参数传入的匿名函数逻辑”);
});

lambda表达式

使用上和匿名函数一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Action a1 =()=>{  Console.WriteLine(“a1”);};

//带参数
Action<int> a2 =(int value)=>{ Console.WriteLine(“a2 {0}”,value);}

a2(200);

//自动识别参数类型
Action<int> a3 =(value)=>{ Console.WriteLine($”a3 {value}”;)}

a3(300);

//有返回值
Func<string,int>a4 = (value){
Console.WriteLine(“a4{0}”,value);
return 1;
}

Console.WriteLine(a4(“666”));

闭包:
内层函数可以引用包含在它外层的函数的变量
即使外层函数的执行已经终止
注意:
该变量提供的值并非变量创建时候的值,而是在父函数范围内的最终值

1
2
3
4
5
6
7
8
9
10
11
// 定义一个整数变量  
int intVal = 10;
// 定义一个闭包,它捕获了 intVal 的上下文
Action closure = () =>{
intVal+=10;
}
// 执行闭包
closure();
Console.WriteLine(intVal);

//20
  •  共享的局部变量i被提升为公共字段(static)
  • 变量i的生命周期延长了;
  • for循环结束后字段i的值是10了;
  • 后面再次调用委托方法,肯定就是输出10了;
1
2
3
4
5
6
7
8
9
Action action;
for(int i=0;i<10;i++){

action+=()=>{
Console.WriteLine(i);
};
}

//CMD: 10 10 10 10 10 10 10 10 10 10

那该如何修正呢?很简单,委托方法使用一个临时局部变量就OK了,不共享数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

for(int i=0;i<10;i++){

//此index非彼index
int index=i;

action+=()=>{
Console.WriteLine(index);
}
}

//CMD:0 1 2 3 4 5 6 7 8 9


协变逆变

什么是协变逆变

里氏替换原则

  • 其核心思想是“子类型必须能够替换其基类型”,也就是说,子类对象应该能够替换掉父类对象并且不影响程序的正确性。

  • 子类必须完全实现父类的抽象方法。也就是说,子类不能删除父类中声明的方法,而且子类可以有自己的方法,但不能修改父类的方法。

  • 子类可以有自己的行为,但不能影响父类原有的行为。也就是说,子类可以扩展父类的功能,但不能改变父类原有的功能。

  • 子类的方法的前置条件(即方法的输入参数)不能比父类的方法的前置条件更严格。也就是说,子类的方法的参数范围必须比父类的方法更宽松,这样才能确保子类对象能够替换父类对象。

  • 子类的方法的后置条件(即方法的返回值)不能比父类的方法的后置条件更宽松。也就是说,子类的方法的返回值类型必须比父类的方法更严格,这样才能确保程序的正确性。

协变 逆变
字面意思 和谐的变化,自然的变化 你常规的变化,不正常的变化
关键字 协变:out 逆变:in
解释 子类装父类 父类装子类
exp 比如string 变成 object 比如 object 变成 string

作用

  • 协变和逆变是用来修饰泛型的

  • 用于在泛型中 修饰 泛型字母的

  • 只有泛型接口和泛型委托能使用

在C#中,协变(Covariance)和逆变(Contravariance)是泛型编程的两个重要概念,它们主要涉及到泛型接口、委托和方法中的类型参数的使用方式。

这些概念有助于我们更灵活地处理不同的类型,特别是当涉及到继承关系时。

协变(Covariance)

协变允许我们将派生类型的对象赋值给基类型的引用。在C#中,协变主要应用于泛型接口和委托的返回类型。

协变接口

在C# 4.0及更高版本中,我们可以使用out关键字来标记泛型接口中的协变类型参数。这意味着接口方法的返回类型可以是该类型参数的派生类型。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface ICovariant<out T>
{
T Get();
}

class Base { }
class Derived : Base { }

class BaseProvider : ICovariant<Base>
{
public Base Get() { return new Base(); }
}

class DerivedProvider : BaseProvider, ICovariant<Derived>
{
public new Derived Get() { return new Derived(); }
}

在上面的例子中,ICovariant<out T>是一个协变接口,它的Get方法返回一个T类型的对象。由于DerivedBase的派生类,因此DerivedProvider类可以隐式地实现ICovariant<Derived>接口,即使它的基类BaseProvider只实现了ICovariant<Base>

协变委托

类似的,协变也适用于委托。在委托的返回类型上使用out关键字,可以允许委托指向返回派生类型的方法。

逆变(Contravariance)

逆变允许我们将基类型的对象赋值给派生类型的引用。在C#中,逆变主要应用于泛型接口和委托的参数类型。

逆变接口

使用in关键字可以标记泛型接口中的逆变类型参数。这意味着接口方法的参数类型可以是该类型参数的基类型。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface IContravariant<in T>
{
void Set(T value);
}

class Base { }
class Derived : Base { }

class BaseConsumer : IContravariant<Base>
{
public void Set(Base value) { /* ... */ }
}

// DerivedConsumer 可以接受 Derived 类型的对象,因为 Derived 是 Base 的派生类
Derived derived = new Derived();
BaseConsumer consumer = new BaseConsumer();
consumer.Set(derived); // 这是合法的,因为发生了逆变

在这个例子中,IContravariant<in T>是一个逆变接口,它的Set方法接受一个T类型的参数。由于DerivedBase的派生类,因此我们可以将Derived类型的对象传递给只期望Base类型参数的Set方法。

逆变委托

类似地,逆变也适用于委托。在委托的参数类型上使用in关键字,可以允许委托指向接受基类型参数的方法。

协变和逆变为C#的泛型编程提供了极大的灵活性,使得我们可以在不改变现有代码的情况下,更加轻松地处理类型之间的关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Father{ }
class Son:Father{ }

delegate T TestOut<out T>();
delega void TestIn<in T>(T t);


TestOut<Son>oS=()=>{return new son();};
TestOut<Father>oF=oS;
Father f=oF;

TestIn<Father>iF=()=>{return new Father();};
TestIn<Son>iS=iF;
is(new Son());

预处理指令

C# 中并没有像 C 或 C++ 中的预处理指令(如 #include, #define, #ifdef 等)那样的功能。C# 是一种更高级的语言,其编译器不处理预处理步骤,而是在编译过程中直接处理源代码。

但是,C# 提供了一些与预处理指令类似的功能或概念,尽管它们的工作方式与传统的预处理指令不同。以下是一些与预处理相关的 C# 功能和概念:

  1. #define 和 #undef: C# 提供了 #define#undef 预处理指令,但它们主要用于条件编译。这些指令通常在项目属性或编译器的命令行参数中设置,而不是在源代码文件中。
1
2
3
4
5
#define DEBUG
// ...
#if DEBUG
Console.WriteLine("Debug mode is on.");
#endif

然而,要注意的是,与 C 和 C++ 中的 #define 不同,C# 中的 #define 并不创建宏或替换文本。它仅仅是一个编译时的标志。
2. 条件编译: 通过 #if, #else, #elif, #endif 指令,你可以在编译时基于某些条件包含或排除代码块。这通常用于调试、平台特定的代码或配置特定的功能。
3. 元数据: C# 使用元数据来描述类型、方法和属性等。这与 C 或 C++ 中的预处理指令不同,但提供了类似的功能,允许在编译时检查类型信息。
4. 编译常量和条件属性: 你可以在项目属性中设置编译常量,或在编译器的命令行参数中设置它们。此外,你还可以使用条件属性(如 [Conditional("DEBUG")])来标记方法,以便它们仅在特定的编译条件下被调用。
5. 区域指令: #region#endregion 指令允许你在 Visual Studio 等 IDE 中折叠和展开代码块,这对于组织大型代码文件非常有用。

总的来说,尽管 C# 没有与 C 或 C++ 完全相同的预处理指令,但它提供了一套功能强大的工具和技术,允许你在编译时根据条件包含或排除代码。

反射

什么是程序集

程序集是经由编译器编译得到的,更进一步编译执行的那个中间产物
在WINDOWS系统中,它一般表现为后缀为.dll(库文件)或者是/exe(可执行文件) 的格式

说人话:
程序集就是我们与的一个代码集合,我们现在写的所有代码
最终都会被编译器翻译为一个程序集供别人使用
比如一个代码库文件 (dll)或者一个可执行文件(exe)

元数据

元数据就是用来描述数据的数据
这个概念上不仅仅用于程序上那个在别的领域也有元数据
说人话:
程序中的类,类中的函数,变量等等信息就是 程序的 元数据
有关程序以及类型的数据被称为 元数据,它们保存在程序集中

反射的概念

程序正在运行时,可以查看其它程序集或者自身的元数据。
一个运行的程序查看本身或者其它程序的元数据的行为就叫做反射
在程序运行时,通过反射可以得到其它程序集或者自己程序集代码的各种信息
类,函数,变量,对象等等,实例化它们,执行它们,操作它们

反射的作用

因为反射可以在程序编译后获得信息,所以它提高了程序的拓展性和灵活性

  1. 程序运行时得得到所有元数据,包括元数据的特性
  2. 程序运行时,实例化对象,操作对象
  3. 程序运行时创建新对象,用这些对象执行任务
方法 描述 示例代码
使用对象的GetType()方法 在任何对象中,可以通过调用GetType()方法来获取该对象的类型。 csharp int a = 42; Type type = a.GetType(); Console.WriteLine(type);
使用typeof关键字 通过typeof关键字并传入类名,可以直接获取到对应类型的Type。 csharp Type type2 = typeof(int); Console.WriteLine(type2);
通过类的全名获取类型 通过Type.GetType()方法,传入包含命名空间的完整类名,可以获取到对应的Type。 csharp Type type3 = Type.GetType("System.Int32"); Console.WriteLine(type3);

在这上面三个例子实际栈中实际指向的堆地址都是一样的

得到类的程序集信息(了解)

Console.WriteLine(type.Assembly);

获取类中所有的公共成员

Type t=typeof(Test);//本文件常用,如果是不同文件的话就用类名找
MemberInfo[]infos=t.GetMembers();

  • 获取类的公共构造函数并调用

你的文字描述中存在一些语法错误和拼写错误,我会尝试将其整理并修改成合理的C#代码。根据你的描述,你想要执行一个无参构造方法和一个有参构造方法,并且打印出构造出来的对象的某个属性。

首先,我们整理并修改无参构造的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 2-1 得到无参构造
ConstructorInfo info = t.GetConstructor(Type.EmptyTypes); // 使用Type.EmptyTypes代替new Type[0]

// 1/执行无参构造
Test obj = info.Invoke(null) as Test; // 调用无参构造方法,传入null作为参数
if (obj != null) // 检查是否成功创建了对象
{
Console.WriteLine(obj.i); // 假设Test类有一个名为i的属性或字段
}
else
{
Console.WriteLine("无法创建对象");
}

然后,我们整理并修改有参构造的部分。这里假设Test类有一个接受intstring类型参数的构造方法,并且你想要构造一个对象并打印它的str属性(假设Test类有这个属性):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 2-2 得到有参构造(一个int参数)
ConstructorInfo info2 = t.GetConstructor(new[] { typeof(int) });
if (info2 != null) // 确保找到了构造方法
{
Test obj2 = info2.Invoke(new object[] { 1 }) as Test; // 调用有参构造方法,传入int参数1
if (obj2 != null)
{
Console.WriteLine(obj2.str); // 打印对象的str属性
}
else
{
Console.WriteLine("无法创建对象");
}
}
else
{
Console.WriteLine("未找到接受一个int参数的构造方法");
}

// 假设还有一个接受int和string参数的构造方法
ConstructorInfo info3 = t.GetConstructor(new[] { typeof(int), typeof(string) });
if (info3 != null)
{
Test obj3 = info3.Invoke(new object[] { 4, "444444" }) as Test; // 调用有参构造方法,传入int和string参数
if (obj3 != null)
{
Console.WriteLine(obj3.str); // 打印对象的str属性
}
else
{
Console.WriteLine("无法创建对象");
}
}
else
{
Console.WriteLine("未找到接受一个int和一个string参数的构造方法");
}

请注意,t应该是一个Type对象,代表你想要反射的类。同时,Test类应该已经定义,并且包含你尝试访问的istr属性或字段。此外,请确保反射的类已经被加载到当前的执行上下文中,否则GetConstructor方法可能会返回null

获取指定名称的公共成员变量

获取类的公共成员方法
https://www.zhihu.com/tardis/zm/art/493738261?source_id=1005

Activator
用于快速实例化对象的类
用于将Type对象快捷实例化为对象
先得到Type
然后 快速实例化一个对象
Type testType=typeof(Test);
//无参构造
Test testObj=Activator.CreateInstance(testType)

特性

在C#中,特性(Attributes)是用于为程序中的类型和成员提供元数据(metadata)的一种机制。特性允许开发人员在源代码中附加声明性信息,以供编译器、运行时和其他工具使用。

以下是关于C#特性的一些重要概念和用法:

  1. 声明特性:特性本身是一种自定义的类,通过在类、方法、属性等声明前面使用方括号并提供特性类的名称来使用特性。例如:

    1
    2
    [Serializable]
    public class MyClass { }
  2. 内置特性:C#提供了一些内置的特性,例如 [Serializable][Obsolete] 等。这些特性可以用来标记类型和成员的属性,告诉编译器或其他工具如何处理它们。

  3. 自定义特性:开发人员可以根据需要创建自定义特性。自定义特性类通常派生自 System.Attribute 类,可以包含字段、属性和方法等。例如:

    1
    2
    3
    4
    5
    [AttributeUsage(AttributeTargets.Class)]
    public class CustomAttribute : Attribute
    {
    // 构造函数、属性和方法等
    }
  4. 应用特性:可以将特性应用于程序中的各种元素,如类、方法、属性、参数等。特性可以使用单个实例应用于多个元素,也可以使用多个实例应用于单个元素。

  5. 获取特性信息:可以使用反射机制在运行时获取特性信息。通过检查类型和成员上的特性,可以动态地获取元数据并做出相应的决策。

  6. 特性参数:特性可以具有参数,这些参数可以在特性的使用中提供。例如:

    1
    2
    [CustomAttribute("example")]
    public class MyClass { }
  7. 使用条件:特性可以具有使用条件,指定了特性可以应用于的元素类型、范围和其他限制条件。

通过使用特性,开发人员可以为代码添加元数据,以提供更多的信息给编译器、运行时和其他工具,从而实现更灵活、更健壮的程序设计和开发。

语法糖

  • var隐式类型:
    var i=5;

var不能作为类的成员只能用于临时变量声明。只能存在于函数语句块中且必须初始化


  • 设置对象初始值
    Person p=new Person{sex=true,Age=18,name=“Frag”};
    Person p=new Person(){sex=true,Age=18,name=“Frag”};
    初始化可以不用写全,且是不用调用构造函数的初始化
    小括号可以加只不过是调用了构造函数

  • 设置集合初始值
    ListlistPerson=new List{
    new Person(200),
    new Person(100){Age=18},
    new Person{sex=true,Age=18,name=“Frag”}
    };
    Dictionary<int,string>=new Dictionary<int,string>{
    {1,”2333”},
    {2,”6666”}
    };

  • 匿名类型
    var变量可以声明为自定义的匿名类型
    var v=new {age=10,money=11,name=“Frag”};
    Console.WriteLine(v.age);

  • 可空类型
  1. 值类型不能为空
    int c=null;(error)

  2. (可以赋值为空)
    int? c=null;

  3. 判断是否为空
    if(c.HasValue){
    Console.WriteLine(c);
    Console.WriteLine(c.value);
    }

  4. 安全获取可空类型值
    Console.WriteLine(c.GetValueOrDefault());
    //0

  5. 指定默认值
    Console.WriteLine(c.GetValueOrDefault(100));
    //100

自动判断是否为空
object o=null;
o?.ToString();
等价于
if(o!=null) o.ToString();

arrayInt?[0].ToString();
action?.Invoke();


  • 空合并操作符
    return left??right;

if(left!=null){
return left;
}else{
return right;
}


  • 内插字符串$
  • Console.WriteLine(“普通字符串{0},value”);
    Console.WriteLine($”内插入字符串{value}”);

  • 逐字字符串@

要实现这一切仅需要在字符串常量的值前加一个符号“@”,以这种形式赋值的字符串叫做逐字字符串,它后面的所有字符都被逐个地收录到字符串的值中!

因此,如果你需要类似“所见所得”效果的赋值,逐字字符串赋值方式会是你的首选!
此外,需要注意的是,当使用符号 “@” 为字符串赋值时,被赋值的所有字符将不需要经过转义——只有双引号这个本身作为界限的字符需要经过转义,此时它的转义输入方法是两个放在一起的双引号:””
在以这种方式给变量赋值时,也只有这唯一一个转义是合法的。如果希望诸如换行、制表符此类字符在字符串体现出来,也可以直接将带有换行与制表符的字面量字符串赋给字符串量;不过这样,就不能在换行前的前一行写注释或其他语句,否则这些语句将被当作字符串值的一部分。

在下面例子中的语句,可以实现不完成输入一系列不经转义的特殊字符:

(1) 逐字:
string NoEscapeFullFileName = @”C:\inetpub\ciznxcom"; // (此句中的反斜扛被直接赋值)

(2)双引号:
string StringWithDbQoute = @”Jim says,””he can reach home in about six minutes””.” //(此句中双引号被转义)

(3)换行: 相当于加一个回车\r 而不是\n
string StringWithNextLineChar = @”We can set a string value for a string variable
with such a “”NextLine”” char.”; // (此句中给字符串变量赋予了回车符,且未经任何转义;并且保持所有空格)

多线程(待更新)

知识点— 了解线程前先了解进程

  • 进程(Process) 是计算机程序中关于某数据集合上的一次运行活动

  • 是系统进行资源分配和调度的基本单位,是操作系统结构的基础

  • 说人话:打开应用程序就是在操作系统上开启了一个进程

  • 进程之间可以相互独立运行,互不干扰

  • 进程之间也可以相互访问、操作

什么是线程

  • 操作系统能够进行运算调度的最小单位。
    它被包含在进程之中,是进程中的实际运作单位
  • 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程
  • 我们目前写的程序 都在主线程中

简单理解线程:

  • 就是代码从上到下运行的一条“管道”

什么是多线程

  • 我们可以通过代码开启新的线程
  • 可以同时运行代码的多条“管道”就叫多线程

在C#中,你可以通过多种方式创建和管理线程。以下是一些基本的线程创建和操作的示例代码:

迭代器