C#知识补充(二)——命名空间、泛型、委托和事件
写在前面:
写本系列(自用)的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解,方便自己以后快速复习,减少遗忘。C#中某些内容在初学的时候没学明白,在这里巩固一下。所以知识点不会连贯,是零散的。
六、命名空间
命名空间就是namespace语句块内包裹的代码。 命名空间可以包裹另一个命名空间。命名空间内的访问修饰符,默认为internal,表示只能在该程序集中使用。其余访问修饰符为public,abstract,sealed,partial。
同名的命名空间代表同一个命名空间,也就是说分开写也同属一个命名空间。
using MyGame; //引用MyGame命名空间
using System;namespace MyGame
{namespace UI{class Image{}}namespace Game{class Image{}}class GameObject{}
}namespace MyGame
{class Player:GameObject{}
}namespace code2
{internal class Program5{static void Main(string[] args){//不同命名空间中相互使用,需要引用命名空间或指明出处GameObject g = new GameObject();//或者MyGame.GameObject g2 = new MyGame.GameObject();//不同命名空间中允许有同名类//指明出处使用MyGame.UI.Image img = new MyGame.UI.Image();}}
}
七、泛型
1、定义
泛型实现了类型参数化,达到了代码重用的目的。通过类型参数化来实现同一份代码上操作多种类型。泛型相当于类型占位符,定义类或方法时使用替代符代表变量类型,当真正使用类或者方法时再具体指定类型。使用泛型可以一定程度避免装箱拆箱。
2、使用
如下所示,接口和类都可以使用泛型,声明类后在后面加上< >即可,class TestClass<T>。< >内传入任意一个大写字符就可指代泛型。之后,类或者接口中所有想要当真正使用类或者方法时再具体指定类型的地方,均使用你定义的泛型即可。
在主函数中使用泛型时,一旦指定了类型,就不能更改了。类在继承泛型接口时,需要指明泛型类型:
TestClass<int> t = new TestClass<int>();
t.value = 10;
namespace code2
{class TestClass<T>{public T value;}interface TestInterface<T>{T Value{get;set;}}class Test : TestInterface<int>{public int Value{ get => throw new NotImplementedException(); set => throw new NotImplementedException();}}internal class Program6{static void Main(string[] args){TestClass<int> t = new TestClass<int>();t.value = 10;Console.WriteLine(t.value);TestClass<string> t2 = new TestClass<string>();t2.value = "hello";Console.WriteLine(t2.value);}}
}
3、普通类中的泛型方法
在普通类中,也可以使用泛型函数。如下例所示,泛型函数中可以包含多个泛型:
namespace code2
{class Test2{public void TestFun<T>(T value){Console.WriteLine(value);}public void TestFun<T>(){T t = default(T);}public T TestFun<T>(string v){return default(T);}//泛型函数public void TestFun<T, K, M>(T t, K k, M m){}}class Test2<T>{public T value;//这种不是泛型函数public void TestFun(T t){}}internal class Program6{static void Main(string[] args){TestClass2<int, string, float, double, bool, TestClass<int>> t3 = new TestClass2<int, string, float, double, bool, TestClass<int>>();Test2<int> tt2 = new Test2<int>();tt2.TestFun(2); }}
}
4、泛型约束
让泛型的类型有一定的限制。
关键字where,泛型约束一共有六种。
值类型: where 泛型字母:struct
引用类型:where 泛型字母:class
存在无参公共构造函数 where 泛型字母:new()
某个类本身或者其派生类 where 泛型字母:类名
某个接口的派生类型 where 泛型字母:接口名
另一个泛型类型本身或者派生类型 where 泛型字母:另一个泛型字母
class TestClass<T> where T : struct
{public T value;}
像这样就完成了泛型约束。
八、委托和事件
委托是函数(方法)的容器,可以理解为表示函数函数(方法)的变量类型。用来存储,传递函数(方法)。委托的本质是一个类,用来定义函数(方法)的类型,不同的函数方法必须对应和各自格式一样的委托
1、使用委托
(1)语法
关键字是delegate。访问修饰符默认为public 在别的命名空间也可以使用,如果是private在别的命名空间就不能使用了。
委托可以声明在namespace和class语句块中。更多会直接写在namespace中。
简单来说,委托的语法就是在函数声明语法前加一个delegate关键字。
(2)简单使用
像这样,delegate void MyFun();就声明了一个无参无返回的委托。delegate int MyFun2(int a);声明的是有一个参数且有返回的委托。需要注意的是,委托是不能重名的。当然,委托是支持泛型的
如delegate T MyFun3<T, K>(T v, K k);
若要使用委托,需要先声明一个委托函数,例如MyFun f = new MyFun(函数名);,括号内传入需要调用的函数,传入后使用方法f.Invoke();
第二种使用方式是,直接MyFun f2 = 函数名;,然后像调用函数一样调用委托f2();
如下例所示:
delegate void MyFun();delegate int MyFun2(int a);delegate T MyFun3<T, K>(T v, K k);internal class Program7
{static void Main(string[] args){MyFun f = new MyFun(Fun);f.Invoke();MyFun f2 = Fun;f2();MyFun2 f3 = Fun2;Console.WriteLine(f3(1));}static void Fun(){Console.WriteLine("12345");}static int Fun2(int value){return value;}
}

(3)使用定义好的委托
使用委托的好处是,可以先处理一些别的逻辑,这些逻辑处理完了,再执行传入的函数。如下例:
class Test
{public MyFun fun;public MyFun2 fun2;public void TestFun(MyFun fun, MyFun2 fun2){int i = 1;i++;fun();fun2(i);}}internal class Program7
{static void Main(string[] args){Test t = new Test();t.TestFun(Fun, Fun2);}
}static void Fun()
{Console.WriteLine("12345");
}static int Fun2(int value)
{return value;
}
2、委托变量可以存储多个函数
MyFun ff = Fun;声明委托后,可以通过+=来存储多个函数ff += Fun;,这样,执行委托时就可以执行这两个函数。
同样的,也可以使用-=来删除存储的函数。多减不会报错。
delegate void MyFun();internal class Program7
{static void Main(string[] args){Console.WriteLine("***********");MyFun ff = Fun;ff += Fun;ff();Console.WriteLine("***********");ff -= Fun;ff();}static void Fun(){Console.WriteLine("12345");}
}

3、系统定义好的委托
为方便使用,系统定义好了委托,可以不使用delegate声明直接使用。
无参无返回:Action;无参有返回,可指定返回类型Func<返回类型>;可以传n个参数的无返回,最多16个Action<n个参数>;可以传n个参数的有返回Func<参数, 返回类型>。如下:
internal class Program7
{static void Main(string[] args){//无参无返回Action action = Fun;action += Fun;action();//无参有返回,可指定返回类型Func<string> func = Fun3;//可以传n个参数的无返回,最多16个Action<int, string, bool> action1;//可以传n个参数的有返回//尖括号内第一个参数是参数,第二个参数是返回值Func<int, int> func2 = Fun2;}static void Fun(){Console.WriteLine("12345");}static int Fun2(int value){return value;}static string Fun3(){return "12";}
}
4、事件
事件是基于委托的存在,是委托的安全包裹,让委托的使用更具有安全性。事件,是一种特殊的变量类型。
(1)使用
事件是作为成员变量存在于类中的,委托怎么用,事件就怎么用。
事件的声明语法是: 访问修饰符 event 委托类型 事件名。相当于在声明委托的时候,在访问修饰符后加上event。
事件相对于委托的区别是:
1、不能在类外部赋值
2、不能在类外部调用
如下所示,在类Test中定义了委托成员变量和事件成员变量,二者的声明方式区别不大
class Test
{//委托成员变量public Action myFun;//事件成员变量public event Action myEvent;public Test(){myFun = TestFun;myEvent = TestFun;}public void TestFun(){}public void DoEvent(){if (myEvent != null){myEvent.Invoke();}}
}
区别在于,事件是不能在外部赋值的,如果取消下例中t.myEvent = null; 的注释,代码就会报错。但是事件可以在外部加减。
事件也不能在外部调用,但是可以在类的内部书写事件调用函数来调用。
internal class Program
{static void Main(string[] args){Console.WriteLine("Hello, World!");Test t = new Test();t.myFun = null;//事件是不能在外部赋值的//t.myEvent = null; //但可以在外部加减t.myEvent += TestFun;t.myFun();//不能在外部调用//t.myEvent();t.DoEvent();}static void TestFun(){}
}
(2)为什么有事件
防止外部随意置空委托、防止外部随意调用委托;事件相当于对委托进行了一次封装,让其更安全。
