文章目錄
  1. 1. .Net Framework
    1. 1.1. Introduction
    2. 1.2. Content
    3. 1.3. Using .NET Framework
      1. 1.3.1. Tools
      2. 1.3.2. 相关概念
        1. 1.3.2.1. FCL(Framework Class Library)
        2. 1.3.2.2. Metadata
        3. 1.3.2.3. IL(Intermediate Language)
        4. 1.3.2.4. CLI(Common Language Infrastructure)
        5. 1.3.2.5. CLR(Common Language Runtime)
        6. 1.3.2.6. CTS(Common Type System)
        7. 1.3.2.7. CLS(Common Language Specification)
      3. 1.3.3. Compile process
        1. 1.3.3.1. Managed Module
      4. 1.3.4. Executing Assembly Code
      5. 1.3.5. 程序集
      6. 1.3.6. 托管代码
      7. 1.3.7. 垃圾回收
      8. 1.3.8. 链接
  2. 2. CSharp(CLR Via C#)
    1. 2.1. Introduction
    2. 2.2. Features
    3. 2.3. Development
    4. 2.4. Language Study
      1. 2.4.1. delegate
      2. 2.4.2. Class & interface
      3. 2.4.3. Not supported multiple inherit
      4. 2.4.4. Class member
      5. 2.4.5. Struct & Class
      6. 2.4.6. Collection class (System.Collection)
      7. 2.4.7. Method
      8. 2.4.8. Comparasion
      9. 2.4.9. Conversion
      10. 2.4.10. Generics
      11. 2.4.11. Hosting, AppDomain, Assembly, Reflection
        1. 2.4.11.1. Hosting
        2. 2.4.11.2. AppDomain
        3. 2.4.11.3. Assembly Loading
        4. 2.4.11.4. Reflection
      12. 2.4.12. Runtime Serialization
      13. 2.4.13. Platform Invoke
      14. 2.4.14. Event
      15. 2.4.15. Chars, Strings, and Working with Text
      16. 2.4.16. Enumerated and Bit Flags
      17. 2.4.17. Custom Attributes
      18. 2.4.18. Exceptions and State Management
      19. 2.4.19. The Managed Heap and Garbage Collection
      20. 2.4.20. Threading
        1. 2.4.20.1. Thread Scheduling and Priorities
  3. 3. C# In Depth(third edition)
    1. 3.1. C#1
      1. 3.1.1. Non Generic Collections
      2. 3.1.2. Sorting an ArrayList using IComparer
    2. 3.2. C#2
      1. 3.2.1. Sorting an List using IComparer or Comparision
      2. 3.2.2. Nullable Value Type
    3. 3.3. C#3
      1. 3.3.1. Properties
      2. 3.3.2. Sorting using Comparision from a lambda expression
      3. 3.3.3. Extension Method
      4. 3.3.4. LINQ(Language-Integrated Query)
    4. 3.4. C#4
      1. 3.4.1. Named Arguments
      2. 3.4.2. Optional Parameters
      3. 3.4.3. DLR(Dynamic Language Runtime)
    5. 3.5. CSharp Evolution

.Net Framework

Introduction

.Net Framework是Microsoft为开发应用程序而创建创的一个具有革命意义的平台。

Content

.Net Framework主要包含一个非常大的代码库,可以在客户语言中通过面向对象编程技术来使用这些代码。这个库分为多个不同的模块。

.Net Framework还包含.NET公共语言运行库(Common Language Runtime, CLR),它负责管理用.NET库开发的所有应用程序的执行

Using .NET Framework

Tools

  1. Visual Studio
  2. VCE(for C#)

相关概念

FCL(Framework Class Library)

“The FCL is a set of DLL assemblies that contain several thousand type definitions in which each type exposes some functionality.”(提供了大量功能的现有DLL库)

Metadata

“There are two main types of tables: tables that describe the types and members defined in your source code and tables that describe the types and members referenced by your source code.”(包含了源代码里类型的定义,对象的索引等信息)

IL(Intermediate Language)

“IL is a CPU-independent machine language created by Microsoft after consultation with several external commercial and academic language/compiler writers.”(CPU无关的中间语言,用来抽象编译后的高阶语言,在JIT中会被编译成特定OS和目标机器架构的机器代码)

CLI(Common Language Infrastructure)

The Common Language Infrastructure (CLI) is an open specification developed by Microsoft and standardized by ISO[1] and ECMA[2] that describes executable code and a runtime environment that allow multiple high-level languages to be used on different computer platforms without being rewritten for specific architectures.(通用语言基础架构定义了可执行码以及代码的运行时环境的规范,使得高级语言编写的软件无需重新编写就可以运行在不同的计算机体系结构上)

Note:
The .NET Framework and the free and open source Mono and Portable.NET are implementations of the CLI.

CLR(Common Language Runtime)

“The common language runtime (CLR) is just what its name says it is: a runtime that is usable by different and varied programming languages. The core features of the CLR (such as memory management, assembly loading, security, exception handling, and thread synchronization) are available to any and all programming languages that target it” — 《CLR Via C# Fourth Edition - Jeffrey Richter》(公共语言运行库提供了内存管理,异常处理,线程同步等功能)

CTS(Common Type System)

“Describes how types are defined and how they behave. Defines the rules governing type inheritance, virtual methods, object lifetime, and so on.”

CLS(Common Language Specification)

“Details for compiler vendors the minimum set of features their compilers must support if these compilers are to generate types compatible with other components written by other CLS-compliant languages on top of the CLR.”(通用语言规范定义了通用语言之间类型交互的基本规范)
CTSAndCLS

Compile process

  1. CIL(Common Intermediate Language)
    首先把代码编译成通用中间语言(Common Intermediate Language, CIL)代码
    编译到程序集

Compile Source Into Managed Modules
从上面可以看出CLR支持的语言都被编译成Managed module(IL and metadata)

Managed Module

Managed Module由两部分组成:

  1. Metadata
  2. IL(Intermediate Language)
    ManagedModulesComponents
    可以看出Metadata是负责记录类型信息。
    IL是通过CLR编译后的CPU-independent的中间语言。
    CLR支持的语言都会编译成IL和Metadata存储在Managed Module里。
    但我们最终在程序里加载的不是Module而是Assebmlies。下面来看看Module和Assebmly之间的关系。
    RelationshipBetweenModulesAndAssemblies
    可以看出Assembly是由多个Module组成。
    而CLR是负责管理Assebmlies里的代码执行。
    所以才有了多种CLR支持的语言在CLR内可以互相调用。

    Executing Assembly Code

  3. JIT(Just-In-Time)
    把CIL编译为专用于OS和目标机器结构的机器代码
    编译为本机代码
    e.g.
    CodeExecutionExample1
    CodeExecutionExample2
    从上面可以看出,之前生成的IL会被JIT在运行时编译成对应的本地机器代码。当同样的方法再次调用时,就不需要JIT进行IL到本地机器代码的编译,直接调用之前编译好的机器代码即可。

这里有个Unsafe code的概念值得注意。
Unsafe code is allowed to work directly with memory addresses and can manipulate bytes at these addresses.
/unsafe compiler switch to control whether allow to executing unsafe code(只有编译器开启了/unsafe标志才允许执行unsafe code(e.g. 直接操作内存地址进行修改))
PEVerify.exe可用不查看Assembly里是否有unsafe code。

程序集

编译程序时所创建的CIL代码存储在一个程序集中(e.g. .exe .dll)
程序集包含程序用到的相关数据信息(Assemblies contains all module’s Metadata and IL)

Note:
PDB(program Database) file helps the debugger find local variables and map the IL instructions to the source code.
NGen.exe tool can compiles all of an assembly’s IL code into native code and saves the resulting native code to a file on disk.(avoid compilation at run time)

托管代码

CLR管理着应用程序,其方式是管理内存,处理安全性以及允许进行跨语言调试等。相反,不受CLR控制运行着的应用程序属于非托管类型。
托管到CLR运行

Note:
“C++ is unique in that it is the only compiler that allows the developer to write both managed and unmanaged code and have it emitted into a single module.”

垃圾回收

托管代码中的一个功能GC(garbage collection)

链接

模块化

CSharp(CLR Via C#)

Introduction

C#是可用于创建要运行在.NET CLR上的应用程序的语言之一,它从C和C++语言演化而来,是Microsoft专门为使用.NET平台而创建的。

Features

  1. 语法简单
  2. 类型安全
  3. 为.NET Framework设计的语言

Development

Application Type

  1. Windows Appliaction Program
  2. Web Application Program
  3. Web Service

Language Study

Only record some difference between C# and C++/Java

delegate

delegates — type-safe
Unmanaged C/C++ callback functions are not type-safe
首先要知道的是delegate在C#里类型安全的(即有有编译时的类型检查)
C++里是不是类型安全的

在调用delegate的时候,CLR提供了当reference type绑定方法到delegate的时covariance和contra-variance的支持。
Covariance means that a method can return a type that is derived from the delegate’s return type.(reference type的方法的返回类型可以是delegate指定返回类型的子类)
Contra-variance means that a method can take a parameter that is a base of the delegate’s parameter type.(reference type的方法的参数可以是delegate指定参数类型的父类)
The reason why value types and void cannot be used for covariance and contra-variance is because the memory structure for these things varies, whereas the memory structure for reference type is always a pointer.(value type和void不支持上述功能)

接下来让我们看看Delegate背后的故事:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace CSharpDeepStudy
{
#region Delegate Study
internal delegate void DelegateStudy(int value);
#endregion

class Program
{
#region Delegate Study
private static void StaticDelegateDemo(int value)
{

Console.WriteLine("StaticDelegateDemo({0})", value);
}

private void InstanceDelegateDemo(int value)
{

Console.WriteLine("InstanceDelegateDemo({0})",value);
}

private static void ChainDelegateDemo(Program p)
{

DelegateStudy cdd = null;
cdd += Program.StaticDelegateDemo;
cdd += p.InstanceDelegateDemo;
cdd.Invoke(3);
}
#endregion

static void Main(string[] args)
{

#region Delegate Study
Program p = new Program();
DelegateStudy sdd = Program.StaticDelegateDemo;
DelegateStudy idd = p.InstanceDelegateDemo;
sdd.Invoke(1);
idd.Invoke(2);
Program.ChainDelegateDemo(p);
#endregion

#region Dynamic Study
//Dynamic delegate part
MethodInfo mi = typeof(Program).GetMethod("InstanceDelegateDemo");
Delegate d = Delegate.CreateDelegate(typeof(DelegateStudy), p, mi);
d.DynamicInvoke(4);
#endregion

Console.ReadKey();
}
}
}

反编译后:
DelegateStudy
从上面可以看到,当我们定义一个delegate的时候CLR会给我们生成一个继承至System.MulticastDelegate的类,上面是DelegateStudy Class。
而MulticastDelegate是我们去累加delegate的关键。
MulticastDelegate
从上面可以看出,MulticastDelegate包含了三个关键成员:

  1. _target
    用于保存delegate的实例对象,如果是全局static的回调则为null
  2. _methodPtr
    用于识别回调方法
  3. _invocationList
    这个是用于delegate chain的关键,用于保存array of delegate objects
    下面我们看看_invocationList是如何完成delegate chain的:
    MultipleDelegatePart1
    MultipleDelegatePart2
    MultipleDelegatePart3
    MultipleDelegatePart4
    可以看出当我们chain delegate的时候_invocationList存储了delegate的指针到_invocationList里,从而实现了multiple delegate chain的效果。
    最后要讲的一点是关于反射动态创建delegate:
    通过System.Reflection.MethodInfo.CreateDelegate()方法我们可以实现动态创建delegate。
    最终上面的代码会输出如下:
    DelegateStudyOutput

(C++里用函数指针实现,Java里通过内部类的闭包和interface去实现)
最后我们还可以通过lambda表达式和匿名方法去定义delegate

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
33
34
35
36
37
38
39
40
41
42
43
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CSharpStudy
{
class Program
{
delegate void testDelegate(string para);

static void doSomething(string para)
{

Console.WriteLine("doSomething:" + para);
}

static void Main(string[] args)
{

#region method 1 to use delegate
testDelegate delegate1;
delegate1 = new testDelegate(doSomething);
#endregion
delegate1("delegate1");

#region method 2 to use delegate(lambda expression)
testDelegate delegate2 = s =>
{
Console.WriteLine("doSomething:" + s);
};
#endregion
delegate2("delegate2");

#region method 3 to use delegate(anonymous method)
testDelegate delegate3 = delegate(string para)
{
Console.WriteLine("delegate3's para = " + para);
};
#endregion
delegate3("delegate3");
Console.ReadKey();
}
}
}

Output:
CSharp_Delegate

详细比较Delegate和函数指针,参见C# VS C++之一: 委托 vs 函数指针

这里只写最后的总结:
1.C#委托对象是真正的对象,C/C++函数指针只是函数入口地址
2.C++的委托对象:functor
3.C++的静多态:模版

Class & interface

  1. Class qualifier
    internal class — only code in current project can access (default)
    public class — other project code can access

abstract class — abstract class
sealed class — cant not be inheritated

Note:
Compiler is not allowed derived class’s access privileges higher than parent class

e.g.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
internal class MyBase
{
public MyBase()
{


}
}
//Cant do like this due to Child class access previlage is higher than parent class
public class MyChild /*: MyBase*/
{
public MyChild()
{



}
}

support extends multiple interface
Note:
base class must be write down first when we inherites from one class and extends several interface
abstract & sealed can not be used by interface due to no implementation in interface (abstract & sealed qualifier are meaningless)

  1. Static constructor & Static class
    静态构造函数只会被调用一次且属于整个类
    静态类不能拥有实例构造函数且只能有static成员
    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace CSharpStudy
    {
    class Program
    {
    class StaticConstructor
    {
    static StaticConstructor()
    {

    Console.WriteLine("Static StaticConstructor()");
    m_ID = 1;
    }

    public StaticConstructor()
    {

    Console.WriteLine("Normal StaticConstructor()");
    }

    public static int m_ID;
    }

    static class StaticClass
    {
    /*
    //Cant have instance constructor (do not know why static constructor neither)
    static StaticClass()
    {
    Console.WriteLine("StaticClass()");
    m_ID = 2;
    m_Type = "Static";
    }
    */

    //Can not non static member
    //public int m_Test = 3;
    public static int m_ID = 2;

    public static string m_Type = "Static";
    }


    static void Main(string[] args)
    {

    StaticConstructor sc = new StaticConstructor();
    Console.WriteLine("StaticConstructor::m_ID = " + StaticConstructor.m_ID);

    Console.WriteLine("StaticClass::m_ID = " + StaticClass.m_ID);
    Console.WriteLine("StaticClass::m_Type = " + StaticClass.m_Type);

    Console.ReadKey();
    }
    }
    }

Output:
Static_Constructor_And_Static_Class

Not supported multiple inherit

C++支持多重继承,Java和C#通过extend多个Interface来实现多重继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Base1
{
public Base1()
{

Console.WriteLine("Base1()");
}
}
class Base2
{
public Base2()
{

Console.WriteLine("Base1()");
}
}
class Child : Base1/*, Base2*///Not support multiple inherit
{
public Child()
{

Console.WriteLine("Child()");
}
}

Child c = new Child();

Class member

access qualifier
public, private, internal, protected

readonly — only can be initilized in constructor or declaration

property & field
property gives more control to field access

accessor privilege — can not be higher than the access privilege that it is belonged to

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Accessor
{
public Accessor()
{

m_A = 0;
}

private int IntA
{
get
{
return m_A;
}
//Can not be public due to IntA's access privilege is lower than public
//public set
set
{
m_A = value;
}
}
private int m_A;
}

  1. member function
    override key word can hide base function(works with polymorphism)
    Access base function that has been hiden uses base key word in child class

  2. Interface member
    all interface member must be public
    can not use static, virtual, abstract, sealed

  3. Interface implementation
    explicit implementation — only can be accessed through interface (return type interface.functionanme(para))
    implicit implementation — can be accessd through both interface and class

  4. partial class definition & partial property
    can put class member, property, method, field into several files(partial key word)
    Partial property is always static and withought return value

Struct & Class

Struct is value type
在栈上分配内存
栈回收快速
传递的是值
转换为reference type会触发boxing引发堆上的额外内存分配
Class is reference type
在堆上分配内存
GC管理
传递的是索引
那么什么时候定义struct,什么时候定义class了?
一下来至MSDNChoosing Between Class and Struct.aspx)
✓ CONSIDER defining a struct instead of a class if instances of the type are small and commonly short-lived or are commonly embedded in other objects.
如果生命周期短,并被包含在其他类里而已考虑使用struct

X AVOID defining a struct unless the type has all of the following characteristics:
It logically represents a single value, similar to primitive types (int, double, etc.).
It has an instance size under 16 bytes.
It is immutable.
It will not have to be boxed frequently.
In all other cases, you should define your types as classes.
可以看出只有在数据简单,不可变,不需要频繁boxing的时候才会选择定义struct。

Shallow copy & Deep copy
Shallow copy will only copy value type member, reference type member will use original one(System.Object.MemberwiseClone())

Deep copy will copy all member value instead of reference(implememnt ICloneable::Clone())

Collection class (System.Collection)

好比C++里的STL里的container
Our own collection (extends CollectionBase Class || DictionaryBase)

因为C#没有自带PriorityQueue而是通过基本的List等数据结构来实现,下面是自己实现PriorityQueue,主要是通过堆排序用List来模拟优先队列。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
public class PriorityQueue<T1, T2>
{
public PriorityQueue()
{
mHeap = new Heap<T1, T2>();
}

public PriorityQueue(Heap<T1, T2> heap)
{
mHeap = heap;
}

public bool Empty()
{
return (mHeap.Size() == 0);
}

public void Push(KeyValuePair<T1, T2> kvp)
{
mHeap.Insert(kvp);
}

public KeyValuePair<T1, T2> Pop()
{
KeyValuePair<T1, T2> result = mHeap.Top();
mHeap.RemoveTop();
return result;
}

public int Size()
{
return mHeap.Size();
}

public KeyValuePair<T1, T2> Top()
{
return mHeap.Top(); ;
}

public void PrintOutAllMember()
{
mHeap.PrintOutAllMember();
}

private Heap<T1, T2> mHeap;
}

public class Heap<T1, T2>
{
private List<KeyValuePair<T1, T2>> mList;
private IComparer<T2> mComparer;
private int mCount;

public Heap()
{
mList = new List<KeyValuePair<T1, T2>>();
mComparer = Comparer<T2>.Default;
mCount = 0;
}

public Heap(List<KeyValuePair<T1, T2>> list)
{
mList = list;
mCount = list.Count;
mComparer = Comparer<T2>.Default;
BuildingHeap();
}

public int Size()
{
if (mList != null)
{
return mCount;
}
else
{
return 0;
}
}

//O(Log(N))
public void RemoveTop()
{
if (mList != null)
{
mList[0] = mList[mCount - 1];
mList.RemoveAt(mCount - 1);
mCount--;
HeapifyFromBeginningToEnd(0, mCount - 1);
}
}

public KeyValuePair<T1, T2> Top()
{
if (mList != null)
{
return mList[0];
}
else
{
//No more member
throw new InvalidOperationException("Empty heap.");
}
}

public void PrintOutAllMember()
{
foreach (KeyValuePair<T1, T2> valuepair in mList)
{
Console.WriteLine(valuepair.ToString());
}
}

//O(Log(N))
public void Insert(KeyValuePair<T1, T2> valuepair)
{
mList.Add(valuepair);
mCount++;
HeapifyFromEndToBeginning(mCount - 1);
}

//调整堆确保堆是最大堆,这里花O(log(n)),跟堆的深度有关
private void HeapifyFromBeginningToEnd(int parentindex, int length)
{
int max_index = parentindex;
int left_child_index = parentindex * 2 + 1;
int right_child_index = parentindex * 2 + 2;

//Chose biggest one between parent and left&right child
if (left_child_index < length && mComparer.Compare(mList[left_child_index].Value, mList[max_index].Value) < 0)
{
max_index = left_child_index;
}

if (right_child_index < length && mComparer.Compare(mList[right_child_index].Value, mList[max_index].Value) < 0)
{
max_index = right_child_index;
}

//If any child is bigger than parent,
//then we swap it and do adjust for child again to make sure meet max heap definition
if (max_index != parentindex)
{
Swap(max_index, parentindex);
HeapifyFromBeginningToEnd(max_index, length);
}
}

//O(log(N))
private void HeapifyFromEndToBeginning(int index)
{
if (index >= mCount)
{
return;
}
while (index > 0)
{
int parentindex = (index - 1) / 2;
if (mComparer.Compare(mList[parentindex].Value, mList[index].Value) > 0)
{
Swap(parentindex, index);
index = parentindex;
}
else
{
break;
}
}
}

//通过初试数据构建最大堆
////O(N*Log(N))
private void BuildingHeap()
{
if (mList != null)
{
for (int i = mList.Count / 2 - 1; i >= 0; i--)
{
//1.2 Adjust heap
//Make sure meet max heap definition
//Max Heap definition:
// (k(i) >= k(2i) && k(i) >= k(2i+1)) (1 <= i <= n/2)
HeapifyFromBeginningToEnd(i, mList.Count);
}
}
}

////O(N*log(N))
private void HeapSort()
{
if (mList != null)
{
//Steps:
// 1. Build heap
// 1.1 Init heap
// 1.2 Adjust heap
// 2. Sort heap

//1. Build max heap
// 1.1 Init heap
//Assume we construct max heap
BuildingHeap();
//2. Sort heap
//这里花O(n),跟数据数量有关
for (int i = mList.Count - 1; i > 0; i--)
{
//swap first element and last element
//do adjust heap process again to make sure the new array are still max heap
Swap(i, 0);
//Due to we already building max heap before,
//so we just need to adjust for index 0 after we swap first and last element
HeapifyFromBeginningToEnd(0, i);
}
}
else
{
Console.Write("mList == null");
}
}

private void Swap(int id1, int id2)
{
KeyValuePair<T1, T2> temp;
temp = mList[id1];
mList[id1] = mList[id2];
mList[id2] = temp;
}
}

static void Main(string[] args)
{
List<KeyValuePair<int, float>> list = new List<KeyValuePair<int, float>>();
list.Add(new KeyValuePair<int, float>(3, 1.0f));
list.Add(new KeyValuePair<int, float>(2, 5.0f));
list.Add(new KeyValuePair<int, float>(1, 3.0f));
list.Add(new KeyValuePair<int, float>(6, 4.0f));
list.Add(new KeyValuePair<int, float>(5, 2.0f));
list.Add(new KeyValuePair<int, float>(4, 6.0f));

Heap<int,float> heap = new Heap<int,float>(list);

PriorityQueue<int,float> pq = new PriorityQueue<int,float>(heap);

pq.PrintOutAllMember();

Console.WriteLine("------------------------pq.Push(new KeyValuePair<int, float>(0, 0.0f));");

pq.Push(new KeyValuePair<int, float>(0, 0.0f));

pq.PrintOutAllMember();

Console.WriteLine("------------------------pq.Pop();");

pq.Pop();

pq.PrintOutAllMember();

Console.WriteLine("------------------------pq.Top();");

Console.WriteLine(pq.Top().ToString());

#endregion

Console.ReadKey();
}

Output:
PriorityQueue_Study

堆排序构造有序堆的时间复杂度是O(N * Log(N))
但插入和移除操作都是O(Log(N))

排序算法参考

Method

关于Method这里主要讲两点:

  1. Extension Methods
    “It allows you to define a static method that you can invoke using instance method syntax.”(Extension Methods最主要的好处就在于当你无法给特定类或结构定义方法的时候,你可以通过定义extension method来为该类或结构添加方法,使用的时候就跟在类里定义方法一样,通过实例就能调用。)
    定义Extension Method首先必须定义在一个静态类里,且方法为静态方法,并且第一个参数类型前要加this关键词,this后面跟的就是我们要extension的类。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public static class StringBuilderExtensions
    {
    public static Int32 Indexof(this StringBuilder sb, Char value)
    {

    for (Int32 index = 0; index < sb.Length; index++)
    {
    if (sb[index] == value)
    {
    return index;
    }
    }
    return -1;
    }
    }

    static void Main(string[] args)
    {

    StringBuilder sb = new StringBuilder("Hello. My name is Tony.");
    Int32 index = sb.Indexof('T');
    Console.WriteLine("sb.Indexof('T') = " + index);
    }

Output:
ExtensionMethods
调用Extension method跟Compiler如何去寻找方法编译有关,具体见《CLR via C#》 — Methods的Extension Methods章节
定义Extension Methods需要注意一下几点:

1. 只能定义在非模板静态类里。
2. 定义Extension Methods的类必须位于file scope(不能被其他类包含)
3. 必须包含添加Extension Methods的类的namespace(为了避免检查所有文件去找extension methods)
4. Extension Methods应该少用,会造成versioning problem(未来可能添加相同方法到类里,不同版本的调用会出现不同的表现)
  1. Partial Methods
    首先得知道我们为什么需要Partial Methods?
    1. Override去重写virtual方法的时候要求父类不能是Sealed,不能为sealed class或value type重写方法
    2. 无需为了重写个别方法而单独定义一个类
      使用Partial Methods在partial class里定义partial方法前加partial关键字
      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
      33
      internal sealed partial class Base
      {
      public String Name
      {
      get
      {
      return mName;
      }
      set
      {
      OnNameChanging(value.ToUpper());
      mName = value;
      }
      }
      private String mName;

      //This defining-partial-method-declaration is called before changing the mName field
      partial void OnNameChanging(String value);
      }

      internal sealed partial class Base
      {
      partial void OnNameChanging(string value)
      {

      Console.WriteLine("Base::OnNameChanging({0})", value);
      }
      }

      static void Main(string[] args)
      {

      Base bs = new Base();
      bs.Name = "Tony";
      }

Output:
PartialMethods
使用Partial Methods需要注意以下几点:

1. 只能在Partial class or Struct里定义
2. Partial Method必须返回void,并且不能有parameterout关键词修饰
3. Partial Method必须和原方法签名一样
4. 如果Partial Method没有实现,Delegate不能指向该partial method
5. Partial Methods永远是private

Comparasion

  1. Type comparasion
    System.Object.GetType()
    &&
    typeof()
    &&
    is operator — is specific type or type can be cast

    1. boxing — cast value type into System.Object type(shollow copy) or interface type that is implemented by value type
    2. unboxing — boxing reverse process
  2. Value comparasion
    operator overloading — must be static
    IComparable — compare object’s data with the same type
    &&
    IComparer — compare two object with different type or the same type

Conversion

Conversion operator
implici
explicit
e.g.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A
{
public static implicit operator B(A a)
{

......
}
}

class B
{
public static explicit operator A(B b)
{

......
}
}

as operator

as
Suit case

  1. operand type is type
  2. operand type can be casted into type implicitly
  3. operand can be boxing into type

Generics

C++里是template实现
System.Collections.Generic
value type can not be initilized with null
Problem

  1. null (value type or reference type)
    1. default key word — if it is reference type, initilized with null. otherwise use default value
  2. type
    1. constraining
      where key words
      e.g.
      class A: where T:B 9( T must inherite from B)
      Code e.g.
      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
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
         using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Collections;

      namespace CSharpStudy
      {
      class Program
      {
      //Can not instantiation with int when where T1 : class
      //public class GenericClass<T1> where T1 : class
      public class GenericClass<T1> : IEnumerable<T1> where T1 : struct
      {
      private List<T1> m_Member = new List<T1>();

      public GenericClass()
      {
      //use T1's default value
      //m_Member.Add(default(T1));
      }
      public List<T1> GetMember
      {
      get
      {
      return m_Member;
      }
      }
      public IEnumerator<T1> GetEnumerator()
      {
      return m_Member.GetEnumerator();
      }

      IEnumerator IEnumerable.GetEnumerator()
      {
      return m_Member.GetEnumerator();
      }

      public static implicit operator List<T1>(GenericClass<T1> gc)
      {
      List<T1> result = new List<T1>();
      foreach (T1 i in gc)
      {
      result.Add(i);
      }
      return result;
      }

      public static GenericClass<T1> operator +(GenericClass<T1> gc, List<T1> l)
      {
      GenericClass<T1> result = new GenericClass<T1>();
      foreach (T1 m in gc)
      {
      result.GetMember.Add(m);
      }
      foreach (T1 m in l)
      {
      if (!result.GetMember.Contains(m))
      {
      result.GetMember.Add(m);
      }
      }
      return result;
      }
      };

      static void Main(string[] args)
      {
      //Nullable problem,
      //value type can not be initiated with null
      //int normalint = null;
      System.Nullable<int> nullableint = null;
      //nullable
      int? nullalbleint = null;
      int? result = nullalbleint ?? 5;
      Console.WriteLine("result = " + result);
      List<int> list = new List<int>(2);
      list.Add(1);
      list.Add(2);
      foreach (int i in list)
      {
      Console.WriteLine("list value = " + i);
      }

      GenericClass<int> gc = new GenericClass<int>();
      Console.WriteLine("gc.m_Member = " + gc.GetMember);

      GenericClass<int> gc2 = new GenericClass<int>();
      gc2.GetMember.Add(11);
      gc2.GetMember.Add(111);
      GenericClass<int> gc3 = new GenericClass<int>();
      gc3.GetMember.Add(11);
      gc3.GetMember.Add(22);
      gc3.GetMember.Add(222);

      gc = gc2 + gc3;
      foreach (int i in gc)
      {
      Console.WriteLine("gc member = " + i);
      }

      Console.ReadKey();
      }
      }
      }

Output:
Generic

Variance (变体)

  1. Convariance — 协变 out key word
    主要用于Interface和delegate的返回类型或者参数类型的隐士转换(子类到父类)
  2. Contravariance — 抗变 int key word
    与协变相反(父类到子类)
    Code e.g.
    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    //Covariance
    static void ListAnimals(IEnumerable<Animal> animals)
    {

    foreach (Animal animal in animals)
    {
    Console.WriteLine(animal.ToString());
    }
    }

    static void FeeAnimal(Func<Animal> animalCreator)
    {

    var animal = animalCreator();
    Console.WriteLine("animal.name = " + animal.Name);
    }

    static void FeeAnimal(Func<Cow> animalCreator)
    {

    var animal = animalCreator();
    Console.WriteLine("animal.name = " + animal.Name);
    }

    static Cow CreateCow()
    {

    return new Cow("DelegateCow");
    }

    //Contravariance
    static void FeeAnimal(Animal animal)
    {

    Console.WriteLine("FeeAnimal:" + animal.Name);
    }

    static void Execute(Action<Cow> cact)
    {

    cact(new Cow("ExecuteCow"));
    }

    //Covariance
    List<Cow> cows = new List<Cow>();
    cows.Add(new Cow("Cow1"));
    ListAnimals(cows);

    FeeAnimal(CreateCow);
    Func<Cow> cFunc = CreateCow;
    Func<Animal> aFunc = cFunc;

    //Contravariance
    Action<Animal> aAct = FeeAnimal;
    Action<Cow> cAct = aAct;

    Execute(aAct);

Output:
Variance_Contravariance

Note:
C++是通过编译器检测出模板使用的特定类型
C#是运行时进行

Hosting, AppDomain, Assembly, Reflection

这一章节主要是学习关于Assembly Loading和Reflection技术。
在学习Assembly Loading和Reflection之前,我们需要了解Hosting,AppDomain的概念。

Hosting

以下英文内容来至《CLR via C#》
Hosting allows any application to use the features of the common language runtime(CLR). Furthermore, hosting allows applications the ability to offer customization and extensibility via programming.
Extensibility means that third-party code will be running inside your process.

The hosting application can call methods defined by ICLRMetaHost interface to:

  1. Set Host managers. Tell the CLR that the host wants to be involved in making decisions related to memory allocations, thread scheduling/synchronization, assembly loading, and more. The host can also state that it wants notifications of garbage collection starts and stops and when certain operations time out.
  2. Get CLR managers. Tell the CLR to prevent the use of some classes/members. In addition, the host can tell which code can and can’t be debugged and which methods in the host should be called when a special event—such as an AppDomain unload, CLR stop, or stack overflow exception—occurs.
  3. Initialize and start the CLR.
  4. Load an assembly and execute code in it.
  5. Stop the CLR, thus preventing any more managed code from running in the Windows process.

Hosting(allows any application to offer CLR features) Benifits:

  1. Programming can be done in any programming language.
  2. Code is just-in-time (JIT)–compiled for speed (versus being interpreted).
  3. Code uses garbage collection to avoid memory leaks and corruption.
  4. Code runs in a secure sandbox.
  5. The host doesn’t need to worry about providing a rich development environment. The
    host makes use of existing technologies: languages, compilers, editors, debuggers, profilers, and more.
    从上面所有内容可以看出Hosting可以让我们去利用CLR的特性,我们而已通过Host去设定很多CLR相关的设定(比如GC,Memory Manager……),初始化CLR,创建出默认的AppDomain,通过CLR去加载Assemly到AppDomain然后执行。

AppDomain

AppDomain allows third-party untrusted code to run in an existing proceess, and the CLR guarantees that the data structures, code, and security context will not be exploited or compromised.(AppDomain允许不可信的代码在当前进程执行,CLR会去确保数据结构,代码等安全问题)
AppDomain和CLR的关系:
“AppDomains are a CLR feature.”

“When the CLR COM server initializes, it creates an AppDomain. An AppDomain is a logical container for a set of assemblies. The first AppDomain created when the CLR is initialized is called the default AppDomain; this AppDomain is destroyed only when the Windows process terminates.”(AppDomain是一个assemblies集合的容器,当CLR初始化的时候会创建默认的AppDOmain,这个AppDomain只能在程序结束的时候被终止)

The whole purpose of an AppDomain is to provide isolation. Here are the specific features offered by an AppDomain(AppDomain的主要目的是为了实现程序隔离):

  1. Objects created by code in one AppDomain cannot be accessed directly by code in another AppDomain When(确保不同AppDomain里的Object不会被其他AppDomain访问)
  2. AppDomains can be unloaded(AppDomain可以被unload)
  3. AppDomains can be individually secured(通过设定AppDomain的permission用于确保assembly的一些权限)
  4. AppDomains can be individually configured(设置AppDomian的配置,影响如何去加载Assemlies等)

这里提到AppDomain的程序隔离功能,那就不得不说一下Process了。
“Process isolation prevents security holes, data corruption, and other
unpredictable behaviors from occurring, making Windows and the applications running
on it robust.”
这里Process可以理解为进程面上的程序隔离,而AppDomain可以理解为进程内的程序隔离(一个进程可以创建多个AppDomain)
让我们看一下程序是如何在Process,AppDomain还有CLR下工作的:
CLRAppDomainProcessRelationship
可以看出一个Process下创建了多个AppDomain,每一个AppDomain加载了特定的Assembly,每一个AppDomain有自己的LoaderHeap,每一个LoaderHeap记录了该AppDomain所访问过的type,当调用type的method的时候,IL code会被JIT运行时编译到对应的机器代码执行。
普通的AppDomain之间的Assembly是完全隔离的,所以就算多个AppDomain引用了同一个Assembly,他们也不会共享数据和内存。
但上图有一个比较特殊的AppDomain,叫做Domain-Neutrl Assemblies。
这个Domain的主要目的是共享一些通用的Assemblys,加载在这个AppDomian下的Assemblys可以被所有的AppDomains访问。
虽然Assembly在AppDomain之间是完全隔离的,但不同AppDomain创建的objects还是可以相互访问的。
让我们看看不同AppDomain创建的objeccts如何相互访问的:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Reflection;

using System.Runtime.InteropServices;
using System.Threading;
using System.Runtime;
using System.Runtime.Remoting;

namespace CSharpDeepStudy
{
class Program
{
#region Hosting and AppDomain Study
// Instances can be marshaled-by-reference across AppDomain boundaries
[Serializable]
public sealed class MarshalByRefType : MarshalByRefObject
{
public MarshalByRefType()
{

Console.WriteLine("{0} ctor running in {1}", this.GetType().ToString(), Thread.GetDomain().FriendlyName);
}

public void SomeMethod()
{

Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
}

public MarshalByValType MethodWithReturn()
{

Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
MarshalByValType t = new MarshalByValType();
return t;
}

public NonMarshalableType MethodArgAndReturn(String callingdomainname)
{

Console.WriteLine("Calling from {0} to {1}", callingdomainname, Thread.GetDomain().FriendlyName);
NonMarshalableType t = new NonMarshalableType();
return t;
}
}

// Instances can be marshaled-by-value across AppDomain boundaries
[Serializable]
public sealed class MarshalByValType : Object
{
private DateTime m_CreationTime = DateTime.Now;

public MarshalByValType()
{

Console.WriteLine("{0} ctor running in {1}, Created on {2:D}", this.GetType().ToString(), Thread.GetDomain().FriendlyName, m_CreationTime);
}

public override String ToString()
{

return m_CreationTime.ToLongDateString();
}
}

// Instances cannot be marshaled across AppDomain boundaries
// [Serializable]
public sealed class NonMarshalableType : Object
{
public NonMarshalableType()
{

Console.WriteLine("Excuting in " + Thread.GetDomain().FriendlyName);
}
}
#endregion

private static void Marshalling()
{

//Obtain current thread AppDomain
AppDomain currentthreadappdomian = Thread.GetDomain();
String callingdomainname = currentthreadappdomian.FriendlyName;
Console.WriteLine("currentthreadappdomian.name = " + callingdomainname);

//Get the assembly that contains the main method
Assembly mainassembly = Assembly.GetEntryAssembly();
String exeassembly = mainassembly.FullName;
Console.WriteLine("Assembly's that contains main method name is " + exeassembly);

//Accessing Objects Across AppDomain Boundaries
//Cross-AppDomain Communication using marshal-by-reference
AppDomain ad2 = null;
ad2 = AppDomain.CreateDomain("AD2", null, null);
MarshalByRefType mbrt = null;
mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeassembly, typeof(MarshalByRefType).FullName);
Console.WriteLine("Type = {0}", mbrt.GetType());
//Prove that we got a reference to a proxy object
Console.WriteLine("Is proxy = {0}", RemotingServices.IsTransparentProxy(mbrt));
//Call method in the AppDomain owning the objects
mbrt.SomeMethod();
//Unload the new AppDomian
AppDomain.Unload(ad2);
//try access mbrt after we unload AppDomain it owned
try
{
mbrt.SomeMethod();
Console.WriteLine("Successful call SomeMehtod()");
}
catch (AppDomainUnloadedException)
{
Console.WriteLine("Failed call SomeMethod()");
}

//Cross-AppDomain Communication using Marshal-by-value
//Create new AppDomain
ad2 = AppDomain.CreateDomain("AD3", null, null);
mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeassembly, typeof(MarshalByRefType).FullName);

MarshalByValType mbvt = mbrt.MethodWithReturn();
//Prove that we did NOT get a reference to a proxy object
Console.WriteLine("Is Proxy={0}", RemotingServices.IsTransparentProxy(mbvt));
//Try call method on real object
Console.WriteLine("Returned object created " + mbvt.ToString());
//Unload AppDomain again
AppDomain.Unload(ad2);
//try access method on real object again
try
{
Console.WriteLine("Returned object created " + mbvt.ToString());
Console.WriteLine("Successful call.");
}
catch (AppDomainUnloadedException)
{
Console.WriteLine("Failed call.");
}

//Cross-AppDomain Communication Using non-marshalable type
ad2 = AppDomain.CreateDomain("AD4", null, null);
//Load assembly into the new AppDoamin
mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeassembly, typeof(MarshalByRefType).FullName);

//call the object method to get non-marshalable object
try
{
NonMarshalableType nmt = mbrt.MethodArgAndReturn(callingdomainname);
}
catch (System.Exception e)
{
Console.WriteLine(e.ToString());
}
}

static void Main(string[] args)
{

#region Hosting and AppDomain Study
Marshalling();
#endregion

Console.ReadKey();
}
}
}

