kizumi_header_banner_img

Hello! Beautiful Kizumi!

加载中

文章导读

C#


avatar
Yuyas 2025年12月19日 5

事件和委托的区别理解

  • 事件采用发布-订阅设计模式,委托只能叫做往委托里添加方法,不能叫做订阅方法
  • 委托可以在任意地方被调用Invoke,而事件只能在声明事件的类内部被调用
  • 事件不能被直接赋值

委托,事件,匿名函数,lambda表达式:

事件(event)相较于委托(delegate),事件不能在类的外部定义,不能使用=直接赋值。`

事件需要先在类的外部定义一下委托类型,然后在类内部再去使用,当然也可以用自带的Func和Action。

事件的定义为event 委托类型 名称…….

事件只能在声明类内部被调用,在别的类被实例化的话只能+=和-=添加或减少事件里的函数,具体实现看底部与lambda表达式结合的案例代码。

匿名函数(delegate ())离开事件和委托不会被使用。

C#自带的委托有Action(void类型,可在后面加<参数1类型,参数2类型………>)

Func<T1,T2……..,返回类型>这类返回类型不能是void,因为void已经有Action了。所以Func和Action唯一的区别就是返回类型是不是void

这是一个较难理解的例子:

static void Main()
{
   Console.WriteLine(TestFun(2)(3));
}
static Func<int,int> TestFun(int a)
{
   return delegate (int b)
  {
       return a * b;
  };
}

函数TestFun的返回值是一个类型为int的委托,然后在函数的内部又把返回的委托设置为一个匿名函数,所以在调用时实际上是调用了两个函数,我用了两个括号来连续调用。当然也可以先用一个Func<int,int>来接受返回的委托,在对委托进行调用。

lambda表达式的使用与匿名函数十分相似,也是需要用一个容器(事件或者委托)来接受,基本用法如下:

Action<int> ac1 = (int value) =>
{
   Console.WriteLine("返回值为{0}", value);
};

Action<int> ac2 = (value) =>
{
   Console.WriteLine("返回值为{0}", value);
}; //这里的lambda表达式的参数类型可以省略,因为委托已经指定了传参类型为int

Func<int, int> fc = (value) =>
{
   return value;
}; //非void返回类型同理

这里为了更好地解释lambda表达式和事件(主要是事件吧,写这段笔记的时候我认为事件的使用比较难理解)写了一段完整些的代码:

class test
{
   static void Main()
  {
       TestEventClass t1 = new TestEventClass();
       t1.myEvent += t1.testFunc1;
       t1.myEvent += t1.testFunc2;
       t1.myEvent += (value) =>
      {
           Console.WriteLine("lambda,x = {0}", value);
      };
       t1.testEvent(1);
  }
}
class TestEventClass
{
   public event Action<int> myEvent;
   public void testEvent(int x)
  {
       myEvent(x);
  }
   public void testFunc1(int x)
  {
       Console.WriteLine("fun1,x = {0}",x);
  }
   public void testFunc2(int x)
  {
       Console.WriteLine("fun2,x = {0}",x);
  }
}

扩展:当有多个返回值时,默认只能接受末尾函数的返回值,所以需要遍历委托,提取其中某一个函数……..

List的排序

方法1:

如果是自带的数据类型如int,可以直接调用list.Sort();若要降序直接.Reverse()即可;

方法2:

在要排序的类中继承IComparable<T>,然后重写public int CompareTo(T other);

class test
{
   static void Main()
  {
       List<Item> lt = new List<Item>();
       lt.Add(new Item(2));
       lt.Add(new Item(6));
       lt.Add(new Item(1));
       lt.Add(new Item(3));
       for(int i = 0;i < lt.Count;++ i)
      {
           Console.WriteLine(lt[i].money);
      }
       Console.WriteLine("------------------------");
       lt.Sort();
       for (int i = 0; i < lt.Count; ++i)
      {
           Console.WriteLine(lt[i].money);
      }
  }
}
class Item : IComparable<Item>
{
   public int money;
   public Item(int money)
  {
       this.money = money;
  }
   public int CompareTo(Item item)
  {
       if (this.money > item.money) return 1;
       else return -1;
  }
}

方法3(最推荐):

匿名函数写法:

lt.Sort(delegate (Item a,Item b)
{
   if (a.money > b.money) return 1;
   else return -1;
});


lambda表达式写法:

lt.Sort((Item a, Item b) =>
{
   if (a.money > b.money) return 1;
   else return -1;
});

反射

三种获取Type的方式

int a = 1;
Type t1 = a.GetType();
// 1.对象直接调用GetType();

Type t2 = typeof(int);
// 2.使用typeof(对象类型);

Type t3 = Type.GetType("System.Int32");
// 3.使用Type.GetType("命名空间.类名");

// 得到类的程序集信息(不重要)

Console.WriteLine(t1.Assembly);

// 获取所有公共成员
MemberInfo[] infos = t.GetMembers();

// 获取所有构造函数
ConstructorInfo[] cons = t.GetConstructors();

// 获取一个无参构造器
ConstructorInfo con0 = t.GetConstructor(new Type[0]);
TClass t0 = con0.Invoke(null) as TClass;

// 获取一个一参数构造器
ConstructorInfo con1 = t.GetConstructor(new Type[] { typeof(int) });
TClass t1 = con1.Invoke(new object[] { 1 }) as TClass;

因为Invoke可能要求任意数量,任意类型的参数传入,所以我们用object数组来作为参数传入

其中用GetConstructor得到的构造器为object类,用其他类去接受时需要进行类型转化,转化方式通常使用

as (需要转化的类型),若使用传统的前缀强转在转化失败是会抛出异常,而使用前者则会在转化失败时返回null

// 获得所有公共成员变量
FieldInfo[] fd = t.GetFields();

// 获取单个时要指定变量名
FieldInfo fd1 = t.GetField("a");

// 可以通过反射获取的变量来设置其对应实例对象的值
TClass tc = new TClass(1,"123");
fd1.SetValue(tc, 2); // 实例对象,值

// 打印对应的值
Console.WriteLine(fd1.GetValue(tc));

// 获取方法
MethodInfo mi = t.GetMethod("fun1", new Type[] {typeof(int)});

// 调用方法
mi.Invoke(tc,new object[] { 1 });

// 获取程序集并实例化
Assembly assembly = Assembly.LoadFrom(@"D:\C#_learning\类库测试\bin\Debug\类库测试.dll");
Type t = assembly.GetType("类库测试.Player");
object obj = Activator.CreateInstance(t);

// 判断是否是其子类
typeof(父).IsAssignableFrom(typeof(子)) // 返回true
   
// 获取list中的泛型类型,返回值是一个数组,所以要xia
t.GetGenericArguments()[0]

特性

特性的本质是类:

// 限制特性的使用范围,两个bool分别为 是否允许多个特性用于一个目标,是否允许继承
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field,AllowMultiple = true,Inherited = true)]
class MyCustomAttribute : Attribute //继承特性父类Attribute
{
   public string s;
   public MyCustomAttribute(string s)
  {
       this.s = s;
  }
   public void func()
  {
       Console.WriteLine("调用特性func");
  }
}

// 导入dll里的方法,下面的方法为dll里存在的方法
[DllImport("test.dll")]
public static extern int exfun(int a, int b);

// Conditional用于决定是否编译该函数,若需要编译则#define Runfun
[Conditional("RunFun")]
static void fun()
{
   Console.WriteLine("调用了fun方法");
}
static void Main()
{
   Type t = typeof(TClass);
   object[] objs =  t.GetCustomAttributes(false); // 参数为bool表示是否查询父类

   if (t.IsDefined(typeof(MyCustomAttribute), false)) ;
       Console.WriteLine("该类使用了MyCustom特性");

   for (int i = 0; i < objs.Length; ++i)
       if (objs[i] is MyCustomAttribute)
           Console.WriteLine((objs[i] as MyCustomAttribute).s);
   fun();
}
[MyCustom("构造类")] // 自定义名字后缀的Attribute会被省略
class TClass { }

迭代器

主要用于遍历容器

foreach (int i in array)

手动实现标准迭代器

class MyArray : IEnumerable,IEnumerator
{
   public int[] a;
   public int position = -1;

   public MyArray(int[] a)
  {
       this.a = a;
  }


   public object Current
  {
       get
      {
           return a[position];
      }
  }

   public IEnumerator GetEnumerator()
  {
       return this;
  }

   public bool MoveNext()
  {
       position++;
       return position < a.Length;
  }

   public void Reset()
  {
       position = -1;
  }
}

使用语法糖yield实现迭代器

class MyArray : IEnumerable
{
   public int[] a;
   public int position = -1;

   public MyArray(int[] a)
  {
       this.a = a;
  }

   public IEnumerator GetEnumerator()
  {
       for(int i = 0;i < a.Length;++ i)
      {
           yield return a[i];
      }
  }
}

特殊语法

var:

var大概相当于auto,隐式转化,不够直观,所以在协同开发时不建议使用

var也可以匿名类型,但是不能实现方法

var c = new { age = 18, sex = "man" };
Console.WriteLine(c.age);

?置空:

int? a = null; //声明时在类型后面加?使其可以置空,否则会报错
if (a.HasValue) Console.WriteLine(a);
if (a != null) Console.WriteLine(a); // 两种判空方式等价,编译器会自动转换为前者
Console.WriteLine(a.GetValueOrDefault()); // 安全的获取值的方式,若为null则返回0,括号内可传参改变默认返回值

?判空:

object o = null; // object为引用类型,所以可以为null
if (o != null) o.ToString();
o?.ToString(); // 两种方式等价
// 案例2
int[] arr = null;
Console.WriteLine(arr?[0]);

??判空:

int? a = null;
int b;
b = (a == null) ? 100 : a.Value; // 这里a为int?类型变量,不能直接赋值给b,所以要用Value或者强转为int
b = a ?? 100; // 与上方完全等价,a不用进行类型转化因为编译器已经自动隐式转化了

$内插字符串:

string s = "content";
Console.WriteLine($"内插字符串{s}");

值和引用

值类型变量省略

引用类型变量有

string,class,interface,数组,委托

值存放在栈内存中,而引用存放在堆内存中,引用实际上就是个地址,指向对应的值

创建值类型的变量时可以不new,默认为0或者null,而引用类型必须new一个地址给它

如何判断是值还是引用: F12转到定义进入该类型的内部去看,如果是class就是引用,如果是struct就是值



评论(0)

查看评论列表

暂无评论


发表评论

表情 颜文字
插入代码

个人信息

avatar

24
文章
1
评论
1
用户

近期文章