事件和委托的区别理解
- 事件采用发布-订阅设计模式,委托只能叫做往委托里添加方法,不能叫做订阅方法
- 委托可以在任意地方被调用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)
暂无评论