CrossAppDomainCommunicationOutPut
上面的测试主要是针对下面三种情况:

  1. Cross-AppDomain Communication Using Marshal-by-Reference
    从上面可以看出当我们Marshal-by-Reference between AppDomain的时候,我们需要继承至MarshalByRefObject。(通过RemotingServices.IsTransparentProxy检查是否是Proxy)
    底层是通过在Destination AppDomain生成的Proxy Type信息,其中还生成了instane fields去记录了哪一个AppDomain真正拥有这个type,如何在该AppDomain下找到这个real object去实现Reference的。
    这样就说得通当我们关掉创建Prox Type的AppDomain后,再次通过Prox Type调用就无法通过了,因为通过调用AppDomain.Unload(),所有在该AppDomain里的assemblies和通过assemblies里的信息创建的对象都被释放回收了。
    Note:
    “although you can access fields of a type derived from MarshalByRefObject, the performance is particularly bad because the CLR really ends up calling methods to perform the field access.”
  2. Cross-AppDomain Communication Using Marshal-by-Value
    当我们Marshal-by-Value时不需要继承至MarshalByRefObject,但需要确保MarshalByValType是[Serializable]的。
    因为底层实现是通过序列化和反序列化实现Destination AppDomain加载并生成对应type信息。
  3. Cross-AppDomain Communication Using Non-Marshalable Types
    最后一个是因为我们采用Marshal-by-Value但却没有把NonMarshalableType设置成[Serializable]导致在Serialize NonMarshalableType到Destination AppDomain的时候抛异常。
    针对AppDomain问题我没有深入学习,如有不对之处欢迎指出,详情请参考《CLR via C#》
    Hosting,CLR,AppDomain,Process,Assemly关系作用总结:
    Hosting使我们可以去利用CLR的特性,通过Host可以设定很多CLR相关的设定(比如GC,Memory Manager……)。
    当CLR初始化完成后,会创建出默认的AppDomain。
    通过CLR去加载Assemly到AppDomain然后执行。
    一个Process可以有多个AppDomian。
    每个AppDomain有自己的Loader Heap去记录加载到AppDomain里的Type信息。
    当调用Type的method的时候,IL code会被JIT运行时编译到对应的机器代码执行。
    普通的AppDomain之间的Assembly是完全隔离的,所以就算多个AppDomain引用了同一个Assembly,他们也不会共享数据和内存。
    但加载在这个Domain-Neutrl Assemblies AppDomian下的Assemblys可以被所有的AppDomains访问。
    虽然Assembly在AppDomain之间是完全隔离的,但不同AppDomain创建的objects还是可以通过Marshal-by-Value和Marshal-by-Reference方式相互访问的。
    关于AppDomain的更多内容参考《CLR via C#》 — CLR Hosting and AppDomains章节(e.g. AppDomain Monitoring, How Hosts Use AppDomians……)
    Note:
    在Windows上默认的AppDomain的名字是是*.exe(执行的程序)

