目录
5类和继承
5.1类继承
5.2访问继承的成员
5.3屏蔽基类的成员
5.4访问基类的成员
5.5虚方法与覆写方法
5.6构造函数的执行顺序
5.7成员访问修饰符
5.8抽象类
5.9密封类与静态类
6.表达式与运算符
6.1运算符和重载
7.结构
7.1结构体的感念。
7.2结构构造函数与析构函数
7.3属性与字段初始化语句
8.枚举
8.1枚举
8.2设置底层类型和显示值
8.3关于位标识
9.委托
9.1委托的概念[请把委托看成一个类型安全的C++的函数指针(但有所不同)]
9.2 声明委托类型
9.3创建委托变量
9.4组合委托
9.5为委托添加方法
9.6 为委托移除方法
9.7调用委托
9.8
9.9匿名方法以及Lambda表达式
Lambda表达式
5类和继承
5.1类继承
(继承的优点就不更多的赘述,在这里主要说他的用法)
通过继承可以定义一个新类,新类纳入一个已经声明的类并进行扩展。
可以使用一个已经存在的类作为新类的基础。已存在的类称为基类(base class),新类称为派生类(derived class)。派生类成员的组成如下:
(1)本身声明中的成员;
(2)基类的成员。
要声明一个派生类,需要在类名后加入基类规格说明。基类规格说明由冒号和用作基类的类名称组成。派生类直接继承自列出的基类。
(1)派生类扩展它的基类,因为它包含了基类的成员,还有它本身声明中的新增功能。
(2)派生类不能删除它所继承的任何成员。
例如,下面展示了名为 OtherClass 的类的声明,它继承自名为SomeClass的类:
class OtherClass : SomeClass //基类规格说明
{
....
}
5.2访问继承的成员
using System;
class SomeClass//基类
{
public string Field1 = "base class field";
public void Method1(string value)
{
Console.WriteLine($"Base class--Methodi:{value}");
}
}
class OtherClass : SomeClass
{//派生类
public string Field2="derived class field";
public void Method2(string value)
{
Console.WriteLine($"Derived class--Method 2:{value}");
}
}
class Program
{
static void Main()
{
OtherClass oc = new OtherClass();
oc.Method1(oc.Field1);//以基类字段为参数的基类方法,
oc.Method1(oc.Field2);//以派生字段为参数的基类方法
oc.Method2(oc.Field1);//以基类字段为参数的派生方法
oc.Method2(oc.Field2);//以派生字段为参数的派生方法
}
}
5.3屏蔽基类的成员
虽然派生类不能删除它继承的任何成员,但可以用与基类成员名称相同的成员来屏蔽(mask)基类成员。这是继承的主要功能之一,非常实用。
例如,我们要继承包含某个特殊方法的基类。该方法虽然适合声明它的类,但不一定适合派生类。在这种情况下,我们希望在派生类中声明新成员以屏蔽基类中的方法。在派生类中屏蔽基类成员的一些要点如下。
(1)要屏蔽一个继承的数据成员,需要声明一个新的相同类型的成员,并使用相同的名称。
(2)通过在派生类中声明新的带有相同签名的函数成员,可以屏蔽继承的函数成员。
请记住,签名由名称和参数列表组成,不包括返回类型。
(3)要让编译器知道你在故意屏蔽继承的成员,可使用new修饰符。
否则,程序可以成功编译,但编译器会警告你隐藏了一个继承的成员。
(4)也可以屏蔽静态成员。
(5)使用new关键字以显式地告诉编译器屏蔽基类成员。
代码展示:
using System;
class SomeClass//基类
{
public string Field1 = "SomeClass Field1";
public void Method1(string value)
{
Console.WriteLine($"SomeClass.Method1:{value}");
}
}
class OtherClass : SomeClass//派生类
{
new public string Field1 = "OtherClass Field1";//屏蔽基类成员
new public void Method1(string value)//屏蔽基类成员
{
Console.WriteLine($"OtherClass.Method1:{value}"); }
}
class Program
{
static void Main()
{
OtherClass oc = new OtherClass();//使用屏蔽成员
oc.Method1(oc.Field1);//使用屏蔽成员
}
}
运行结果:
Image
5.4访问基类的成员
如果派生类必须访问被隐藏的继承成员,可以使用基类访问表达式。基类访问表达式由关键字base后面跟着一个点和成员的名称组成,如下所示:
Console. WriteLine("{0}", base. Field 1);
基类访问代码: 在下面的代码中,派生类 OtherClass 隐藏了基类中的Field1,但可以使用基类访问表达式访问它。
using System;
class SomeClass
{
public string Field1 ="Field1--In the base class";
}
class OtherClass : SomeClass
{//派生类
new public string Field1="Field1--In the derived class";
public void PrintField1()
{
Console.WriteLine(Field1);//访问派生类Console. Writeline(base. Field 1);//访问基类
}
}
class Program
{
static void Main()
{
OtherClass oc = new OtherClass();
oc.PrintField1();
}
}
5.5虚方法与覆写方法
5.5.1在开始之前首先介绍一些使用基类的引用的知识 派生类的实例由基类的实例和派生类的新增成员组成。派生类的引用指向整个类的对象(包括基类部分)
如果有一个派生类对象的引用,就可以获取该对象基类部分的引用(使用类型转换运算得该引用转换为基类类型)。类型转换运算符放置在对象引用的前面,由圆括号括起的要被转换的类名组成。类型转换将在以后阐述。将派生类对象强制转换为基类对象的作用是产生的量只能访问基类的成员(在被覆写方法中除外,稍后会讨论)。
这里直接看下面的代码
using System;
class MyBaseClass
{
public void Print()
{
Console.WriteLine("This is the base class.");
}
}
class MyDerivedClass : MyBaseClass
{
public int var1;
new public void Print()
{
Console.WriteLine("This is derived class");
}
}
class Program
{
static void Main()
{
MyDerivedClass derived = new MyDerivedClass();
MyBaseClass mybc = (MyBaseClass)derived;
derived.Print();
mybc.Print();
//mybc.var1 = 1;//错误基类的引用无法访问派生类成员
}
}
运行结果:
Image
5.5.2虚方法与覆写
其实,虚方法与覆写就相当于给一个机会给派生类去实现:比如说你有一个动物类,作为你的基类,它其中有一个方法是睡眠,你以动物类为基类派生了一个狮子类,狮子类的睡眠就与动物类的睡眠方法一致,就不用在派生类中再重新书写睡眠这个方法。而你写一个蛇类作为动物类的派生,我们知道蛇类的睡眠肯定与动物类的有所区别,因为蛇在冬天要冬眠。在这样的情况下。我们就可以运用虚方法与腹泻方法,有助于程序的可读性
当使用基类引用访问派生类对象时,得到的是基类的成员。虚方法可以使基类的引用访问“升至”派生类内。 可以使用基类引用调用派生类的方法,只需满足下面的条件。
(1)派生类的方法和基类的方法有相同的签名和返回类型。
(2)基类的方法使用virtual标注。
(3)派生类的方法使用override标注。
代码示例:
using System;
class MyBaseClass
{
virtual public void Print()
{
Console.WriteLine("This is the base class.");
}
}
class MyDerivedClass : MyBaseClass
{
public int var1;
override public void Print()
{
Console.WriteLine("This is derived class");
}
}
class Program
{
static void Main()
{
MyDerivedClass derived = new MyDerivedClass();
MyBaseClass mybc = new MyBaseClass();
derived.Print();
mybc.Print();
}
}
运行结果:
Image
using System;
class MyBaseClass
{
virtual public void Print()
{
Console.WriteLine("This is the base class.");
}
}
class MyDerivedClass : MyBaseClass
{
public int var1;
override public void Print()
{
Console.WriteLine("This is derived class");
}
}
class Program
{
static void Main()
{
MyDerivedClass derived = new MyDerivedClass();
MyBaseClass mybc = (MyBaseClass)derived;
derived.Print();
mybc.Print();
}
}
运行结果:
Image
virtual和override方法代码说明:
注意和上一种情况(用new隐藏基类成员)相比在行为上的区别。
当使用基类引用(mybc)调用Print方法时,方法调用被传递到派生类并执行,因为:基类的方法被标记为virtual;在派生类中有匹配的override方法。
覆写方法可以在继承的任何层次出现:
当使用对象基类部分的引用调用一个被覆写的方法时,方法的调用被沿派生层次上溯执行,一直到标记为override的方法的最高派生(most-derived)版本。如果在更高的派生级别有该方法的其他声明,但没有被标记为override,那么它们不会8被调用。
5.6构造函数的执行顺序
这一块不详细介绍有需要可以看看别人写的(我感觉自己才疏学浅,写出来的东西生涩难懂不如去看看别人写的)
http://t.zoukankan.com/TJessica-p-6366066.html
5.7成员访问修饰符
这部分基本是纯定义部分直接贴图(C#图解教程)QWQ其实是自己懒 (图片有点不清楚了,等我回去重新拍)
5.8抽象类
5.8.1抽象成员
抽象成员是指设计为被覆写的函数成员。抽象成员有以下特征。
(1)必须是一个函数成员。也就是说,字段和常量不能为抽象成员。
(2)必须用abstract修饰符标记。
(3)不能有实现代码块。抽象成员的代码用分号表示。
例如、下面取自一个类定义的代码声明了两个抽象成员:一个名为 PrintStuff 的抽象方法和一个名为 MyProperty 的抽象属性。注意在实现块位置的分号。
关键字分号替换实现
abstract public void PrintStuff (string s);
abstract public int MyProperty
{
get; 一分号替换实现
set; 一分号替换实现
}
抽象成员只可以在抽象类中声明,下一节中会讨论。一共有4种类型的成员可以声明为抽象的:方法;属性;事件;索引器。 关于抽象成员的其他重要事项如下。
(1)尽管抽象成员必须在派生类中用相应的成员覆写,但不能把 virtual修饰符附加abstract修饰符。
(2)类似于虚成员,派生类中抽象成员的实现必须指定override修饰符
5.8.2抽象类
抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。不能创建抽象类的实例。 抽象类使用abstract修饰符声明。
abstract class MyClass
{
}
抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带实现的成员的任意组合。 抽象类自己可以派生自另一个抽象类。
任何派生自抽象类的类必须使用override关键字实现该类所有的抽象成员,除非派生类自己也是抽象类。
5.9密封类与静态类
密封类:
密封类只能被用作独立的类,它不能被用作基类。
密封类使用sealed修饰符标注。
例如,下面的类是一个密封类。将它用作其他类的基类会产生编译错误。
关键字
sealed class MyClass
{
---
}
静态类:
静态类中所有成员都是静态的。静态类用于存放不受实例数据影响的数据和函数。静态类的一个常见用途可能是创建一个包含一组数学方法和值的数学库。
关于静态类需要了解的重要事项如下。
(1)类本身必须标记为static。
(2)类的所有成员必须是静态的。
(3)类可以有一个静态构造函数,但不能有实例构造函数,因为不能创建该类的实例。口(4)静态类是隐式密封的,也就是说,不能继承静态类。
可以使用类名和成员名,像访问其他静态成员那样访问静态类的成员。从C#6.0开始,也可以通过使用using static指令来访问静态类的成员,而不必使用类名。
6.表达式与运算符
这一章的其他内容不过多赘述(跟C++几乎一致);
6.1运算符和重载
C#运算符被定义为使用预定义类型作为操作数来工作。如果面对一个用户定义类型,运算符完全不知道如何处理它。运算符重载允许你定义C#运算符应该如何操作自定义类型的操作数。
(1)运算符重载只能用于类和结构。
(2)为类或结构重载一个运算符x,可以声明一个名称为operator x的方法并实现它的行为(例如:operator+和operator-等)。
一元运算符的重载方法带一个单独的class或struct类型的参数。
二元运算符的重载方法带两个参数,其中至少有一个必须是class或struct类型。
形如:
public static LimitedInt operator-( LimitedIntx )//一元
public static LimitedInt operator+( LimitedIntx , double y)//二元
运算符重载的方法声明需要:
(1)声明必须同时使用static和public的修饰符;
(2)运算符必须是要操作的类或结构的成员。
例如,下面的代码展示了类Limited Int的两个重载的运算符:加运算符和减运算符。你可以说它是负数而不是减法,因为运算符重载方法只有一个单独的参数,因此是一元的,而减法运算符是二元的。
public static LimitedInt operator + ( LimitedIntx , double y)//二元
(必需的) (类型)( 关键字)(运算符) (操作数)
代码展示:
using System;
class LimitedInt
{
const int MaxValue = 100;
const int MinValue = 0;
public static LimitedInt operator -(LimitedInt x)
{
LimitedInt li = new LimitedInt();
li.TheValue = 0;
return li;
}
public static LimitedInt operator -(LimitedInt x,LimitedInt y)
{
LimitedInt li = new LimitedInt();
li.TheValue = x.TheValue - y.TheValue;
return li;
}
public static LimitedInt operator +(LimitedInt x, double y)
{
LimitedInt li = new LimitedInt();
li.TheValue = x.TheValue + (int)y;
return li;
}
private int _theValue = 0;
public int TheValue
{
get { return _theValue; }
set
{
if (value < MinValue)
_theValue = 0;
else
_theValue = value > MaxValue ? MaxValue : value;
}
}
}
class Program
{
static void Main()
{
LimitedInt li1 = new LimitedInt();
LimitedInt li2 = new LimitedInt();
LimitedInt li3 = new LimitedInt();
li1.TheValue = 10;
li2.TheValue = 26;
Console.WriteLine($"li1:{li1.TheValue}, li 2:{li2.TheValue}");
li3 = -li1;
Console.WriteLine($"-{li1.TheValue}={li3.TheValue}");
li3 = li2 - li1;
Console.WriteLine($"{li2.TheValue}-{li1.TheValue}={li3.TheValue}");
li3 = li1 - li2;
Console.WriteLine($"{li1.TheValue}-{li2.TheValue}={li3.TheValue}");
}
}
代码运行结果:
Image
7.结构
7.1结构体的感念。
结构是程序员定义的数据类型,与类非常类似。它们有数据成员和函数成员。虽然与类相似,但是结构有许多重要的区别。最重要的区别是:
(1)类是引用类型,而结构是值类型;
(2)结构是隐式密封的,这意味着不能从它们派生其他结构。
声明结构的语法与声明类相似。
关键字
struct StructName
{
MemberDeclarations
}
代码如下:
using System;
class Program
{
struct Point
{
public int x;
public int y;
}
static void Main()
{
Point node;
node.x = 1; node.y = 1;
Console.WriteLine($"frist point:({node.x},{node.y})");
}
}
7.2结构构造函数与析构函数
跟类的构造函数基本一致,但要注意要调用构造函数则必须要使用new运算符
直接上代码:
using System;
class Program
{
struct Point
{
public int x;
public int y;
public Point(int _x = 0 ,int _y = 0)
{
x = _x;
y = _y;
}
}
static void Main()
{
Point node1;
Point node2 = new Point();
node1.x = 1; node1.y = 1;
Console.WriteLine($"frist point:({node1.x},{node1.y})");
Console.WriteLine($"second point:({node2.x},{node2.y})");
}
}
运行结果:
Image
7.3属性与字段初始化语句
在声明结构体时,不允许使用实例属性和字段初始化语句,如下所示。
struct Simple
{
public int x=40;//编译错误
public int y=10;//编译错误
public int prop1{get;set;}=5;//编译错误
}
但是,结构体的静态属性和静态字段都可以在声明结构体时进行初始化,即使结构体本身不 是静态的.
8.枚举
8.1枚举
枚举是由程序员定义的类型,与类或结构一样。
(1)与结构一样,枚举是值类型,因此直接存储它们的数据,而不是分开存储成引用和数
据。 (2)枚举只有一种类型的成员:命名的整数值常量。
下面的代码展示了一个示例,声明了一个名称为 Traffictight 的新枚举类型,它含有3个成 员。注意成员声明列表是逗号分隔的列表,在枚举声明中没有分号。 关键字枚举名称
enum Trafficlight
{
Green, //逗号分隔,没有分号
Yellow, //逗号分隔,没有分号
Red
}
每个枚举类型都有一个底层整数类型,默认为int。(这里跟C++一致)
(1)每个枚举成员都被赋予一个底层类型的常量值。
(2)在默认情况下,编译器对第一个成员赋值为0,对每一个后续成员赋的值都比前一个
成员多1。
8.2设置底层类型和显示值
可以把冒号和类型名放在枚举名之后,这样就可以使用int以外的整数类型。类型可以是任 何整数类型。所有成员常量都属于枚举的底层类型。
enum TrafficLight : ulong
{
……
}
成员常量的值可以是底层类型的任何值。要显式地设置一个成员的值,在枚举声明中的变量 名之后使用初始化表达式。尽管不能有重复的名称,但可以有重复的值,如下所示。
enum TrafficLight
{
Green=10,
Yellow=15,
Red=15//重复的值
}
8.3关于位标识
一般用法: * 确定需要多少个位标志,并选择一种有足够多位的无符号类型来保存它。
- 确定每个位位置代表什么,并给它们一个名称。 声明一个选中的整数类型的枚举, 每个成员由一个位位置表示。
- 使用按位或( | ) 运算符设置保持该位标志的字中适当的位。
- 使用按位与( & )运算符, 或 HasFlag 方法解开位标志。
例:可以使用HasFlag()函数可以判断标志字是否包含特定位标志,会对结果真假的bool值
class Program
{
[Flags]enum MarkTest
{
first = 0x01,
second = 0x02,
third = 0x04,
fourth = 0x08
}
static void Main()
{
MarkTest ops = MarkTest.second | MarkTest.third;
bool useSecond = ops.HasFlag(MarkTest.second);
bool useFourth = ops.HasFlag(MarkTest.fourth);
MarkTest testFlag = MarkTest.second & MarkTest.third;
bool useSecondAndThird = ops.HasFlag(testFlag);
WriteLine(useSecondAndThird);
WriteLine(useSecond);
WriteLine(useFourth);
}
}
9.委托
9.1委托的概念[请把委托看成一个类型安全的C++的函数指针(但有所不同)]
可以认为委托是持有一个或多个方法的对象。当然,一般情况下你不会想要“执行”一个对象, 但委托与典型的对象不同。可以执行委托,这时委托会执行它所“持有”的方法。同时委托是 引用类型。
下面来看细节。委托和类一样,是一种用户定义类型。但类表示的是数据和方法的集合,而 委托则持有一个或多个方法,以及一系列预定义操作。可以通过以下操作步骤来使用委托。
(1)声明一个委托类型。委托声明看上去和方法声明相似,只是没有实现块。
(2)使用该委托类型声明一个委托变量。
(3)创建一个委托类型的对象,并把它赋值给委托变量。新的委托对象包含指向某个方法的引用,这个方法的签名和返回类型必须跟第一步中定义的委托类型一致。
(4)你可以选择为委托对象添加其他方法。这些方法的签名和返回类型必须与第一步中定义的委托类型相同。
(5)在代码中你可以像调用方法一样调用委托。在调用委托的时候,其包含的每一个方法 都会被执行。
你可以把delegate看作一个包含有序方法列表的对象,这些方法具有相同的签名和返回类 型
(1)方法的列表称为调用列表。
(2)委托持有的方法可以来自任何类或结构,只要它们在下面两方面匹配:
委托的返回类型;
委托的签名(包括ref和out修饰符)。
调用列表中的方法可以是实例方法也可以是静态方法。
(3)在调用委托的时候,会执行其调用列表中的所有方法。
我们将从下面的示例代码开始。
(1)代码开始部分声明了一个委托类型MyDel(没错,是委托类型不是委托对象。我们很快 就会介绍这一点)。
(2)Program类声明了3个方法:PrintLow、PrintHigh和Main。接下来要创建的委托对象 将持有PrintLow或PrintHigh方法,但到底使用哪个要到运行时才能确定。
(3)Main声明了一个局部变量del,它将持有一个Mybel类型的委托对象的引用。这并不 会创建对象,只是创建持有委托对象引用的变量,在几行之后便会创建这个委托对象,并将 其赋值给这个变量。
(4)Main创建了一个Random类的对象,Random是一个随机数生成器类。接着程序调用 该对象的Next方法,将99作为方法的输入参数。这会返回一个介于0到99之间的随机整 数,并将这个值保存在局部变量 randomValue 中。
(5)下面一行检查返回并存储的随机值是否小于50。(注意,我们使用三元条件运算符来返 回两个委托之一。)如果该值小于50,就创建一个MyDel委托对象并初始化,让它持有 PrintLow方法的引用。否则,就创建一个持有PrintHigh方法的引用的MyDel委托对象。
(6)最后,Main执行委托对象del,这将执行它持有的方法(Printlow或 PrintHight )。
代码:
using System;
delegate void MyDel(int value);
class Program
{
void PrintLow(int value)
{
Console.WriteLine($"{value} - Low Value");
}
void PrintHigh(int value)
{
Console.WriteLine($"{value} - High Value");
}
static void Main()
{
Program program = new Program();
MyDel del;
Random rand = new Random();
int randomValue = rand.Next(99);
del = randomValue < 50 ? new MyDel(program.PrintLow) : new MyDel(program.PrintHigh);
del(randomValue);
}
}
运行结果:
Image
9.2 声明委托类型
委托类型必须在被用来创建变量以及类型的对象之前声明。如下示例代码声明了委托类型。
形如:
delegate void MyDel (int x);
(关键字) (签名)
注:虽然委托类型声明看上去和方法的声明一样,但它不需要在类内部声明,因为它是类型声明。
9.3创建委托变量
委托是引用类型、因此有引用和对象。在委托类型声明之后,我们可以声明变量并创建类型的对象。如下代码演示了委托类型的变量声明:
形如:
MyDel delVar;
(委托类型) (变量)
有两种创建委托对象的方式,第一种是使用带new运算符的对象创建表达式,如下面的代码所示。new运算符的操作数的组成如下。 (1)委托类型名。 (2)一组圆括号,其中包含作为调用列表中第一个成员的方法的名称。 该方法可以是实例方法或静态方法。
形如:
delVar = new MyDel( myInstObj.MyM1 );//创建委托并保存引用
dVar=new MyDel (SClass.OtherM2);//创建委托并保存引用
静态方法
我们还可以使用快捷语法,它仅由方法说明符构成,如下面的代码所示。这段代码和之前的代码是等价的。这种快捷语法能够工作是因为在方法名称和其相应的委托类型之间存在隐式转换。 形如:
delVar=myInstObj.MyM1;//创建委托并保存引用
dVar = SClass.0therM2;//创建委托并保存引用
9.4组合委托
委托可以使用额外的运算得来“组合”。这个运算最终会创建一个新的委托,其调用列表连接了作为操作数的两个委托的调用列表副本。
例如:
MyDel delA=myInstObj.MyM1;
MyDel delB=SClass.OtherM2;
MyDel delC = delA + delB;//组合调用列表
尽管术语组合委托(combining delegate)让我们觉得好像操作数委托被修改了,但其实它们并没有被修改。事实上,委托是恒定的。委托对象被创建后不能再被改变。
9.5为委托添加方法
C#提供了看上去可以为委托添加方法的语法,即使用+=运算符。
MyDel delVar = inst.MyM1;
delVar +=SC1.m3;
delVar +=X.Act;
在使用+=运算符时,实际发生的是创建了一个新的委托,其调用列表是左边的委托加上右边方法的组合。然后将这个新的委托赋值给delVar。
你可以为委托添加多个方法。每次添加都会在调用列表中创建一个新的元素。
9.6 为委托移除方法
可以使用-=运算符从委托移除方法。如下代码演示了--运算符的使用
与为委托添加方法一样,其实是创建了一个新的委托。新的委托是旧委托的副本——只是没有了已经被移除方法的引用。
如下是移除委托时需要记住的一些事项。
(1)如果在调用列表中的方法有多个实例,-=运算符将从列表最后开始搜索,并且移除第一个与方法匹配的实例。
(2)试图删除委托中不存在的方法将无效。
(3)试图调用空委托会抛出异常。可以通过将委托和null进行比较来判断委托的调用列表是:否为空。如果调用列表为空,则委托是null。
代码:
using System;
delegate void MyDel(int value);
class Program
{
void PrintLow(int value)
{
Console.WriteLine($"{value} - Low Value");
}
void PrintHigh(int value)
{
Console.WriteLine($"{value} - High Value");
}
static void Main()
{
Program program = new Program();
MyDel del = new MyDel(program.PrintHigh);
int randomValue = 121;
del += program.PrintLow;
del -= program.PrintHigh;
del(randomValue);
}
}
运行结果
Image
9.7调用委托
(1)可以通过两种方式调用委托。一种是像调用方法一样调用委托,另一种是使用委托的Invoke方法。
(2)如下面的代码块所示,可以将参数放在调用的圆括号内。用于调用委托的参数作用于调用列表中的每个方法(除非其中一个参数是输出参数,稍后将介绍)。
(3)如果一个方法在调用列表中多次出现,则在调用委托时,每次在列表中遇到该方法时都会调用它。
(4)调用时委托不能为空(null),否则将引发异常。可以使用if语句进行检查,也可以使用空条件运算符和Invoke方法。
9.8
剩下包括带返回值的委托,带引用参数的委托的使用方法与上面一致
9.9匿名方法以及Lambda表达式
匿名方法样例
using System;
delegate void MyDel(ref int value);
class Program
{
void add_10_1(ref int x)
{
x += 10;
}
static void Main()
{
Program program = new Program();
MyDel del = delegate (ref int x) { x += 10; };
int num = 0;
Console.WriteLine($"number is {num}");
program.add_10_1(ref num);
Console.WriteLine($"number is {num}");
del(ref num);
Console.WriteLine($"number is {num}");
}
}
我们可以在如下地方使用匿名方法。
(1)声明委托变量时作为初始化表达式。
(2)组合委托时在赋值语句的右边。
(3)为委托增加事件时在赋值语句的右边。第15章会介绍事件。
匿名方法表达式的语法包含如下组成部分。
(1)delegate类型关键字。
(2)参数列表,如果语句块没有使用任何参数则可以省略。
(3)语句块,它包含了匿名方法的代码。
delegate ( Parameters ){ ImplementationCode }
(关键字)(参数列表) (语句块)
返回类型 匿名方法不会显式声明返回值。然而,实现代码本身的行为必须通过返回一个与委托的返回类型相同的值来匹配委托的返回类型。如果委托有void类型的返回值,匿名方法就不能返回值。
Lambda表达式
C#3.0引入了Lambda表达式;
简化了匿名方法的语法,从而避免包含这些多余的信息。我们可能会希望使用Lambda表达式来替代匿名方法。其实,如果先引入了Lambda表达式,那么就不会有匿名方法。
在匿名方法的语法中,delegate关键字有点多余,因为编译器已经知道我们在将方法赋值给委托。我们可以很容易地通过如下步骤把匿名方法转换为Lambda表达式:
(1)删除delegate关键字;
(2)在参数列表和匿名方法主体之间放置Lambda运算符=>。Lambda运算符读作“goes to”。
如下代码演示了这种转换。第一行演示了将匿名方法赋值给变量del。第二行演示了同样的匿名方法在被转换成Lambda表达式之后,赋值给了变量let。
MyDel del = delegate(int x){{return x+1;};//匿名方法
MyDel let =(int x)=>{ return x + 1;} ;//Lambda表达式
这种简单的转换少了一些多余的东西,看上去更简洁了,但是只省了6个字符。然而,编译器可以推断更多信息,所以我们可以进一步简化Lambda表达式,如下面的代码所示。
(1)编译器还可以从委托的声明中知道委托参数的类型,因此Lambda表达式允许省略类型参数,如le2的赋值代码所示。
带有类型的参数列表称为显式类型。
省略类型的参数列表称为隐式类型。
(2)如果只有一个隐式类型参数,我们可以省略两端的圆括号,如le3的赋值代码所示。(3)最后,Lambda表达式允许表达式的主体是语句块或表达式。如果语句块包含了一个返回 语句,我们可以将语句块替换为return关键字后的表达式,如le4的赋值代码所示。
MyDel del=delegate(int x)={return x+1;};...//匿名方法
MyDel le1 = (int x)=>{ return x+1;}; //Lambda表达式
MyDel le2 = (x) =>{return x+1;} ; //Lambda表达式
MyDel le3 = x => {return x + 1;} ; //Lambda表达式
MyDel le4 = x =>x + 1; //Lambda表达式