CLR-via-CSharp-06 第10章 属性 第11章 事件

无参属性

建议把所有的字段都设置为false,所有想公开的都设置为public属性,以达到能够封装的目的。

我已经懂了所以基础的就不写了,具体可以自行搜索。(属性都不会你写什么C#)

自动实现的属性

只写了set; get;而没有定义具体set什么get什么的属性,叫做自动实现的属性,C#会自动声明一个私有字段和get、set方法。

比如

1
2
3
4
public class Program
{
public Int32 ID { get; set; }
}

用ILDasm可以看到其实是生成了两个方法get_ID, set_ID,以及一个字段<ID>k__BackingField

看一下get_ID的IL代码:

1
2
3
4
5
6
7
8
9
10
.method public hidebysig newslot specialname virtual final 
instance uint32 get_ID() cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld uint32 ConsoleApp2.Program::'<ID>k__BackingField'
IL_0006: ret
} // end of method Program::get_ID

可以看到这个方法也是specialname的,所以就算你在自己类里面多定义了一个同名方法,也不会被意外调用。

注意:使用AIP(auto implement property)的时候必须同时写set和get。

使用属性的注意事项

属性说到底是个方法,如果消耗很大,千万不要频繁使用。因为说到底是个方法,看起来是个字段,所以最好把它当做一个方法来看,不要乱用。

匿名类型

利用C#对的匿名类型功能,可以用很简洁的语法来自动生命不可变的元组类型。

1
var ol = new { Name = "abo", Age = 99 };

查看ILDasm,可以看到生成了一个匿名类,其中包含了属性AgeName,属性是。这其中还重写了Equals,GetHashCode,ToString方法。

其中Equals方法会比较每个字段是否相同。

由于重写了GetHashCode所以可以被放在哈希表里。

如果定义了两个字段一样的匿名实例,最后其实用的是同一个匿名类。

匿名类经常被用在LINQ中,返回一个匿名类型实例,我们用var去承接就可以了。

有参数性

其实就是给自己加一个索引器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Program
{
private Int32[] _array = new Int32[] { 1, 2, 3, 4 };

public Int32 this[Int32 index]
{
get
{
return _array[index];
}
set
{
_array[index] = value;
}
}

static void Main(string[] args)
{
Program p = new Program();
Console.WriteLine(p[1]);
Console.ReadLine();
}
}

调用属性访问器方法时的性能

对于简单的get和set访问器方法,JIT编译器会将代码内联,避免了性能上的损失。

属性访问器的可访问性

可以为set或者get设置不同的访问性,但是只能同时在内部定义其中一个。如果想定义两个不同的访问性,应该这样:

1
2
3
4
5
public bool Property
{
get; //跟随外面的访问性,为public
private set; // 为private
}

下面讲11章 事件。

常见事件用法

先贴一下所有IL命令的reference文档:OpCodes Class

这本书对事件写得异常复杂,我这里就写一下自己的理解吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Program
{
public delegate void OnMsgReceivedHandler();
public static OnMsgReceivedHandler OnMsgReceived;

public Program()
{
OnMsgReceived += OnMsgReceivedMain;
if(OnMsgReceived != null)
{
OnMsgReceived();
}
}

static void Main(string[] args)
{
Program p = new Program();
Console.WriteLine();
Console.ReadLine();
}

private void OnMsgReceivedMain()
{

}
}

先贴一下反编译的图。

可以看到当我们定义了一个handler的时候,编译器会帮我们生成一个同名的封闭类,它继承于System.MulticastDelegate,其中有两个方法需要重视:

一个是构造方法.ctor,参数有两个,一个是Object类型,一个是int类型。

另一个是Invoke方法,从字面上了解,应该是可以被调用的意思。