了解了AppDomain的基本概念,接下来让我们看看关于Assembly Loading:

Assembly Loading

Assembly是一个包含类型信息,方法信息,成员信息,程序名称,版本号,自我描述,文件关联关系和文件位置等信息的一个集合。
System.Reflection.Assembly.Load — 加载Assembly到AppDomain。(相比System.AppDomain.Load, Prefer use System.Reflection.Assembly)
System.Reflection.Assembly.LoadFrom — 加载指定路径的Assembly到AppDomain,这里也可以指定URL
System.Reflection.Assembly.ReflectionOnlyLoad or ReflectionOnlyLoadFrom — 确保只加载Assembly不会去执行里面的任何代码(只用于获取Assembly里的一些相关信息)。用这两个方法需要注册AppDomain’s ReflectionOnlyAssemblyResolve
event去手动加载索引的assemblies
既然我们知道了如何加载Assembly,也知道了Assembly包含了我们程序去创建实例所需要的所有信息,那么我们如何动态的使用Assembly里的信息去创建实例了,答案是反射。
System.Reflection给我们提供了很多方法可以去访问Assembly里的fields,methods,properties等信息。
通过这些信息,我们可以制作像ILDasm.exe这样的反编译工具,因为通过有些类我们可以得到方法的IL指令数据。
这也就是我们为什么能通过System.Reflection.Emit去动态创建类的原因(使用IL指令反向构建方法,类等)。参见AOT & JIT
那么接下来让我们看看Reflection:

