C#(二) C#高级进阶
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
1.C# 委托
C# 中的委托Delegate类似于 C 或 C++ 中的函数指针是一种引用类型表示对具有特定参数列表和返回值类型的方法的引用。简单来说委托是一种方法的代理/中介委托指向某个方法来实现具体的功能。委托是方法的抽象它存储的就是一系列具有相同参数和返回值类型的方法的地址。 委托的使用包括定义声明委托、实例化委托以及调用委托三个阶段在实例化委托时必须将委托的实例与具有相同返回值类型以及参数列表的方法相关联这样就可以通过委托来调用方法。另外使用委托还可以将方法作为参数传递给其他方法。下面是一个委托的形象例子
某人有三子让他们各自带一样东西出门并带回一头猎物。
上面一句话可以理解为父亲对儿子的委托猎物 办法(工具 某工具)-->delegate 猎物(返回值) 带回猎物(委托名)(工具(参数类型) x)-->delegate int GetValue(int i)
三个人执行委托的方法各不相同
兔子 打猎(工具 弓)-public static int GetValue1(int i){ return i; }
野鸡 买(工具 钱)-public static int GetValue2(int i){ return i*2; }
狼 诱捕(工具 陷阱)-public static int GetValue3(int i){ return i*i; }
1.1 声明委托
delegate <return type> delegate-name(<parameter list>)
- return type 为返回值类型
- delegate-name 为委托的名称
- parameter list 为参数列表
注意 委托可以引用与委托具有相同签名的方法也就是说委托在声明时即确定了委托可以引用的方法。
public delegate int MyDelegate (string s);
//上面的委托可被用于引用任何一个带有一个单一的 string 参数的方法并返回一个 int 类型变量。
1.2 实例化委托
委托一旦声明想要使用就必须使用 new 关键字来创建委托的对象同时将其与特定的方法关联。当创建委托时传递到 new 语句的参数为关联的方法名称就像方法调用一样书写但是不带有参数。
public delegate void printString(string s); // 声明一个委托
...
printString ps1 = new printString(WriteToScreen); // 实例化委托对象并将其与 WriteToScreen 方法关联
printString ps2 = new printString(WriteToFile); // 实例化委托对象并将其与 WriteToFile 方法关联
1.3 委托的使用
- Action(arg): 调用委托Action并传入参数arg
- Action.Invoke(arg): 调用委托Action并传入参数arg
- 两种方式的关系所有的委托类型编译器都会自动生成一个
invoke
方法。用委托类型直接加参数是Invoke(参数)的一个捷径。其实等价调用 Invoke();
using System;
delegate int NumberChanger(int n); // 定义委托
namespace c.biancheng.net
{
class Demo
{
static int num = 10;
public static int AddNum(int p){
num += p;
return num;
}
public static int MultNum(int q){
num *= q;
return num;
}
public static int getNum(){
return num;
}
public static void Calculate(NumberChanger nc, int a)
{
nc(a);
}
static void Main(string[] args){
// 创建委托实例
NumberChanger nc1 = new NumberChanger(AddNum);
// 使用委托对象调用方法
// 等价于 nc1.Invoke(25)
nc1(25);
Console.WriteLine("num 的值为: {0}", getNum());
// 使用委托传递方法作为参数
Calculate(MultNum,5)
Console.WriteLine("num 的值为: {0}", getNum());
Console.ReadKey();
}
}
}
1.4 多播委托合并委托
在开发中我们有时候会遇到要通过调用一个委托同时可以执行多个方法的时候就可以考虑用多播委托。委托对象有一个非常有用的属性那就是可以通过使用
+
运算符将多个对象分配给一个委托实例同时还可以使用-
运算符从委托中移除已分配的对象当委托被调用时会依次调用列表中的委托。委托的这个属性被称为委托的多播也可称为组播利用委托的这个属性您可以创建一个调用委托时要调用的方法列表。注意仅可合并类型相同的委托。
多播委托的实质一个委托实例不仅可以指向一个方法还可以指向多个方法。
总结多播委托提供了一种类似于流水线的钩子机制只要加载到这条流水线上的委托都会被顺序执行。因为所有的都继承自MulticastDelegate,因此所有的委托都具有多播特性
using System;
delegate int NumberChanger(int n); // 定义委托
namespace c.biancheng.net
{
class Demo
{
static int num = 10;
public static int AddNum(int p){
num += p;
return num;
}
public static int MultNum(int q){
num *= q;
return num;
}
public static int getNum(){
return num;
}
static void Main(string[] args){
// 创建委托实例
NumberChanger nc;
NumberChanger nc1 = new NumberChanger(AddNum);
NumberChanger nc2 = new NumberChanger(MultNum);
nc = nc1;
nc += nc2;
// 调用多播
nc(5);
Console.WriteLine("num 的值为: {0}", getNum());
Console.ReadKey();
}
}
}
1.5 Func与Action
在上述我们在使用委托的过程中都是自定义一个委托类型再使用这个自定义的委托定义一个委托字段或变量。而在.NetFramework3.0之后又新加入了一种特性C#语言预先为我们定义了两个常用的委托分别是Func和Action还带来了Lambda这使得委托的定义和使用变得简单起来 在以后进行C#程序编写中引入委托更加灵活。
1Action 委托
C#中与预定义了一个委托类型Action其基本特点就是可以执行/指向一个没有返回值void的方法。是一类没有输出参数的委托。
- 原生Action: public delegate void Action(); 封装一个方法该方法不具有参数且不返回值。
- 泛型Action: public delegate void Action(T arg1,…); 封装一个方法该方法具有参数并且不返回值(最多可有16个参数)
static void printString()
{
Console.WriteLine("Hello World");
}
static void printNumber(int x)
{
Console.WriteLine(x);
}
static void Main(String[] args)
{
//Action基本使用
Action a = printString;
a(); // 输出结果 Hello World
//Action指向有参数的方法
Action<int> b = printNumber; // 定义一个指向 形参为int的函C#数
b(5); // 输出结果 5
}
2Func 委托
委托类型Func同样也是C#中与预定义的委托其基本特点就是可以执行/指向一个具有返回值的方法。与Action不同的是Func只有带泛型的一种形式。
- 无参 Funcpublic delegate TResult Func();封装一个方法该方法不具有参数且返回由
TResult
参数指定的类型的值。- 有参 Funcpublic delegate TResult Func<in T,int T,…,out TResult>(T arg1,T arg2,…);封装一个方法该方法具有参数且返回由
TResult
参数指定的类型的值。(最多可有16个参数)- 注意泛型列表中最后一个泛型参数代表返回类型前面的都是参数类型参数类型必须跟指向的方法的参数类型按照顺序一一对应。
namespace delegatesample_10._15_
{
class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
//声明Func
Func<int, int, int> func1 = new Func<int, int, int>(calculator.Add);
Func<int, int, int> func2 = new Func<int, int, int>(calculator.Sub);
int x = 100;
int y = 200;
int z1 = 0;
int z2 = 0;
z1 = func1.Invoke(x, y);
Console.WriteLine(z1);
z1 = func1(x, y);
Console.WriteLine(z1);//都是Add方法的调用输出两遍。
z2 = func2.Invoke(x, y);
Console.WriteLine(z2);
z2 = func2(x, y);
Console.WriteLine(z2);//都是Sub方法的调用输出两遍。
}
}
class Calculator
{
public int Add(int a,int b)
{
int result = a + b;
return result;
}
public int Sub(int a,int b)
{
int result = a - b;
return result;
}
}
}
2.C# 类型转换
2.1 类型检查 is
- is 检查对象是否与给定类型兼容。
object objTest = 11;
if( objTest is int )
{
int nValue = (int)objTest;
}
2.2 自动转换 as
- as 用于在兼容的引用类型之间自动执行转换。如果是as会返回对同一个对象的一个非null的目标引用如果不兼容于目标类型as运算符会返回null。
Employee myEmployee = myObject as Employee;
if (myEmployee != null) {
...
}
注意 用as来进行类型转换的时候所要转换的对象类型必须是目标类型或者转换目标类型的派生类型但不能应用在值类型数据。参考C#中对象或普通类型进行类型转换
3.C# 匿名函数/方法
在 C# 中可以将匿名函数简单的理解为没有名称只有函数主体的函数。匿名函数提供了一种将代码块作为委托参数传递的技术它是一个“内联”语句或表达式可在任何需要委托类型的地方使用。匿名函数可以用来初始化命名委托或传递命名委托作为方法参数。需要注意的是无需在匿名函数中指定返回类型返回值类型是从方法体内的 return 语句推断出来的。匿名函数包括 Lambda 表达式和普通匿名函数两种声明方式。
- 普通匿名函数使用 delegate 关键字创建委托实例来声明。 = delegate (string str){ return str;};
- Lambda表达式 = (参数列表 input-parameters) => { 函数体 expression; }; 括号内多个输入参数使用逗号加以分隔
class Test
{
delegate void TestDelegate(string s);
static void M(string s)
{
Console.WriteLine(s);
}
static void Main(string[] args)
{
//委托实例化形式1普通的委托new方式
TestDelegate testDelA = new TestDelegate(M);
//委托实例化形式2普通匿名函数方式
TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); };
//委托实例化形式3Lambda表达式方式
TestDelegate testDelC = (x) => { Console.WriteLine(x); };
testDelA("Hello. My name is M and I write lines.");
testDelB("That's nothing. I'm anonymous and ");
testDelC("I'm a famous author.");
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
4. C# 属性
属性语法是字段的自然延伸。 字段用来定义存储位置而属性定义包含
get
和set
访问器的声明至少有一个这两个访问器用于检索该属性对应的字段的值以及对其赋值。访问属性时其行为类似于字段。 但与字段不同的是属性通过访问器实现访问器用于定义访问属性或为属性赋值时执行的语句。通过属性可以提供验证、不同的可访问性、迟缓计算或方案所需的任何要求。
1传统属性定义
//传统属性声明
//特点存取时可做逻辑操作但代码量多比较繁琐
class Student
{
//字段
private int _age;
private string _name;
private Gender _gender;
//属性访问器
public int Age { get { return _age; } set { _age = value; } }
public string Name { get { return _name; } set { _name = value; } }
public Gender Gender { get { return _gender; } set { _gender = value; } }
}
2自动属性定义
自动属性有完全没有逻辑的get访问器和set访问器。在访问器后不写函数主体而是直接以分号结尾。编译器会自动生成支持该属性的字段的存储位置这个字段被称为匿名字段仅供该属性访问。匿名字段没有名字所以你不能在代码中亲自使用它。只能通过配套的自动属性间接使用。
//自动属性声明编译器会自动把这段代码翻译成上面的完整版本
//特点减少代码量简化开发但属性定义时不能做其他逻辑判断只能简单存取
class Student
{
public int Age { get; set; }
public string Name { get; set; }
public Gender Gender { get; set; }
}
3 可访问性控制
- 访问权限
可以在访问器前使用访问修饰符但在单个访问器上放置的任何访问修饰符都必须比属性定义上的访问修饰符提供更严格的限制。
public class Person
{
//现在可以从任意代码访问 FirstName 属性但只能从 Person 类中的其他代码对其赋值。
public string FirstName { get; private set; }
// Omitted for brevity.
}
- 只读/只写
还可以直接限制对属性的修改方式即访问器的声明。
//声明只读属性
public ICommand CheckCommand
{
get
{
return new RelayCommand(_ =>
{
//do something...
});
}
}
4抽象属性
抽象类可拥有抽象属性这些属性应在派生类中被实现。下面的程序说明了这点
using System;
namespace runoob
{
public abstract class Person
{
public abstract string Name
{
get;
set;
}
public abstract int Age
{
get;
set;
}
}
class Student : Person
{
private string code = "N.A";
private string name = "N.A";
private int age = 0;
// 声明类型为 string 的 Code 属性
public string Code
{
get
{
return code;
}
set
{
code = value;
}
}
// 声明类型为 string 的 Name 属性
public override string Name
{
get
{
return name;
}
set
{
name = value;
}
}
// 声明类型为 int 的 Age 属性
public override int Age
{
get
{
return age;
}
set
{
age = value;
}
}
public override string ToString()
{
return "Code = " + Code +", Name = " + Name + ", Age = " + Age;
}
}
class ExampleDemo
{
public static void Main()
{
// 创建一个新的 Student 对象
Student s = new Student();
// 设置 student 的 code、name 和 age
s.Code = "001";
s.Name = "Zara";
s.Age = 9;
Console.WriteLine("Student Info:- {0}", s);
// 增加年龄
s.Age += 1;
Console.WriteLine("Student Info:- {0}", s);
Console.ReadKey();
}
}
}
5. C# 事件
事件建立在委托之上与委托不同的是委托是一个行为/动作而事件是一个从动作发生->行为处理的过程是个模型。事件Event 基本上说是一个用户操作如按键、点击、鼠标移动等等或者是一些出现如系统生成的通知。应用程序需要在事件发生时响应事件。
举个例子“发生→响应”中的五个部分闹钟响铃了小明起床。其中4个部分很容易看出闹钟事件源响铃事件小明订阅者起床事件处理行为。其中还有一个隐含的部分为订阅。 也就是小明订阅了他手机的铃声手机响铃时小明才会发生反应。如果是别人的手机小明没有订阅就算响铃小明也不会有反应。
5.1 事件模型
事件在类中声明且生成且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器publisher 类。其他接受该事件的类被称为 订阅器subscriber 类。事件使用 发布-订阅publisher-subscriber 模型。
-
发布器publisher 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器publisher类的对象调用这个事件并通知其他的对象。
-
订阅器subscriber 是一个接受事件并提供事件处理程序的对象。在发布器publisher类中的委托调用订阅器subscriber类中的方法事件处理程序。
5.2 事件声明
在类的内部声明事件首先必须声明该事件的委托类型。例如
public delegate void BoilerLogHandler(string status);
然后声明事件本身使用 event 关键字包装委托
// 基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;
5.3 事件的使用
//事件发布者
public class PublishEvent
{ //拥有事件及其委托
public delegate string Display(string str);
public event Display DisplayEvent;
//事件触发器
public void Shows(string str)
{
if (DisplayEvent != null)
{
DisplayEvent(str);
}
}
}
//事件订阅者
public class Listen1
{
//订阅事件的处理/响应方法
public string MakeAlert(string str)
{
Console.WriteLine(str + "Listen1");
return str + "Listen1";
}
}
class Program
{
static void Main()
{
//1.声明事件发布者
PublishEvent pe = new PublishEvent();
//2.声明订阅者
Listen1 l1 = new Listen1();
Listen2 l2 = new Listen2();
//变量l1和l2订阅了事件当侦听到事件发生时会调用各自的方法响应事件
pe.DisplayEvent += l1.MakeAlert;
pe.DisplayEvent += l2.ShowMsg;
//触发事件->通知订阅->响应事件
pe.Shows("事件");
Console.ReadKey();
}
}
5.4 注意事项
- 事件的本质事件的本质是委托字段的一个包装器是特殊的委托。这个包装是对委托起到限制作用防止对象内部的委托实例被外部乱用封装的一个重要的功能就是隐藏。事件对外界隐藏了委托实例的大部分功能。
- 限制一委托的调用没有限制可以在任意的地方进行调用而事件修饰过的委托只能在定义自己的类内部进行调用。
- 限制二委托可以使用“=”、“+=”、“-=”三种符号来表示赋值添加和移除一个函数而事件修饰过的委托在自己的类中也可以使用这三个符号注意子类也属于外部的类而在外部的类中时则只能使用“+=”和“-=”来添加移除函数不能使用“=”进行直接赋值
- 命名规范用于声明事件的委托一般命名为事件名+EventHandler。事件名+EventHandler的委托参数一般有两个由win32API 演化而来历史悠久
- 第一个参数sender是Object类型表示事件源\事件发送者 。
- 第二个参数e是EventArgs类型 。EventArgs类表示包含事件数据的类的基类并提供用于不包含事件数据的事件的值。
6. 快捷方式
- prop+tab : 自动创建 属性
- propdp+tab : 自动创建 依赖属性及其包装器
- propfull+tab : 自动创建 属性及其字段
- propa+tab : 自动创建 附加属性