接下来再看我们在Program的构造方法中那几句话翻译成IL是怎么样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
.method public hidebysig specialname rtspecialname 
instance void .ctor() cil managed
{
// Code size 66 (0x42)
.maxstack 3
.locals init ([0] bool V_0)
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: nop
IL_0008: ldsfld class ConsoleApp2.Program/OnMsgReceivedHandler ConsoleApp2.Program::OnMsgReceived
IL_000d: ldarg.0
IL_000e: ldftn instance void ConsoleApp2.Program::OnMsgReceivedMain()
IL_0014: newobj instance void ConsoleApp2.Program/OnMsgReceivedHandler::.ctor(object,
native int)
IL_0019: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_001e: castclass ConsoleApp2.Program/OnMsgReceivedHandler
IL_0023: stsfld class ConsoleApp2.Program/OnMsgReceivedHandler ConsoleApp2.Program::OnMsgReceived
IL_0028: ldsfld class ConsoleApp2.Program/OnMsgReceivedHandler ConsoleApp2.Program::OnMsgReceived
IL_002d: ldnull
IL_002e: cgt.un
IL_0030: stloc.0
IL_0031: ldloc.0
IL_0032: brfalse.s IL_0041
IL_0034: nop
IL_0035: ldsfld class ConsoleApp2.Program/OnMsgReceivedHandler ConsoleApp2.Program::OnMsgReceived
IL_003a: callvirt instance void ConsoleApp2.Program/OnMsgReceivedHandler::Invoke()
IL_003f: nop
IL_0040: nop
IL_0041: ret
} // end of method Program::.ctor

首先,Program还是要先调用一下基类Object的构造方法(前面几篇讲过)。

接下来的几句话对应着OnMsgReceived的自加操作:

  1. ldsfld(load static field) 让OnMsgReceived入栈
  2. ldarg.0 作为第0个参数
  3. ldftn(load function) 让OnMsgReceivedMain方法入栈
  4. newobj new一个新的实例,调用OnMsgReceivedHandler类的构造方法
  5. 调用System.Delegate::Combine(Delegate a, Delegate b)方法
  6. castclass 类型转换,转成OnMsgReceivedHandler类型
  7. stsfld 替换静态字段的值

首先,我们定义了一个event的时候,实际上是定义了一个OnMsgReceivedHandler类型的实例,这个实例在这里名为OnMsgReceived
总结一下就是当进行自加操作的时候:

  1. 新建一个OnMsgReceivedHandler对象
  2. System.Delegate::Combine(Delegate a, Delegate b)方法把静态的OnMsgReceived和新生成的实例合并在一起,
  3. 返回得到的实例被转化成OnMsgReceivedHandler类型
  4. stsfld操作把新得到的实例替换到静态引用上去

写成C#伪代码大概是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Program
{
public class OnMsgReceivedHandler : MulticastDelegate
{
public void Invoke(){}
// ...
}
public static OnMsgReceivedHandler OnMsgReceived;

public Program()
{
OnMsgReceivedHandler handler = new Handler(OnMsgReceivedMain);
OnMsgReceivedHandler newHandler = (OnMsgReceivedHandler)System.Delegate.Combine(OnMsgReceived, handler); // Combine返回的是Delegate类型的
OnMsgReceived = newHandler;
}

private void OnMsgReceivedMain()
{
// ...
}
}

知道了自加操作其实是生成一个新的Delegate后替换掉老的,那么后面的分发操作就好理解了。首先要判断这个delegate是否为null,MulticastDelegate重写了!=和==操作符,所以可以做出正确的判断。

当判断到这个delegate是有实际内容的之后,接下来就只是简单地执行它的Invoke()方法,就可以了。


泛型

代码爆炸

在AppDomain中,每一个出现的泛型类都会被编译成一个完全不同的类。也就是说List<A>List<B>会是完全不同的代码。如果使用了太多不一样的泛型,有可能会造成代码爆炸。

主要约束

where关键字,主要约束包括了classstruct

次要约束

同样用where关键字,次要约束包括了继承的基类和实现的接口。

构造器约束

where T : new()表示该泛型应该实现了公共无参构造器。


接口

普通的方法没有什么好讲的。

哦,被实现的方法可以是virtual的。

实现多个具有同样方法签名的接口

必须使用显式接口方法实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface IWindow
{
Object GetMenu();
}
public interface IRestaurant
{
Object GetMenu();
}

public sealed class Dirived : IWindow, IRestaurant
{
Object IWindow.GetMenu() {}
Object IRestaurant.GetMenu() {}
}

由于实现了两个签名一样的方法,所以在调用的时候要转成对应的接口才能调用。

1
2
3
Dirived d = new Dirived();
IWindow iw = d as IWindow;
iw.GetMenu();

要用基类还是接口

接口是can do,基类是is a

Buy Me A Coffee / 捐一杯咖啡的钱
分享这篇文章~
0%
//