Reflection

这里不得不提一个Reflection使用的典型案列,那就是Serialization,序列化反序列是通过reflection去获取类型信息去存储和构建实例的。
具体关于Serialization后续后讲到。
反射最强大的地方就在于可以在运行时动态创建和使用一些类型,但这些类型我们在编译时期是不可知的。
但缺点如下:

  1. Reflection prevents type safety at compile time(因为是运行时才动态创建,所以编译时期就无法确保类型安全)
  2. Reflection is slow(反射很慢,因为我们需要在运行时动态去获取类型信息去动态创建)
    反射慢的主要原因就在于运行时去访问获取类型信息,所以我们应该尽可能的使用在编译时期就能知道类型信息的方式。
    比如:
    通过实现一个继承至父类或接口的类,通过多态的方式去调用方法。(编译时期就知道该调用哪个方法)
    那么接下来让我们看看如何使用反射去访问类型信息并调用里面的方法。
    首先我们看看如何去获取Assembly里的一些类型信息:
    首先我们创建一个只包含了几个类信息的dll
    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
    using System;
    ......

    namespace CSharpDLL
    {
    public class Program
    {
    public class CSharpDLLPublicClass1
    {
    ......
    }

    public class CSharpDLLPublicClass2
    {
    ......
    }

    sealed class CSharpDLLSealedClass
    {
    ......
    }

    static void Main(string[] args)
    {

    }
    }
    }

然后通过Assembly.Load去加载并查看里面的Type信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static void LoadAssemAndShowPublicTypes(string assemblename)
{

Assembly a = Assembly.LoadFrom(assemblename);
foreach (Type t in a.GetExportedTypes())
{
Console.WriteLine(t.FullName);
}
}

static void Main(string[] args)
{

LoadAssemAndShowPublicTypes("CSharpDLL.dll");

Console.ReadKey();
}

ExportPublicAssemblyType
可以看出除了sealed的CSharpDLLSealedClass都成功打印出来了。
那么这里就有个疑问了,Type是个什么类型?
“Represents type declarations: class types, interface types, array types……A System. Type object represents a type reference”
A TypeInfo instance contains the definition for a Type, and a Type now contains only reference data.
可以看出TypeInfo包含类型定义,Type只存储类型定义的索引。
我么可以通过以下方法获取Type:

  1. System.Type.GetType
  2. System.Type.ReflectionOnlyGetType — 只能通过reflect调用里面的方法
  3. System.Reflection.TypeInfo.GetDeclaredNestedType
  4. System.Reflection.Assemly.GetType or ExportedTypes or DefinedTypes
  5. typeof operator — early-bound
    TypeInfo包含了类型的大量信息,我们可以通过System.Reflection.TrospectionExtensions的GetTypeInfo去转换Type到TypeInfo(TypeInfo在.NET 4.5才开始支持),然后通过TypeInfo去获取Type的相关信息。
    也可以通过调用AsType把TypeInfo转回Type。

现在我们得到了Type,我们而已通过一下方法使用Type去构建一个实例对象:

  1. System.Activator.CreateInstance
  2. System.Activator.CreateInstanceFrom
  3. System.AppDomain.CreateInstance or ……
  4. System.Reflection.ConstructorInfo.Invoke
    上述方法不能用于创建array和delegate:
    创建array使用Array.CreateInstance
    创建delegate使用MethodInfo.CreateDelegate
    当创建泛型实例的时候,我们需要先调用Type.MakeGenericType去设置泛型类的T参数,然后返回的Type是泛型类的Type了,然后通过前面讲到的方法就能创建出泛型类实例了。
    一下是创建一个泛型类实例的过程:
    1
    2
    3
    4
    Type closedtype = opentype.MakeGenericType(typeof(string), typeof(int));
    Object o = Activator.CreateInstance(closedtype);

    Console.WriteLine(o.GetType());

CreateGenericInstance
知道了如何通过Type创建实例对象,接下来让我们看看如何通过反射去访问Type里的所有信息:
在开始之前让我们先来看看Reflection里的类是如何应对到Type里的各个信息里的(e.g. Method, Field, Property, Event……)
ClassHierchyOfReflection
MemberInfo代表了Type里的所有成员信息,FieldInfo,PropertyInfo,EventInfo,MethodBase等都分别对应了类定义里的成员,属性,事件,方法等信息
接下来让我们修改一下之前定义的CSharpDLL.dll里的代码:

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
33
34
35
36
37
using System;

namespace CSharpDLL
{
public class Program
{
public class CSharpDLLPublicClass1
{
public CSharpDLLPublicClass1()
{

mPublicClass1ID = 0;
}

public void CSharpDLLPublicClass1Method()
{

Console.WriteLine("CSharpDLLPublicClass1Method() called");
}

public int PublicCLass1ID
{
get
{
return mPublicClass1ID;
}
set
{
mPublicClass1ID = value;
}
}
private int mPublicClass1ID;
}

static void Main(string[] args)
{

}
}
}

然后通过Reflection里的方法,把CSharpDLL.dll里的所有public的类型定义信息打印出来
因为Type.GetTypeInfo()在.NET 4.5才开始支持,所以这里我通过Type.GetMembers()去访问public的成员信息并打印而非所有的成员信息

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
33
34
35
36
37
38
39
40
41
42
43
private static void PritAllTypeInfoInAssembly(string assemblename)
{

Assembly a = Assembly.LoadFrom(assemblename);
Console.WriteLine(string.Format("{0}.Fullname = {1}",assemblename,a.FullName));
foreach (Type t in a.GetExportedTypes())
{
Console.WriteLine(string.Format("Type = {0}", t));
foreach (MemberInfo mi in t.GetMembers())
{
String typename = String.Empty;
if (mi is Type)
{
typename = "Type";
}
else if (mi is FieldInfo)
{

typename = "FieldInfo";
}
else if (mi is MethodInfo)
{

typename = "MethodInfo";
}
else if (mi is ConstructorInfo)
{

typename = "ConstructorInfo";
}
else if (mi is PropertyInfo)
{

typename = "PropertyInfo";
}
else if (mi is EventInfo)
{

typename = "EventInfo";
}
Console.WriteLine(string.Format("{0} : {1}", typename, mi.ToString()));
}
}
}

static void Main(string[] args)
{

PritAllTypeInfoInAssembly("CSharpDLL.dll");
}

PrintOutPublicMemberInfo
这样一来就打印出了所有public的MemberInfo
下面是Reflection访问程序信息的层次结构图:
ReflectionClassHierarchical
既然能够访问特定的类型信息了,那么通过reflection去访问调用就易如反掌了:
我们只需通过构造函数构建一个实例,然后通过Invoke方法传递实例对象就能调用对应方法了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static void ReflectionInvoke(string assemblename, string classname, string methodname)
{

Assembly a = Assembly.LoadFrom(assemblename);
Console.WriteLine(string.Format("{0}.Fullname = {1}",assemblename,a.FullName));
foreach (Type t in a.GetExportedTypes())
{
if (t is Type)
{
if (t.Name == classname)
{
ConstructorInfo constructor = t.GetConstructor(Type.EmptyTypes);
object instance = constructor.Invoke(new object[] { });
MethodInfo methods = t.GetMethod(methodname);
methods.Invoke(instance, new object[]{});
}
}
}

}
static void Main(string[] args)
{

ReflectionInvoke("CSharpDLL.dll", "CSharpDLLPublicClass1","CSharpDLLPublicClass1Method");
}

ReflectionMethodInvoke
这样一来我们就实现了动态加载Assemble,然后通过反射调用里面特定类的特定方法。
关于反射使用Event并动态创建Delegate参考《CLR vir C#》 — Assembly Loading and Reflection章节

注意下面讲到的内容和书上的测试结果不一致,暂时不知道为什么。结论对错暂时不予置评。
如果我们要频繁的通过反射去访问特定类里的方法和成员,我们会采用存储Type,MemberInfo-derived Object到collection的方式,然后再通过collection去访问。
“Type and MemberInfo-derived objects require a lot of memory.”
但Type,MemberInfo及子类存储了大量的类型信息,会耗费大量的内存。
如何解决这个问题了?
“Developers who are saving/caching a lot of Type and MemberInfoderived
objects can reduce their working set by using run-time handles instead of objects.”
通过存储run-time handles而非Type,MemberInfo object本身可以节约大量内存,然后通过run-time handles转换到对应Type/MemberInfo去访问类型信息。

  1. RuntimeTypeHandle
  2. RuntimeFieldHandle
  3. RuntimeMethodHandle
    “All of these types are value types that contain just one field, an IntPtr. The IntPtr field is a handle that refers to a type, field, or method in an AppDomain’s loader heap.”
    Run-time handles只包含一个成员,那就是IntPtr,这里存储的相当于类型信息的索引或指针。而真正的类型信息是存储在AppDomain的Loader heap上。
    所以我们只需要通过去构造run-time handles指向AppDomain loader heap上特定type,field,method,然后通过转换handle到对应Type/MemberInfo去访问类型信息就能避免存储大量的Type,MemberInfo对象,从而达到节约内存的目的。
    那么我们来看看如何通过run-time handle到底能节约多少内存?如何通过转换run-time handle去访问类型信息:
    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    private static void ShowHeapMemoryUsing(string postfix)
    {

    Console.WriteLine(string.Format("Heap Memory Size = {0} -- {1}",GC.GetTotalMemory(true), postfix));
    }

    private static void RuntimeTypeHandleAccessObjectTypeInfo()
    {

    ShowHeapMemoryUsing("Before do anything!");

    List<MethodBase> methodinfos = new List<MethodBase>();
    foreach (Type t in typeof(Object).Assembly.GetExportedTypes())
    {

    //skip over any generic types
    if(t.IsGenericTypeDefinition) continue;

    MethodBase[] mb = t.GetMethods();
    methodinfos.AddRange(mb);
    }

    Console.WriteLine(string.Format("Methods Number in Object : {0}",methodinfos.Count));

    ShowHeapMemoryUsing("After building cache of MethodInfo objects!");

    //Build cache of RuntimeMethodHandles for all MethodInfo objects in class.name = classname
    List<RuntimeMethodHandle> methodhandles = methodinfos.ConvertAll<RuntimeMethodHandle>(mb => mb.MethodHandle);

    ShowHeapMemoryUsing("Holding MethodInfo and RuntimeMethodHandle cache!");

    //Prevent cache from being GC'd early
    GC.KeepAlive(methodinfos);

    //Allow cache from being GC's now
    methodinfos = null;

    ShowHeapMemoryUsing("After freeing MethodInfo Objects!");

    //Obtain methodinfos from methodhandle
    methodinfos = methodhandles.ConvertAll<MethodBase>(rmh => MethodBase.GetMethodFromHandle(rmh));

    ShowHeapMemoryUsing("Size of heap after re-creating MethodInfo objects!");

    GC.KeepAlive(methodhandles);
    GC.KeepAlive(methodinfos);

    //Alow cache to be GC'd now
    methodhandles = null;
    methodinfos = null;

    ShowHeapMemoryUsing("After freeing MethodInfos and RuntimeMethodHandles!");
    }

RuntimeHandlesUsing
上述测试结果和《CLR via C#》测试结果完全不一致:
RuntimeHandlesTestInBook
对于释放之后methodinfos后,内存没有减少,重新转换run-time handle到
当我们得到run-time handles后,我们可以通过转换run-time handle到MethodBase后反倒内存使用减少。(对于run-time handles是否能够减少内存使用,这里表示疑问)

1
2
3
4
5
Object objinstance = typeof(Object).GetConstructor(new Type[] { }).Invoke(new Object[]{});

MethodBase migethashcode = methodinfos.Find( mi=> mi.Name == "GetHashCode");

Console.WriteLine(string.Format("objinstance.GetHashCode = {0}",migethashcode.Invoke(objinstance, new Object[]{})));

Note:
“The CLR doesn’t support the ability to unload individual assemblies.you want to unload an assembly, you must unload the entire AppDomain that contains it.”(CLR不支持unload单独的assembly,如果需要unload assembly只能通过unload加载了该assembly的AppDomian来实现)
“avoid using reflection to access a field or invoke a method property.”(
尽量避免使用反射去调用方法和访问属性成员,因为很慢)

在学习了Hosting,AppDomain,Assembly,Reflection相关知识后,让我们看看Serialization是如何实现的。

Runtime Serialization

“Serialization is the process of converting an object or a graph of connected objects into a stream of
bytes. Deserialization is the process of converting a stream of bytes back into its graph of connected objects.”
序列化和反序列化支持我们把对象信息存储到bytes里,然后通过bytes去构建对象。

“When serializing an object, the full name of the type and the name of the type’s defining assembly are written to the stream.When deserializing an object, the formatter first grabs the assembly identity and ensures that the assembly is loaded into the executing AppDomain by calling System.Reflection.Assembly’s Load method.”
从上面可以看出来,序列化和反序列化的关键技术是通过写入类型信息和类型数据到byte里,然后通过反射实例化出对象。
反序列化的时候一定要确保正确的Assembly被加载,并且类型信息要和序列化时候使用的类型信息对应上。

那么怎么样才是使得类型信息支持序列化了?
我们需要在支持序列化的类型的定义前面加上flag:
[Serializable]]
“the SerializableAttribute attribute is not inherited by derived types.”
序列化的flag只对父类有效。
既然可以指定可序列化,那么当然也可以指定不支持序列化的flag:
[NonSerialized]
那么这些不支持反序列化的成员信息,如何确保在反序列化的时候初始化到正确的值了?
这里就需要一个flag:
[OnDeserialized]
OnDeserialized标记的方法会在反序列化该类型的时候被调用,用于初始化那些NonSerialized的成员信息。
那么如果我们在将来添加了类型信息里的成员定义,反序列化的时候需要怎样才能保证不出错了?
只需要在新添加的成员定义前添加下面这个flag:
[OptionalFieldAttribute]
Specifies that a field can be missing from a serialization stream so that the BinaryFormatter and the SoapFormatter does not throw an exception..aspx)
标记该成员可以在序列化的时候缺失,不抛出异常。
更多的序列化相关控制标记参见如下:
[OnSerializing] — called during serialization of an object
[OnSerialized] — called after serialization of an object
[OnDeserializing] — called during deserialization of an object
[OnDeserialized] — called immediately after deserialization of an object
Note:
定义了以上flag的方法必须带一个StreamingContext的参数

接下来让我们详细看看Serialize的过程:
先大概了解下Serialization和Deserialization的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Serializable]
publci class Map
{
......
}

//Serialization
BinaryFormatter bf = new BinaryFormatter ();
FileStream fs = File.Open (mMapSavePath, FileMode.Open);
bf.Serialize (fs, mMap);
fs.Close ();

//Deserialization
BinaryFormatter bf = new BinaryFormatter ();
FileStream fs = File.Open (mMapSavePath, FileMode.Open);
mMap = (Map)bf.Deserialize (fs);
fs.Close ();

以下内容源于《CLR via C#》
Serialize Steps:

  1. The formatter calls FormatterServices’s GetSerializableMembers method.
    public static MemberInfo[] GetSerializableMembers(Type type, StreamingContext context);
    首先获取所有需要Serialize的成员信息(MemberInfo),返回MemberInfo[]
  2. The object being serialized and the array of System.Reflection.MemberInfo objects are then passed to FormatterServices’ static GetObjectData method.
    通过MemberInfo去获取Object里成员信息的值,存储在Object[]里
  3. The formatter writes the assembly’s identity and the type’s full name to the stream.
    把相关的assembly identity,type名字写入stream
  4. The formatter then enumerates over the elements in the two arrays, writing each member’s name and value to the stream.
    最后把所有MemberInfo名字(MemberInfo[]里)和实际Object成员值(Objectp[]里)分别对应写入stream。

Deserialize Steps:

  1. 首先通过写入stream的assembly identity和type name去判断对应的Assembly是否已经加载。
    如果加载了就通过FormatterServices::GetTypeFromAssembly去获取需要deserialize的type信息
  2. 然后通过FormatterServices::GetUninitializedObject去预分配内存但不调用构造函数,所有成员数据为null or 0
  3. 然后利用FormatterSerices::GetSerializableMembers得到支持序列化的类型成员信息用于构建和初始化
  4. 读取之前序列化保存成员数据信息
  5. 利用前面得到的支持序列化的成员信息和读取出的成员数据信息去初始化Object。FormatterServices::PopulateObjectMembers方法负责填充数据。

因为序列化底层是通过反射来实现的,但反射是很慢的,如何高效的序列化数据了?
前面我们提到,序列化和反序列化真正去填充或读取的序列化和反序列数据是在调用FormatterServices::GetObjectData方法里。而默认的GetObjectData的数据填充是通过反射来实现的,所以我们只要使支持序列化的类实现ISerializaable的GetObjectData去自定义数据填充就能避免数据填充式reflection的使用。
确保传入GetObjectData的数据安全,在GetObjectData定义前加上:
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
还有就是需要定义一个特殊的构造函数,在Deserialization之前会被调用,这里会传入我们反序列化的SerializationInfo数据,然后我们通过IDeserializationCallback.OnDeserialization(Object sender)去填充数据完成反序列化。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
[Serializable]
public class Map : ISerializable, IDeserializationCallback
{
//Special construct(required by ISerializable) to control deserialization
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
protected Map(SerializationInfo info, StreamingContext context)
{
Console.WriteLine("protected Map(SerializationInfo info, StreamingContext context) called!");
m_SiInfo = info;
}

[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
Console.WriteLine("Map::GetObjectData() called!");
info.AddValue("mID", mID);
info.AddValue("mMapName", mMapName);
}

void IDeserializationCallback.OnDeserialization(Object sender)
{
Console.WriteLine("Map::OnDeserialization() called!");
if (m_SiInfo == null)
{
return;
}

mID = m_SiInfo.GetInt32("mID");
mMapName = m_SiInfo.GetString("mMapName");
}

private SerializationInfo m_SiInfo;

public Map()
{
mID = 0;
mMapName = "DefaultMap";
}

public int ID
{
get
{
return mID;
}
set
{
mID = value;
}

}
private int mID;

public string MapName
{
get
{
return mMapName;
}
set
{
mMapName = value;
}
}
private string mMapName;
}

static void Main(string[] args)
{
string mMapSavePath = "./mapInfo.dat";
Map mMap = new Map();
mMap.ID = 110;
mMap.MapName = "TonyMap";
BinaryFormatter bf = new BinaryFormatter ();
if (!File.Exists(mMapSavePath))
{
FileStream fsc = File.Create(mMapSavePath);
fsc.Close();
}

FileStream fs = File.Open(mMapSavePath, FileMode.Open);
bf.Serialize(fs, mMap);
fs.Close();

//Deserialization
Map mDSMap;
BinaryFormatter dsbf = new BinaryFormatter ();
if (File.Exists(mMapSavePath))
{
FileStream dsfs = File.Open(mMapSavePath, FileMode.Open);
mDSMap = (Map)dsbf.Deserialize(dsfs);
dsfs.Close();
Console.WriteLine(string.Format("Map.ID = {0}, Map.MapName = {1}", mDSMap.ID, mDSMap.MapName));
}
}

Serialization
可以看到我们成功自定义了数据的填充和解析,避免了不必要的reflection调用。(

—————————————-2018/04/22——————————————————-
但实际测试发现并没有加快序列化和反序列化的速度,反而增加了内存开销。详情参考:Data-Config-Automation)
—————————————-2018/04/22——————————————————-

更多关于Serialization学习参考《CLR via C#》 — Runtime Serialization章节

Note:
The .NET Framework also offers other serialization technologies that are designed
more for interoperating between CLR data types and non-CLR data types. (以下serialization技术支持CLR data type和non-CLR data types之间的交互,支持从XML序列化和反序列化,这里暂时没有深入学习了解)

  1. System.Xml.Serialization.XmlSerializer class
  2. System.Runtime.Serialization.DataContractSerializer class
    还有一种方式序列化是通过SoapFormatter类,.soap格式。
    还有一种高效的平台无关话的序列化反序列化方式,参见Google Protocol Buffer学习
    待续……

Platform Invoke

跨语言的调用,比如managed的C#调用unmanaged的C++代码
DllImport — Allow reusing existing unmanaged code in a managed application.

DllImport Attribute在DllImport的时候很重要,确保我们能找到正确的unmanaged function,传入正确的参数类型等。

DllImport Attribute有以下几个重要的参数:
EntryPoint — 指明我们将要导入的unmanaged方法名(只有指明正确的方法名,才能找到该方法)
CharSet — 指明如何去处理string类型,比如unicode or ansi(宽字符和单个字符是不一样的)
CallingConvention — 指明函数的调用约定(一般我们会涉及到stdcall和cdecl,前者是C++的标准调用约定,后者是C语言调用约定,只有用同样的调用约定我们才能正确调用方法)
Note:
不同的调用约定会决定参数的传入顺序,传参方式,堆栈维护,如何生成方法名等。

普通参数类型的调用事例:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
MyMath.h
#ifndef MATH_H
#define MATH_H
#endif

#include "stdafx.h"

#define UTILITYDLL_API _declspec(dllexport)

class MyMath
{
public:
static UTILITYDLL_API int __stdcall MyAdd(int a, int b);

static UTILITYDLL_API int __cdecl MySubstract(int a, int b);
};

MyMath.cpp
#include "stdafx.h"
#include "MyMath.h"
#include <string>

using namespace std;

UTILITYDLL_API int __stdcall MyMath::MyAdd(int a, int b)
{
return a + b;
}

UTILITYDLL_API int __cdecl MyMath::MySubstract(int a, int b)
{
return a - b;
}

extern "C"
{
UTILITYDLL_API double MyMultiple(double a, double b)
{

return a * b;
}
};

UTILITYDLL_API double __stdcall MyDivision(double a, double b)
{

return a / b;
}

struct MyStruct
{
int mID;
bool mBMan;
};
extern "C"
{
UTILITYDLL_API int ModifyMyStruct(MyStruct* ms)
{

if(ms->mBMan == true)
{
ms->mBMan = false;
return sizeof(MyStruct);
}else
{
ms->mID = -1;
ms->mAge = -1;
return sizeof(MyStruct);
}
}
};

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Collections.Generic;

using System.Runtime.InteropServices;

namespace CSharpStudy
{
class Program
{
#region DLL import Study
[StructLayout(LayoutKind.Explicit, Pack = 1)]
public struct MyStruct
{
[FieldOffset(0)] public int mID;
[FieldOffset(4)] public bool mBMan;
[FieldOffset(5)] public int mAge;
}

[StructLayout(LayoutKind.Sequential)]
public struct MyStruct2
{
public int mID;
public bool mBMan;
public int mAge;
}

[StructLayout(LayoutKind.Explicit)]
public struct MyStructExplicit
{
[FieldOffset(0)] public Byte mByte;
[FieldOffset(4)] public int mID;
}

[StructLayout(LayoutKind.Explicit, Pack = 1)]
public struct MyStructExplicit2
{
[FieldOffset(0)]
public Byte mByte;
[FieldOffset(1)]
public int mID;
}

[StructLayout(LayoutKind.Explicit, Pack = 2)]
public struct MyStructExplicit3
{
[FieldOffset(0)] public Byte mByte;
[FieldOffset(2)] public int mID;
}

[DllImport("TESTDLL.dll", EntryPoint = "?MyAdd@MyMath@@SGHHH@Z")]
public static extern int MyAdd(int a, int b);

[DllImport("TESTDLL.dll", EntryPoint = "?MySubstract@MyMath@@SAHHH@Z", CallingConvention = CallingConvention.Cdecl)]
public static extern int MySubstract(int a, int b);

[DllImport("TESTDLL.dll", EntryPoint = "MyMultiple", CallingConvention = CallingConvention.Cdecl)]
public static extern double MyMultiple(double a, double b);
#endregion

static void Main(string[] args)
{

#region DLL import Study
int a = 1;
int b = 2;
int sum = 0;
sum = MyAdd(a, b);
Console.WriteLine(string.Format("{0} + {1} = {2}", a, b, sum));

int substractionresult = 0;
substractionresult = MySubstract(a, b);
Console.WriteLine(string.Format("{0} - {1} = {2}", a, b, substractionresult));

double multiplier = 1;
double multiplicand = 2;
double multipleresult = 0;
multipleresult = MyMultiple(multiplier, multiplicand);
Console.WriteLine(string.Format("{0} * {1} = {2}", multiplier, multiplicand, multipleresult));

double divisor = 1;
double dividend = 2;
double divisionresult = 0;
divisionresult = MyDivision(divisor, dividend);
Console.WriteLine(string.Format("{0} / {1} = {2}", divisor, dividend, divisionresult));

MyStruct ms = new MyStruct();
MyStruct2 ms2 = new MyStruct2();
MyStructExplicit mse = new MyStructExplicit();
MyStructExplicit2 mse2 = new MyStructExplicit2();
MyStructExplicit3 mse3 = new MyStructExplicit3();
Int32 sizeofms = 0;
ms.mID = 4;
ms.mBMan = false;
ms.mAge = 4;
ms2.mID = 5;
ms2.mBMan = false;
ms2.mID = 5;
mse.mID = 1;
mse.mByte = 1;
mse2.mID = 2;
mse2.mByte = 2;
mse3.mID = 3;
mse3.mByte = 3;
Console.WriteLine(string.Format("sizeof(MyStruct) = {0}", Marshal.SizeOf(ms)));
Console.WriteLine(string.Format("sizeof(MyStructExplicit) = {0}", Marshal.SizeOf(mse)));
Console.WriteLine(string.Format("sizeof(MyStructExplicit2) = {0}", Marshal.SizeOf(mse2)));
Console.WriteLine(string.Format("sizeof(MyStructExplicit3) = {0}", Marshal.SizeOf(mse3)));
Console.WriteLine(string.Format("ms.mID = {0}, ms.mBMan = {1}, ms.mAge = {2}", ms.mID, ms.mBMan, ms.mAge));
sizeofms = ModifyMyStruct(ref ms);
Console.WriteLine(string.Format("ms.mID = {0}, ms.mBMan = {1}, ms.mAge = {2}, sizeofms = {3}", ms.mID, ms.mBMan, ms.mAge, sizeofms));
Console.WriteLine(string.Format("ms2.mID = {0}, ms2.mBMan = {1}, ms2.mAge = {2}", ms2.mID, ms2.mBMan, ms2.mAge));
sizeofms = ModifyMyStruct2(ref ms2);
Console.WriteLine(string.Format("ms2.mID = {0}, ms2.mBMan = {1}, ms2.mAge = {2}, sizeofms = {3}", ms2.mID, ms2.mBMan, ms2.mAge, sizeofms));
#endregion

Console.ReadKey();
}
}
}

Output:
PlatformInvokeDemo

分析上述事例:
针对MyAdd方法我们定义了Stdcall的C++调用约定方式,而且属于类的静态方法,所以在C#中import的时候我们需要指明具体的方法名(通过VS自带的dumpbin我们可以打出TESTDLL.dll里的符号表信息 — dmpbin.exe /all TESTDLL.dll > TestDllDump.txt,我们可以找到MyAdd方法的具体方法名,否者会显示找不到EntryPoint方法MyAdd),原本还需要指明调用约定为stdcall,但CallingConvention的默认值就是__stdcall,所以这里就不用指明了。

针对MySubstract方法我们定义了__cdecl的C调用约定方式,而且属于类的静态方法,所以我们在C#中import的时候需要指明具体的方法名和指明调用约定为CallingConvention = CallingConvention.Cdecl,否者会显示调用堆栈不对称等问题。

针对MyMultiple方法我们定义了cdecl的C调用约定方式,同时是全局方法,所以我们只需要指明调用约定为cdecl,直接指明调用方法为MyMultiple就能找到MyMultiple方法了。

针对MyDivision方法我们定义了stdcall的C++调用约定方式,同时是全局方法,但由于stdcall调用约定对方法名生成的方式(包含函数名,参数字节数信息等),我们不能直接通过MyDivision来调用MyDivision方法而需要在EntryPoint里指定方法全名。

除了找到正确的函数方法和指定正确的函数调用约定等信息外,我们在Manage code调用Unmanaged code的时候需要保证Manage struct和Unmanage struct的内存布局要一致,以确保unmanaged code访问managed数据的时候拿到正确信息。

These structures can have any legal name; there is no relationship between the native and managed version of the two structures other than their data layout. Therefore, it is vital that the managed version contains fields that are the same size and in the same order as the native version.
从官网可以看出,managed code的函数名字并不重要,我们必须确保结构体的内存布局要一致。

那我们为何要进行内存对齐了?
一下参考百度百科:

平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬
件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问
未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问

从测试例子里可以看出,当我们通过StructLayoutAttribute.Pack指定MyStuct的内存对齐方式是1的时候,MyStruct的大小只有9bytes,而MyStruct2使用默认采用结构体内数据最大的值作为对齐方式,事例中是int即4bytes为对齐方式,MyStruct2的大小是12bytes。反观C++返回的MyStruct的大小是12(可以看出是以4bytes为对齐方式),所以如果我们传递MyStruct的时候访问数据就出问题了,而MyStruct2访问到了正确的数据。

而后续的MyStructExplicit,MyStructExplicit2,MyStructExplicit3则展示了通过制定Pack的值(即内存对齐大小)是如何影响数据结构的大小的。

更多:
StructLayoutAttribute.Pack
在 Visual Studio 2015 之前,可以使用 Microsoft 专用关键字 __alignof 和 declspec(alignas) 来指定大于默认值的对齐方式。从 Visual Studio 2015 开始,应使用 C++11 标准关键字 alignof 和 alignas (C++) 以获得最高代码可移植性。

Event

C#里的Event响应相当于listener and obsever 模式里触发监听回调。Delegate好比C++里的回调 — 当事件发生时调用

“The common language runtime’s (CLR’s) event model is based on delegates.”
event key word

定义Event我们需要做一下几件事,下面模拟邮件收发事件提醒为例:

  1. 定义EventArgs(这个必须继承至EventArgs,里面包含了我们事件发生时所需要传递的信息,如果什么都不需要传递,使用EventArgs即可)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // Define a type that will hold any additional information that should be sent to receivers of the event notification
    internal class NewMailEventArgs : EventArgs
    {
    public NewMailEventArgs(String from, String to, String subject)
    {

    m_From = from;
    m_To = to;
    m_Subject = subject;
    }

    public String From{ get { return m_From; } }

    private readonly String m_From;

    public String To { get { return m_To; } }

    private readonly String m_To;

    public String Subject { get { return m_Subject; } }

    private readonly String m_Subject;
    }
  2. 定义Event成员,用于指定监听什么样的Event

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class EmailManager
    {
    // Define the event member
    // 虽然这里只有简短的一句话,
    // 但是编译器会给我们去定义关于此事件的监听添加和删除代码
    // 详情请看下面截图
    // 这样一来我们只要通过NewEmail就能添加和删除监听NewMailEventArgs事件的delegate了
    // EventHandler决定了我们监听事件的Delegate原型如下
    // public delegate void EventHandler(object sender, EventArgs e);
    // 监听的事件是NewMailEventArgs
    public event EventHandler<NewMailEventArgs> NewMail;
    }

    EventExtraCode

  3. 定义需要监听事件的类并提供添加和删除监听的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class Fax
    {
    public Fax(EmailManager em)
    {

    // Trigger add_NewMail method
    em.NewMail += FaxMsg;
    }

    // Delegate that is used to listen for NewMailEventArgs
    public void FaxMsg(Object sender, NewMailEventArgs e)
    {

    Console.WriteLine("Faxing mail message:");
    Console.WriteLine("From = {0}, To = {1}, Subject = {2}", e.From, e.To, e.Subject);
    }

    // Remove listener for NewMailEventArgs
    public void Unregester(EmailManager em)
    {

    // Trigger remove_NewMail method
    em.NewMail -= FaxMsg;
    }
    }
  4. 定义事件触发和事件通知方法

    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
    33
    class EmailManager
    {
    ......

    // Define a method responsible for raising the event
    // to notify registered objects that the event has occurred
    // If this class is sealed, make this method private and nonvirtual
    // 事件通知
    protected virtual void OnNewMail(NewMailEventArgs e)
    {

    // Copy a reference to the delegate field now into a temporary field for thread safety
    // Be careful race condition
    EventHandler<NewMailEventArgs> temp = NewMail;
    // 这里有thread safe问题,但由于delegate is immutable,
    // 所以我们把NewMail传递给临时变量temp后,无论别人如何改变NewMail都没关系了
    // If any methods registered interest with our event, notify them
    if (temp != null)
    {
    temp(this, e);
    }
    }

    // Define a method that translates the input into the desired event
    // 事件触发
    public void SimulateNewMail(String from, String to, String subject)
    {

    // Hold the information we want to pass
    NewMailEventArgs e = new NewMailEventArgs(from, to, subject);

    // Call OnNewMail to notify registered objects that the event has occured
    OnNewMail(e);
    }
    }
  5. 测试Event

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    static void Main(string[] args)
    {

    EmailManager emailmanager = new EmailManager();

    Fax fax = new Fax(emailmanager);

    emailmanager.SimulateNewMail("Tony", "Tom", "Hello World!");

    fax.Unregester(emailmanager);

    emailmanager.SimulateNewMail("Tom", "Tony", "Hello World Again!");
    }

Test Result:
EventTestResult
可以看出我们成功的添加了自定义的事件监听也成功的移除了事件监听。

Note:
一般来说,设计事件监听都会设计成通过Dictionary来存储EventKey和Delegate,通过判断Dictinary里面是否存在该事件来添加Delegate,如果不存在则添加该事件监听。删除监听同理。

Chars, Strings, and Working with Text

Chars:
“In the .NET Framework, characters are always represented in 16-bit Unicode code values, easing the development of global applications.A character is represented with an instance of the System.Char structure (a value type).”

说到字符和字符串,就不得不提字符编码了,从上面可以看出,.NET的Char都是采用16bit的Unicode编码,主要是为了语言通用话(包含所有的字符编码)。还有一点就是Char是Structure是Value type。

针对字符本身的方法(Char):
Char还提供了很多获取字符具体类型等相关信息的方法,e.g IsDigit, IsLetter,
IsWhiteSpace, IsUpper,GetUnicodeCategory(得到字符关于Unicode的分类信息)……

1
2
3
4
5
6
Char c = 'a';
UnicodeCategory uc = Char.GetUnicodeCategory(c);
Console.WriteLine("UnicodeCategory = {0}", uc.ToString());
c = '1';
uc = Char.GetUnicodeCategory(c);
Console.WriteLine("UnicodeCategory = {0}", uc.ToString());

针对字符全球化(CultureInfo):
在Char里面很多方法都有包含CultureInfo参数类型的版本,这个是针对全球化指定特定语言。

1
2
CultureInfo ci = CultureInfo.CurrentCulture;
Console.WriteLine("CurrentCulture = {0}", ci);

CharAndCultureInfoOutput

Strings:
“The String type is derived immediately from Object, making it a reference type.”

String的构建:

  1. 通过Literal string构建
    虽然String是reference type,但我们构建String的时候不是通过new,而直接通过literal string(e.g. String s = “Tony”;)
  2. 支持像C++里那样特殊符号代表特定含义
    String s = “Hi\r\nthere”;
  3. 跨平台考虑
    特殊符号在不同平台有不同的表示方式,所以出于跨平台考虑,我们最好使用Environment里的变量来表示特定环境的特定符号
    1
    2
    3
    4
    5
    6
    String s1= "Tony";
    String s2 = "Hi\nTony!";
    String s3 = "Hi" + Environment.NewLine + "Tom!";
    Console.WriteLine("s1 = {0}", s1);
    Console.WriteLine("s2 = {0}", s2);
    Console.WriteLine("s3 = {0}", s3);

StringConstructOutput

多个String合并:
多个字符串合并的时候最主要的是避免通过重复的literal string去构建String,因为String是reference type,多个literal string的构建会在heap上分配多个内存。正确的是通过System.Text.StringBuilder去构建。

“StringBuilder’s members allow you to manipulate this character array, effectively shrinking the string or changing the characters in the string.”

Unlike a String, a StringBuilder represents a mutable string. This means that most of StringBuilder’s members change the contents in the array of characters and don’t cause new objects to be allocated on the managed heap.”

可以看出StringBuilder之所以不会构建多个String是因为它内部构建了可变的character array,这样允许我们在StringBuilder里操作的时候不会触发新的String构建。这样以来我们就可以利用里面现有的String去动态构建我们需要的String了。
Error:

1
String s = "Hi" + "there!";

Right:

1
2
3
4
5
6
7
8
String sp1 = "Tony";
String sp2 = " and ";
String sp3 = "Tom";
StringBuilder sb = new StringBuilder("Hello ", 50);
sb.Append(sp1);
sb.Append(sp2);
sb.Append(sp3);
Console.WriteLine("sb = {0}", sb.ToString());

StringCatenationOutput

String比较:
String.Compare(*)
……
比较的时候需要注意可能有不同国家的语言,这里需要注意需要传入CultureInfo作为比较的语言环境参数。

同时当我们的代码文件中直接书写了特定国家语言或语言Unicode编码的时候,我们需要把文件存储为Unicode格式,否则到时候编译器无法正常解析。

程序中大量的比较特别是针对特定国家语言的String比较很费时,我们应该尽量避免。

同时String是immuatable的,我们可以重复利用现有的String,无需大量重复构造相同的String去增加memory负担。
CLR里有一个叫internal hash table的东西,所有的Strings作为key,所有String的reference作为value。因为String是immutable的且是reference type,我们可以通过访问internal hash table去查看是否存在现有String,这样一来就避免了重构相同的String。

1
2
3
String ss = "internal string";
String sintern = String.Intern(ss);
Console.WriteLine("sintern = {0}",sintern);

StringConstructionWithMemorySave

“String objects referred to by the internal hash table can’t be freed until the AppDomain is unloaded or the process terminates.”

“System.Runtime.CompilerServices.CompilationRelaxations.NoStringInterning flag will control whether to inern all of the string.”

Security String:
System.Security.SecureString ……

Note:
String object is immutable. The String class is sealed, which means that you cannot use it as a base class for your own type.

Enumerated and Bit Flags

  1. Enum
    “Every enumerated type is derived directly from System.Enum, which is derived from System.ValueType, which in turn is derived from System.Object”
    首先可以看出Enumerated属于Value Type。
    这里还是说一下使用Enumerate的好处:

    1. “Enumerated types make the program much easier to write, read, and maintain.”(可视化,容易看懂,无需hard code)
    2. “Enumerated types are strongly typed.”(类型传递不对,编译器会报错)
      Enumerate在C#里作为一个最基础的type,且是面向对象的,C#给我们提供了很多可以相互转化的方法(e.g. 比如Enum到String — ToString() String到Enum — Parse())

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      enum Colors
      {
      BLACK = 0,
      RED = 1,
      GREEN = 2,
      BLUE = 3,
      WHITE = 4
      };

      static void Main(string[] args)
      {

      Colors c = Colors.RED;
      Console.WriteLine("Decimal format: c = {0}", c.ToString("D"));
      Console.WriteLine("General format: c = {0}", c.ToString("G"));
      Colors c2 = (Colors)Enum.Parse(typeof(Colors), "GREEN", true);
      Console.WriteLine("Decimal format: c2 = {0}", c2.ToString("D"));
      Console.WriteLine("General format: c2 = {0}", c2.ToString("G"));
      }

      Output:
      EnumeratesOuput
      还有一点值得注意的就是enum无法定义methods,properties,or events。
      针对methods我们可以通过C#里extention methods(详情见Methods那一节)的特性给enum添加methods。

      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
      [Flags]
      enum Actions
      {
      NONE = 0,
      READ = 0x0001,
      WRITE = 0x0002,
      READANDWRITE = READ | WRITE,
      DELETE = 0x0004,
      QUERY = 0x0008,
      Sync = 0x0010
      };

      internal static class ActionsExtensionMethods
      {
      public static Actions Set(this Actions flags, Actions setflags)
      {

      return flags | setflags;
      }
      }

      static void Main(string[] args)
      {

      Actions actions = Actions.READ;
      Console.WriteLine("actions = {0}", actions.ToString());
      actions = actions | Actions.DELETE;
      Console.WriteLine("actions = {0}",actions.ToString());
      actions = actions.Set(Actions.WRITE);
      Console.WriteLine("actions = {0}", actions.ToString());
      }

      Output:
      EnumWithExtentionMethod
      Note:
      “Symbols defined by an enumerated type are constant values.”

  2. Bit
    如果说Enum是一个成员代表一个含义,那么Bit可以看做是一个Bit代表一组含义。
    最经常用到的地方就是File的访问控制权限:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public enum FileAttributes {
    ReadOnly = 0x00001,
    Hidden = 0x00002,
    System = 0x00004,
    Directory = 0x00010,
    Archive = 0x00020,
    368 PART III Essential Types
    Device = 0x00040,
    Normal = 0x00080,
    Temporary = 0x00100,
    SparseFile = 0x00200,
    ReparsePoint = 0x00400,
    Compressed = 0x00800,
    Offline = 0x01000,
    NotContentIndexed = 0x02000,
    Encrypted = 0x04000,
    IntegrityStream = 0x08000,
    NoScrubData
    }

这里有个比较方便的用法,可以把enum看做一组Bits。
定义enum的时候加上前缀[Flags],可以使enum的成员被看做一组bits。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[Flags]
enum Actions
{
NONE = 0,
READ = 0x0001,
WRITE = 0x0002,
READANDWRITE = READ | WRITE,
DELETE = 0x0004,
QUERY = 0x0008,
Sync = 0x0010
};

static void Main(string[] args)
{

Actions actions = Actions.READ;
Console.WriteLine("actions = {0}", actions.ToString());
actions = actions | Actions.DELETE;
Console.WriteLine("actions = {0}",actions.ToString());
}

BitWithFlag
BitWithoutFlag

Custom Attributes

“they’re just a way to associate additional information with a target.The compiler emits this additional information into the managed module’s metadata.”
上面这句话可以理解成,custom attributes是为了关联一些额外的信息到特定的目标上(这里的目标可以是class,event,methods…..),这些额外的信息是被编译器编译保存到了module的metadata里。

让我们看个Custom Attributes例子:

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
using System;
[assembly: SomeAttr] // Applied to assembly
[module: SomeAttr] // Applied to module
[type: SomeAttr] // Applied to type
internal sealed class SomeType<[typevar: SomeAttr] T> { // Applied to generic type variable
[field: SomeAttr] // Applied to field
public Int32 SomeField = 0;
[return: SomeAttr] // Applied to return value
[method: SomeAttr] // Applied to method
public Int32 SomeMethod(
[param: SomeAttr] // Applied to parameter
Int32 SomeParam)
{
return SomeParam;
}

[property: SomeAttr] // Applied to property
public String SomeProp {
[method: SomeAttr] // Applied to get accessor method
get { return null; }
}

[event: SomeAttr] // Applied to event
[field: SomeAttr] // Applied to compiler-generated field
[method: SomeAttr] // Applied to compiler-generated add & remove methods
public event EventHandler SomeEvent;
}

从上面可以看出我们可以定义custom attribute的范围很广,包括assembly,module,type,filed,method…….

“A custom attribute is simply an instance of a type.”
Custom attribute其实也是一个类,只是我们定义custom attribute的时候触发了这些类的构造,把custom attribute的信息写入到了metadata里。

那么知道了Custom attribute是一个类,那么怎么定义Custom attribute了?
“Common Language Specification (CLS) compliance, custom attribute classes must be derived, directly or indirectly, from the public abstract System.Attribute class.”
从上面可以看出custom attribute必须直接或间接的继承至System.Attribute class.

接下来我们使用MSDN上的例子来详细了解下Custom Attribute是怎么工作的。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using System;
using System.Reflection;

namespace CustomAttrCS {
// An enumeration of animals. Start at 1 (0 = uninitialized).
public enum Animal {
// Pets.
Dog = 1,
Cat,
Bird,
}

// A custom attribute to allow a target to have a pet.
// 定义Custom attribute必须直接或间接继承至Attribute
public class AnimalTypeAttribute : Attribute {
// The constructor is called when the attribute is set.
// 构建的时候我们可以去设置含参和不含参构造函数
public AnimalTypeAttribute()
{

thePet = Animal.Bird;
}

public AnimalTypeAttribute(Animal pet) {
thePet = pet;
}

// Keep a variable internally ...
protected Animal thePet;

// .. and show a copy to the outside world.
public Animal Pet {
get { return thePet; }
set { thePet = Pet; }
}
}

// A test class where each method has its own pet.
class AnimalTypeTestClass {
// 定义custom attribute的时候,我们可以调用对应构造函数
[AnimalType(Animals.DOG)]
public void DogMethod() { }
// 除了调用构造函数外,我们还可以调用Custom Attribute Class的Property设定特定值
[AnimalType(Pet = Animals.CAT)]
public void CatMethod() { }

[AnimalType()]
public void BirdMethod() { }
}

class DemoClass {
static void Main(string[] args) {
// 通过反射去检查AnimalTypeTestClass里的方法是否定义了Attribute
AnimalTypeTestClass testClass = new AnimalTypeTestClass();
Type type = testClass.GetType();
// Iterate through all the methods of the class.
foreach(MethodInfo mInfo in type.GetMethods()) {
// Iterate through all the Attributes for each method.
foreach (Attribute attr in
Attribute.GetCustomAttributes(mInfo)) {
// Check for the AnimalType attribute.
if (attr.GetType() == typeof(AnimalTypeAttribute))
Console.WriteLine(
"Method {0} has a pet {1} attribute.",
mInfo.Name, ((AnimalTypeAttribute)attr).Pet);
}

}
}
}
}

Output:
CustomAttribute
Note:
“all non-abstract attributes must contain at least one public constructor.”(非abstract attributes必须至少有一个public构造函数)

知道了如何自定义Custom Attribute,但Attribute可以用于Assembly,module,type…..,我们如何限制其使用的地方了?
AttributeUsageAttribute用于指定Custom Attribute的使用范围。
AttributeTargets包含了所有可指定的使用范围。
同时AttributeTargets还有连个成员变量,m_allowMultiple,m_inherited,前者决定这个attribute是否允许针对同一个target设定多个,后者决定含该attribute修饰的类的子类是否继承AttributeUsage设定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[AttributeUsage(AttributeTargets.Method, Inherited = false)]public class AnimalTypeAttribute : Attribute {
......
}

class AnimaTypeTestClass
{
// 因为AnimaTypeAttribute定义了只对Method有效,所以这里对构造函数就无效
//[AnimalType(Animals.CAT)]
//AnimaTypeTestClass() { }

[AnimalType(Animals.DOG)]
public void DogMethod() { }

......
}

知道了Custom Attribute的定义和其限制作用,那么Custom Attribute有什么实际意义了?
还记得在Enumerated Types and Bit Flags讲到的[FLAG]标记改变了Enum.ToString(),Format()行为吗?
正是因为我们动态检查了绑定在Enum上的Flag属性导致的。
而实现动态检查的底层方法是通过reflection(反射 — 参见Hosting,AppDomain,Assembly,Reflection章节)
还记得之前MSDN的例子是如何检查类里各方法是否定义了Attribute吗(这里就是使用了反射)?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 通过反射去检查AnimalTypeTestClass里的方法是否定义了Attribute
AnimalTypeTestClass testClass = new AnimalTypeTestClass();
Type type = testClass.GetType();
// Iterate through all the methods of the class.
foreach(MethodInfo mInfo in type.GetMethods()) {
// Iterate through all the Attributes for each method.
foreach (Attribute attr in
Attribute.GetCustomAttributes(mInfo)) {
// Check for the AnimalType attribute.
if (attr.GetType() == typeof(AnimalTypeAttribute))
Console.WriteLine(
"Method {0} has a pet {1} attribute.",
mInfo.Name, ((AnimalTypeAttribute)attr).Pet);
}
}

这样一来我们就可以动态的判断是否定义了Attribute并获取Attribute里定义的信息。
.NET里有一个System.Reflection.CustomAttributeExtensions class,这个了定义了真多各个target(module,event,method……)的获取关于自定义信息的三个方法:

  1. IsDefined
  2. GetCustomAttributes
  3. GetCustomAttribute
    第二和第三方法调用的时候会触发Attribute Class的构造函数,那么我们怎么才能在不触发Attribute Class构造函数的情况下获取Attrbute信息了?
    答案是:System.Reflection.CustomAttributeData的GetCustomAttributes方法(利用反射,但要注意的是CustomAttributeData的GetCustomAttributes只有四个版本分别是Assembly,Module, ParameterInfo and MemberInfo)

知道了如何去检查method是否包含Custom Attribute,那么我们怎样去判断两个instances完全一样了(所有Custom Attribute都一样)
这里我们可以通过System.Attribute的Equals方法去判断,这里的Equals被重写了,会通过reflection去检查每一个attribute进行比较。
除了上述方法我们也可以在自定义的Attribute里重写Equal和Match方法去实现特定比较判断。

使用和定义Custom attribute的时候需要注意的点:

  1. “When applying an attribute to a target in source code, the C# compiler allows
    you to omit the Attribute suffix to reduce programming typing and to improve the
    readability of the source code.”(注意定义custom attribute的时候,我们可以省略attribute后缀)
  2. “When defining an attribute class’s instance constructor, fields, and properties, you must restrict yourself to a small subset of data types.”(当定义Custom Attribute时,我们只能声明基础类型的fields,properties,constructor(必须符合CLS-compilation))
  3. “Be aware that only Attribute, Type, and MethodInfo classes implement reflection
    methods that honor the Boolean inherit parameter.”(只有Attribute,Type and MethodInfo实现了反射inherit parameter信息的方法)

Exceptions and State Management

What is Exception?
“An expcetion is when a member fails to complete the task it is supposed to perform as indicated by ites name.”

Exception-Handling Mechanics
The .NET Framework exception handling mechanism is built using the Structured Exception Handling(SEH) mechanism offered by Windows.

首先看一下捕获异常的最基本写法:

1
2
3
4
5
6
7
8
9
10
11
12
try{
// Put code requiring graceful recovery and/or cleanup operations here...
}
catch(excetion)
{
// Put code that recovers from any kind of exception
}
finally
{
// Put code that cleans up any operations started within the try block here...
// The code in here ALWAYS executes, regardless of whether an exception is thrown.
}

Try Block:
“A try block contains code that requires common cleanup operations, exception recovery operations, or both.”

Note:
“Sometimes developers ask how much code they should put inside a single try
block. The answer to this depends on state management.”

Catch Block:
“A catch block contains code to execute in response to an exception.”

Note:
“When debugging through a catch block by using Microsoft Visual Studio, you can
see the currently thrown exception object by adding the special $exception variable name to a watch window.”(当调试catch block的时候,可以通过查看$exception变量名查看异常信息)

Finally Block:
“A finally block contains code that’s guaranteed to execute. Typically, the code in a finally block performs the cleanup operations required by actions taken in the try block.”

CLS and Non-CLS Exceptions:
CLS(Common Language Specification) — throw Exception-derived objects
Non-CLS — throw Exception not derived from Exception

After CLR 2.0:
“Microsoft introduced a new RuntimeWrappedException class (defined in the System.Runtime.CompilerServices namespace). This class is derived from Exception, so it is a CLS-compliant exception type. The RuntimeWrappedException class contains a private field of type Object (which can be accessed by using RuntimeWrappedException’s WrappedException read-only property). In CLR 2.0, when a non–CLS-compliant exception is thrown, the CLR automatically constructs an instance of the RuntimeWrappedException class and initializes its private field to refer to the object that was actually thrown.”(CLR 2.0之后,通过RuntimeWrapperdException class把所有的Non-CLS Exception都封装成了CLS Exception)

如果想要就支持2.0之前的行为:

1
2
using System.Runtime.CompilerServices;
[assembly:RuntimeCompatibility(WrapNonExceptionThrows = false)]

接下来让我们看看Exception的基类:
Systen.Exception
以下是一些重要的Properties:
ExceptionProperties
必要重要的一些Properties:

  1. Message(描述了和异常相关的重要信息)
  2. StackTrace(描述了导致抛出异常的方法相关信息)

我们也可以通过System.Diagnostics.StackTrace去获取详细的堆栈信息。

但有些时候我们会发现有些方法没有显示在详细的堆栈信息里:
原因有两个:

  1. the stack is really a record of where the thread should return to, not where the thread has come from. (Stack只记录返回点不记录当前点)
  2. The just-in-time (JIT) compiler can inline methods to avoid the overhead of calling and returning from a separate method(JIT编译器使得一些方法称为了inlie的(在当前方法被调用的地方直接展开),从而无法记录到Stack里)

禁止JIT inlie需要用到System.Runtime.CompilerServices.MethodImplAttribute里的MethodImplOption.NoInlining:

1
2
3
4
[MethodImpl(MethodImplOptions.NoInlining)]
public void SomeMethod() {
......
}

FCL(Framework Class Library)里定义很多现成的Exception。

Throwing an Exception:
当我们需要自己抛出异常的时候,我们需要考虑如下:

  1. 哪一个Exccetion class我们应该继承(是否使用现有的Exception class)
  2. 传递什么样的string到exception构造函数里(传递说明为什么方法不能完成要抛出这个异常)

Defining Your Own Exception Class:
自定义Exception类是比较容易出问题且冗长的。
原因如下:
“The main reason for this is because all Exception-derived types should be serializable so that they can cross an AppDomain boundary or be written to a log or database”(我们必须保证自定义的Exception类支持序列化,因为我们可能会跨AppDomain去写入Log或则数据库里)

Note:
“When you throw an exception, the CLR resets the starting point for the exception;
that is, the CLR remembers only the location where the most recent exception object
was thrown.”(当我们再次抛出异常的时候,CLR会重置异常,只记录最近异常Object)

Guidelines and Best Practices:

  1. Use finally Blocks Liberally(finlly always execute, do cleanup operations)
  2. Do not Catch Everything
  3. Recovering Gracefully from an Exception(catch some exceptions that are known in advanced and try to recover from it)
  4. Backing Out of a Partially Completed Operation When an Unrecoverble Exception Occus — Maintaining State

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public void SerializeObjectGraph(FileStream fs, IFormatter formatter, Object rootObj) {
    // Save the current position of the file.
    Int64 beforeSerialization = fs.Position;
    try {
    // Attempt to serialize the object graph to the file.
    formatter.Serialize(fs, rootObj);
    }
    catch { // Catch any and all exceptions.
    // If ANYTHING goes wrong, reset the file back to a good state.
    fs.Position = beforeSerialization;
    // Truncate the file.
    fs.SetLength(fs.Position);
    // NOTE: The preceding code isn't in a finally block because
    // the stream should be reset only when serialization fails.
    // Let the caller(s) know what happened by re-throwing the SAME exception.
    throw;
    }
    }

    Note:
    “After you’ve caught and handled the exception, don’t swallow it—let the caller know that the exception occurred. You do this by re-throwing the same exception.”(特别是写给别人用的时候,再次抛出异常让使用者可以去捕获并知道发生了什么)

  5. Hiding an Implementation Detail to Maintain a “Contract”
    “you might find it useful to catch one exception and re-throw a different exception.”(抛出更符合当前API行为的异常。或则增加更多符合当前API的异常的信息。)

Unhandled Exceptions:
什么时候会出现Unhandled Exceptions?
“When an exception is thrown, the CLR climbs up the call stack looking for catch blocks that match the type of the exception object being thrown. If no catch block matches the thrown exception type, an unhandled exception occurs.”(当异常被抛出却没有对应的catch的时候,成为Unhandled Exception)
更多内容参考《CLR via C#》 — Unhandled Exceptions

Note:
“When the CLR detects that any thread in the process has had an unhandled exception, the CLR terminates the process.”(当CLR检测到任何线程有未处理的异常的时候,CLR会终止进程)

Debugging Exceptions:
VS -> Debug -> Exception
ExceptionWindow
如果针对特定异常勾选抛出,那么当该异常被抛出的会后,程序会进入断点(帮助我们快速定位特定异常)。不勾选也会进入断点(前提是该异常是unhandled)

也可以通过上述窗口添加自定义的异常。

Exception-Handling Performance Considerations:
……

Constrained Excecution Regions(CERs):
……

更多内容待学习……

The Managed Heap and Garbage Collection

这一小节会讲到CLR里重要的内存管理(GC)。
首先要区分栈(Stack)和堆(Heap)。
下面堆栈的学习参考C# Heap(ing) Vs Stack(ing) in .NET: Part I
栈 — The Stack is more or less responsible for keeping track of what’s executing in our code (or what’s been “called”).
这里栈可以理解为用于记录代码执行顺序。
Note:
栈是LIFO(Last In First Out)原则。
堆 — The Heap is more or less responsible for keeping track of our objects (our data, well… most of it;)
堆是记录那些动态分配内存的(比如reference type)
哪些是分配在栈上,哪些分配在堆上,记住下面两个原则:

  1. A Reference Type always goes on the Heap; easy enough, right? (索引类型都是分配在堆上)
  2. Value Types and Pointers always go where they were declared. This is a little more complex and needs a bit more understanding of how the Stack works to figure out where “things” are declared. (值类型和指针是分配在栈上)
    下面让我们结合实例来看一下是如何分配在栈和堆上的?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class MyInt
    {
    public int MyValue;
    }

    public MyInt AddFive(int pValue)
    {

    MyInt result = new MyInt();
    result.MyValue = pValue + 5;
    return result;
    }

当调用AddFive方法的时候,首先函数参数pValue会入栈
StackAndHeapPart1
然后因为我们创建了索引类型的MyInt实例,这时候MyInt会在堆上分配内存,同时在栈上会生成一个指针指向MyInt在堆上的索引地址
StackAndHeapPart2
当我们给result.MyValue赋值时,我们通过result Pointer记录的地址去访问堆上的MyValue成员并修改值。
最后我们返回result时,栈被清理,只剩下堆上我们分配的数据。
StackAndHeapPart3
而剩下堆上的数据,就是由CLR的GC来管理了。
NOTE :
“The method does not live on the stack and is illustrated just for reference.”
接下来看看CLR的GC是如何工作的。
在了解GC之前,让我们看看C#里在堆上分配内存是如何分配的?
这里就不得不提new这个关键字了。
当我们通过new去创建reference type的时候,会经历下列步骤:

  1. Calculate the number of bytes required for the type’s fields)(计算type所需分配的内存)
  2. Add the bytes required for an object’s overhead(contain a type object pointer and a sync block index)(为type分配object pointer和sync block index所需内存 — 如果是32-bit Application则分配8 bytes,如果是64-bit Application则分配16 bytes)
  3. Zero out the memory start at NextObjPtr(Indicates where the next object is to be allocated within the heap). Return reference. Move on NextObjPtr to next address that is available to be allocated..(根据NextObjPtr指向的可用堆上的起始位置分配内存并清零,然后传递指向type的内存起始位置的NextObjPtr到构造函数去进行初始化,初始化完成后返回type的索引,最后把NextObjPtr指向下一个heap可分配内存的位置。)
    知道了我们在堆上是如何分配内存,让我们看看GC是如何工作来管理所有堆上分配的内存的?

让我们来了解下GC Algorithm:

  1. Reference Counting Algorithm(COM use)
    就是我们平时说的索引计数,通过判断当前所有指针指向特定对象的数量来决定是否要回收该对象内存。
    缺点:
    Circular references会导致内存永远无法回收(e.g. A包含了B的索引,B也包含了A的索引)
  2. Reference Tracking Algorithm(CLR use. Cares only about reference type variables)
    步骤如下:
    1. Marking Phase
      CLR first suspends all threads in the process(prevents threads from accessing objects and changing their state while the CLR examines them)
      Marking All objects to 0(means all objects should be deleted)
      Scan active roots to marking object(not mark the same object again to avoid circular references)
      标记阶段,首先悬挂所有线程防止访问Objects和相关状态。
      然后标记所有在堆上对象的引用为0,然后扫描所有active的roots(即reference type variables — 引用类型的变量),如果有roots指向任何一个堆上的Object,就标记该Object并对该Object内部的roots进行扫描标记。这里最重要的一点就是对标记过的Object不会再扫描内部root(比如有roots指向了A,我们标记了A,然后检查A内部发现B,因为B还没被标记所以标记B并检查B内部,在B内部又发现了A但因为A已经被标记了,所以不会再次标记A,这样一来如果最初指向A的roots不存在了的话,A和B都会因为没有引用不会被标记而清除。这样一来就避免了Circular references)
      Note:
      “Refer to all reference type variables as roots.”
    2. Compacting Phase
      Shifts the memory consumed by the marked objects down in the heap, compacting all the surviving objects together so that they are contiguous in memory.(reduce application’s working set size &access fast in future & no address space fragmentation issues)
      CLR resumes all the application’s threads and they continue to access the objects as if the GC never happened at all
      Note:
      A static field keeps whatever object it refers to forever or until the AppDomain that the types are loaded into is unloaded
      在标记阶段完成后,所有标记为0的堆上对象内存都会被回收。
      压缩阶段主要是为了内存的高效利用(防止内存碎片化)。
      要注意的是静态变量在内存中的位置不会改变。

接下来看看如何提升GC的performance:
CLR’s GC assumptions(提升GC性能的最基本假设):
The newer an object is, the shorter its lifetime will be(越新的对象lifetime越短)
The older an object is, the longer its lifetime will be(越旧的对象lifetime越长)
Collecting a portion of the heap is faster than collecting the whole heap(GC一部分heap比GC所有heap快)
基于上述理论:
Heap被分为了Generation 0,1,2。
GCGenerations
最初创建的对象会存放在generation 0,GC首先检查Generation 0的对象,objects在通过第一次GC后会提升到generation 1,当generation 1对象数量超过generation 0的时候,GC就会检查generation 0和1,同理当 object从generation 1存活下来后会被存放到generation 2。
通过上述方式,我们GC就不必每次都对整个heap的对象进行检查以达到GC优化的目的。
Note:
The Managed heap supports only three generations: generation 0,1,2
The garbage collector fine-tunes itself automatically based on the memory load required by your application.
关于更多GC的学习详情参考《CLR vir C#》 — The Managed Heap and Garbage Collection章节
Note:
Finalize methods are called at the completion of a garbage collection on objects that the GC has determined to be garbage.(Object的Finalize方法是在Object在完成内存被回收之前调用)
Finalize is not equal to destructor in C++(Finalize!=C++的析构函数)

Threading

What is Thread?
“A thread is a Windows concept whose job is to virtualize the CPU. “

Major parts of Thread:

  1. Thread kernel object(“data structes contains a bunch of properties that describe the thread”描述线程信息的数据对象)
  2. Thread environment block(TEB)(“The TEB contains the head of the thread’s exception-handling chain. In addition, the TEB containes the thread’s thread-local storage data and some data structures for use by GDI and OpenGL graphics”)
  3. User-mode stack(“The use-mode stack is used for local variables and arguments passed to methods. It also contains the address indicating what the thread should execute next when the current method returns”)
  4. Kernel-mode stack(“The kernel-mode stack is also used when application code passes arguments to a krenel-mode function in the operating system”)
  5. DLL thread-attach and thread-detach notifications

Why do we need Thread?
Benfits:

  1. Responsiveness(同一时间只能运行一个程序,单核CPU一个程序卡死就会导致整个电脑卡死)
  2. Data safe(程序卡死后重启会导致数据丢失)
  3. Performance(多核CPU可以同时执行多个任务,使得处理任务更高效)

Shorcomings:

  1. Threads consume a lot of memory and require time to create, destroy…..(创建和销毁费时,并且消耗大量内存)
  2. Context switches(Change to other thread) takes much time(Thread切换费时)

How to use Thread correctly?
“Have the number of thread that is no more than the number of CPUs on that machine.”

Note:
“A CLR thread is identical to a Windows thread”

Thread Scheduling and Priorities

待续……

C# In Depth(third edition)

C#1

Non Generic Collections

1
ArrayList list = new ArrayList();

Sorting an ArrayList using IComparer


C#2

Strongly Typed Collections

1
List<T> list = new List<T>();

Sorting an List using IComparer or Comparision

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ProductNameComparer : IComparer<Product>
{
public int Compare(Product p1, Product p2)
{
return p1.Name.CompareTo(p2.Name);
}
}
// IComparer<T>
List<Product> products = new List<Product>();
products.Sort(new ProductNameComparer());
// Comparison<T>
products.Sort(delegate(Product x, Product y)
{
return x.Name.CompareTo(y.Name);
});

Nullable Value Type

1
decimal? price = null;

C#3

Properties

Automatically Implementaed Properties

1
2
3
4
class ClassName
{
public type PropertyName{get;set;}
}

Sorting using Comparision from a lambda expression

1
2
List<Product> products = new List<Product>();
products.Sort((x, y) => x.Name.CompareTo(y.Name));

Extension Method

1
2
3
4
5
public static class StringExtension
public static int getLength(this string s)
{

return s.Length;
}

LINQ(Language-Integrated Query)

“LINQ is at the heart of the changes in C# 3. The aim is to make it easy to write queries against multiple data sources with consistent syntax and features, in a readable and composable fashion.”

1
2
3
4
List<Product> products = new List<Product>();
var filtered = from Product p in products
where p.Price > 10
select p;

C#4

Named Arguments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Product
{
public string Name
{
get { return name; }
}
readonly string name;

public Product(string name)
{

this.name = name;
}
}

Product product = new Product( name : "TonyTang"),

Optional Parameters

1
2
3
4
5
public int Sum(int a,int b = 0)
{

return a + b;
}
var sum = Sum(1);

DLR(Dynamic Language Runtime)

CSharp Evolution

CSharpEvolution1
CSharpEvolution2
CSharpEvolution3

参考书籍下载:
《C#入门经典第五版》
《CLR Via C# Fourth Edition》 - Jeffrey Richter
《C# in Depth 3rd Edition》 - Jon Skeet

文章目錄
  1. 1. .Net Framework
    1. 1.1. Introduction
    2. 1.2. Content
    3. 1.3. Using .NET Framework
      1. 1.3.1. Tools
      2. 1.3.2. 相关概念
        1. 1.3.2.1. FCL(Framework Class Library)
        2. 1.3.2.2. Metadata
        3. 1.3.2.3. IL(Intermediate Language)
        4. 1.3.2.4. CLI(Common Language Infrastructure)
        5. 1.3.2.5. CLR(Common Language Runtime)
        6. 1.3.2.6. CTS(Common Type System)
        7. 1.3.2.7. CLS(Common Language Specification)
      3. 1.3.3. Compile process
        1. 1.3.3.1. Managed Module
      4. 1.3.4. Executing Assembly Code
      5. 1.3.5. 程序集
      6. 1.3.6. 托管代码
      7. 1.3.7. 垃圾回收
      8. 1.3.8. 链接
  2. 2. CSharp(CLR Via C#)
    1. 2.1. Introduction
    2. 2.2. Features
    3. 2.3. Development
    4. 2.4. Language Study
      1. 2.4.1. delegate
      2. 2.4.2. Class & interface
      3. 2.4.3. Not supported multiple inherit
      4. 2.4.4. Class member
      5. 2.4.5. Struct & Class
      6. 2.4.6. Collection class (System.Collection)
      7. 2.4.7. Method
      8. 2.4.8. Comparasion
      9. 2.4.9. Conversion
      10. 2.4.10. Generics
      11. 2.4.11. Hosting, AppDomain, Assembly, Reflection
        1. 2.4.11.1. Hosting
        2. 2.4.11.2. AppDomain
        3. 2.4.11.3. Assembly Loading
        4. 2.4.11.4. Reflection
      12. 2.4.12. Runtime Serialization
      13. 2.4.13. Platform Invoke
      14. 2.4.14. Event
      15. 2.4.15. Chars, Strings, and Working with Text
      16. 2.4.16. Enumerated and Bit Flags
      17. 2.4.17. Custom Attributes
      18. 2.4.18. Exceptions and State Management
      19. 2.4.19. The Managed Heap and Garbage Collection
      20. 2.4.20. Threading
        1. 2.4.20.1. Thread Scheduling and Priorities
  3. 3. C# In Depth(third edition)
    1. 3.1. C#1
      1. 3.1.1. Non Generic Collections
      2. 3.1.2. Sorting an ArrayList using IComparer
    2. 3.2. C#2
      1. 3.2.1. Sorting an List using IComparer or Comparision
      2. 3.2.2. Nullable Value Type
    3. 3.3. C#3
      1. 3.3.1. Properties
      2. 3.3.2. Sorting using Comparision from a lambda expression
      3. 3.3.3. Extension Method
      4. 3.3.4. LINQ(Language-Integrated Query)
    4. 3.4. C#4
      1. 3.4.1. Named Arguments
      2. 3.4.2. Optional Parameters
      3. 3.4.3. DLR(Dynamic Language Runtime)
    5. 3.5. CSharp Evolution