在CLR环境中运行程序,需要提前进行编译,编译分为两个阶段:
1. 将源代码编译为中间语言(MSIL, Microsoft Intermediate Language).
2. 将MSIL编译为平台专用的代码.
\^1\]: Common Language Infrastructure.通用语言基础结构,CLI是CLR的一部分。 在CLR结构图中,CLI位于下半部分,主要包含类加载器(Class Loader)、实时编译器(IL To Native Compilers)和一个运行时环境的垃圾收集器(Garbage Collector)。CLI是.Net和CLR的灵魂,CLI为IL代码提供运行的环境,你可以将使用任何语言编写的代码通过特定的编译器转换为MSIL代码后运行其上,甚至还可以自己编写MSIL代码在CLI上面运行。 \[\^2\]: Common Language Runtime.通用语言运行库,是.Net Framework核心的执行环境。通常将在CLR控制下运行的代码称为托管代码。 ## 中间语言IL(Intermediate Language) 1. 值类型与引用类型 中间语言提供了许多预定义的基本数据类型,它的一个特性是值类型与引用类型之间有明显的区别。 引用类型:变量仅存储地址(4或8字节),真正的实例存储在名为"托管堆"的内存区域中。 值类型:变量直接存储其数据,通常直接存放在栈上。(如果作为引用类型的成员,则作为内联也存放在托管堆上)。 2. 通用类型系统 中间语言要在本层提供语言之间的互操作性,必须有一套通用的数据类型系统(CTS)。CTS定义了可以在中间语言中使用的预定义数据类型,所有面向.Net Framework的语言都可以生成最终基于这些类型的编译代码。CTS指定了基本数据(int,float,double...)的值类型,提供了两个引用类型:object 根类型和string字符串类型,此外还约定了一个内容丰富的类型层次结构,允许不同的语言中自定义数据类型。 3. 垃圾回收 在托管堆上的创建的数据对象,无需手动释放。CLR会不时的调用垃圾回收期进行托管堆的维护,这个过程是不确定的,不能保证什么引用计数为0时,对象资源就会被回收。 ## C#面向对象基础 1. 构造函数。如果设计了一个类,没有提供任何形式的构造函数,则编译器会在编译时生成一个无参数的基本构造函数,将所有的成员字段初始化为标准的默认值:引用类型字段指向null空引用,值类型字段设为全0。如果设计类时,提供了带参数的构造函数,编译器将不会生成其他构造函数,此时就要求类的设计者自行对各个成员字段完成初始化。在客户端使用这个类时,也无法用无参的构造函数实例化该类。 2. 析构函数。由于C#托管机制的存在,上层用户不必考虑对象析构的问题,因此虽然C#允许为类定义析构函数(语法与C++一样,波浪号+类名,无参),但一般很少在C#代码里看到析构函数,如果有析构函数,则在编译阶段将被自动转换为Finalize重载。 3. 如果在定义类时没有指定基类,编译器将假定该类直接继承Object类(结构体默认继承ValueType类,而ValueType继承Object类,但是结构体之间不能存在继承关系,结构体定义的无参构造函数由编译器生成,不允许覆盖重写)。Object基类的方法: - ToString方法:获取对象的字符串表示,返回字符串的引用。如果子类没有重写该方法,则返回类的名称。 - GetHashCode()方法:如果希望把类用作字典的键,则需要重写该方法。 - Equals()方法:判断两个对象值是否相等 - Finalize()方法:引用类型对象在被GC垃圾回收时,由回收器发起调用,可以看作是C#类的通用析构函数,用于执行一些非托管资源的清理工作。Object基类的该方法内部为空。因为垃圾回收器不能处理非托管资源(譬如打开的文件句柄, Windows内核对象 ,事件,未断开的网络连接等),所以存在非托管资源的类,可以重载Finalize()方法,在里面执行非托管资源释放。然而,又由于垃圾回收是一个低优先级任务,Finalize的调用可能不那么及时,如果短时间内出现大量非托管资源申请(比如网络套接字申请),则会导致资源耗尽。所以拥有非托管资源的类,需要自行定义资源释放方法,客户端创建的实例在使用结束后,需要显示调用该方法,完成资源释放。为了规范化这个过程,C#提供了IDisposable接口类,声明了Dispose()方法。拥有非托管资源的类,可以继承该接口,按规范实现Dispose。 - GetType()方法:该方法返回一个System.Type派生类的实例,该实例包含了对象各个成员的详细信息。C#中的反射机制依赖该接口实现。 - MemberwiseClone()方法:复制对象生成副本,并返回副本的一个引用。需要注意的是,副本是一个浅复制,所有的值类型成员拷贝了一份,剩余的引用类型成员,则只是复制了引用,而不是复制引用的对象。该方法不是虚方法,所以不能重写它的实现。 3. 多重继承 C#不支持多重继承,除非多出来的是接口类。结构体不允许继承,但是,允许有多个接口基类。 4. 存在继承关系时,对象的构造顺序是先基类,再派生类。派生类的构造函数后面,通过初始化列表的形式,发起基类构造函数的调用,与C++不同的,C#通过关键字base进行调用而不是基类名。如果基类只有默认的无参构造函数,则可以省略base()的调用,编译器在编译时会自动添加。 \`\`\`c# public class SubClassA:A { public SubClassA():base(xxx){} //For c++, there will be "SubClassA():A(xxx){}" } \`\`\` 5. 接口类中只允许存在方法,属性(注意是属性,不是字段)的声明,而不允许提供实现,且不能声明为静态、私有和虚方法,也不能有构造函数。继承接口的类,需要定义接口中声明的方法和属性,如果没全部定义,它就是抽象类,不允许构造实例。 \`\`\`c# //1.Microsoft 预定义的一个接口System.IDisposable public interface IDisposable { void Dispose(); }; //2.自定义接口 public interface ISomeInterface { void Func1(int i); bool Func2(decimal amount); string Name{ get; set;}//属性声明 }; \`\`\` 6. 属性和字段 属性是对字段的封装,用于提供对字段的安全读写保护。当只定义了属性,没有显式规定该属性所保护的字段时,编译器将创建一个匿名字段。 class Person { public string Name;//不受保护的字段 private string m_age;//受保护的字段 public string Age { get { return m_age; } private set { m_age = value; } }//属性定义 //public string Age=\>m_age;//等效写法 }
7.结构体为值类型,一般存在于栈上。但是如果作为类的成员(包括静态成员),或在封箱操作时,会自动变成引用类型,变成托管变量(结构体如果继承了接口类,则它可以封箱成接口类,也可以从接口类拆箱成相应的值类型,否则他只能和Object类型进行拆封箱操作)。普通结构体类型在作为函数形参时,默认是值传递,如果加了ref /in/out,则会成为引用,注意这种引用方式不是通过封箱来实现的。如果结构体定义时添加了ref,则该结构体不能用作类的字段,不允许它有封箱操作,不能继承接口类,只能作为值类型使用,在函数传参时无论加不加ref都以引用方式传递。(封箱指的是一种值类型转引用类型的操作,封箱会导致在托管堆上多出一个副本,得到的引用是引用托管堆上这个副本,因此基于引用变量的更新操作,不会影响原对象)。
```c#
public ref struct MyRefStruct
{
public int MyIntValue1;
public int MyIntValue2;
}
```
- C#的事件和委托
1>. C#的委托是一种引用类型,包含了一个或多个函数地址以及这些函数的细节信息,可以看作是C++的函数指针类型的扩展。
2>. 事件是对委托的封装,委托作为事件的私有成员,由事件对外提供受约束的访问。委托可以独立存在,而事件脱离了委托就没有意义。
以下代码片段等价(编译器在底层将第一种写法自动转换为第二种写法):
'''
using System;
namespace CSharpConsoleApplication2
{
public class EventTest
{
public delegate void StateChangeHandler();//声明委托类型(类似声明函数指针)
public event StateChangeHandler StatechangeEvent;//定义基于该类型委托的事件
public void OnStateChange()//触发
{
StatechangeEvent?.Invoke();//StatechangeEvent的invoke方法为私有,外部无法直接调用
}
}
public class Program
{
public static void Method()
{
Console.WriteLine("Event Triggered.");
}
public static void Main(string[] args)
{
EventTest eventTest = new EventTest();
eventTest.StatechangeEvent += Method;//注册回调
eventTest.OnStateChange();//触发事件
Console.WriteLine("End.");
}
}
}
'''
等价于:
'''
using System;
namespace CSharpConsoleApplication2
{
public class EventTest
{
public delegate void StateChangeHandler();
private StateChangeHandler _stateChangeHandler;
public event StateChangeHandler StatechangeEvent
{
add { _stateChangeHandler += value; }
remove { _stateChangeHandler -= value; }
}
public void OnStateChange()
{
_stateChangeHandler?.Invoke();
}
}
public class Program
{
public static void Method()
{
Console.WriteLine("Event Triggered.");
}
public static void Main(string[] args)
{
EventTest eventTest = new EventTest();
eventTest.StatechangeEvent += Method;
eventTest.OnStateChange();
Console.WriteLine("End.");
}
}
}
'''
- Task
C#的Task类实现了awaitable pattern 等待模式,能够配合await/sync实现异步编程。下面仿照实现一个awaitable的Task类,帮助理解异步编程原理
cs
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncAwaitDemo
{
class Program
{
static async System.Threading.Tasks.Task Test()
{
var t = new MyCpuTask();
await t;
//遇到await关键字,c#关键字会为其编译成以下等效代码(伪代码)
/*var awaiter = t.GetAwaiter();
if(!awaiter.IsCompleted)
{
awaiter.OnCompleted(stateMachine.Resume);//stateMachine是编译器创建的状态机,保存了当前调用上下文,此处不阻塞
return;
}
else
return;
*/
// 如果在await的对象是带返回值的类型,编译器将生成更为复杂的结构,用于存放返回值
}
static async System.Threading.Tasks.Task<string> TestRtn()//带返回值的Task
{
Func<string> func = () =>
{
Console.WriteLine("Do task Begin (string)...");
Thread.Sleep(5000);
Console.WriteLine("Do task End (string)...");
return "Hello Awaitable";
};
var t = MyCpuTaskRtn<string>.Run(func);
var res = await t;//编译器将在此生成一系列代码
return res;
}
static void Main(string[] args)
{
var res1 = Test();
var waiter1 = res1.GetAwaiter();
waiter1.GetResult();
var res2 = TestRtn();
var waiter2 = res2.GetAwaiter();
var str = waiter2.GetResult();
Console.WriteLine("Res : {0}, Any Key to Exit...", str);
Console.ReadKey();
}
}
public class MyCpuTask
{
public delegate void TaskHandlerDelegate();
private TaskHandlerDelegate m_TaskHandler;
public class MyAwaiter : INotifyCompletion
{
private Exception m_error;
private Action m_continuation;
private TaskHandlerDelegate m_task;
private volatile bool m_completed;
public bool IsCompleted{get{return m_completed;} private set{m_completed = value;}}
public MyAwaiter(TaskHandlerDelegate t)
{
if (t == null) throw new ArgumentNullException(nameof(t));
m_completed = false;
m_continuation = null;
m_error = null;
m_task = t;
ThreadPool.QueueUserWorkItem(Callback);// Register the CPU task. you can also register the IO Event or timer Event.
}
public void GetResult()
{
if (!m_completed)
throw new InvalidOperationException("Async Operation has not completed yet.");
if (m_error != null)
{
Console.WriteLine("Async Operation Failed.");
ExceptionDispatchInfo.Capture(m_error).Throw();
}
Console.WriteLine("Async Operation Finished, Fetching Result Now...");
}
public void OnCompleted(Action continuation)
{
if (continuation == null)
throw new ArgumentNullException(nameof(continuation));
if (Interlocked.CompareExchange(ref m_continuation, continuation, null) != null)
throw new InvalidOperationException("This awaiter does not support multiple awaits.");
if (m_completed)//If completed, trigger the subsequent actions directly.
{
var cont = Interlocked.Exchange(ref m_continuation, null);
if (cont != null)
ThreadPool.QueueUserWorkItem(_=> { cont.Invoke(); });
}
}
private void Callback(object state)
{
try
{
if (m_task != null)
m_task.Invoke();//Do task;
m_completed = true;
var cont = Interlocked.Exchange(ref m_continuation, null);
try {
if (cont != null)
{
cont();// Reume the subsequent actions.
//if(SynchronizationContext.Current != null)//在UI 应用中, Current不为空, 可以选择将continuation放到UI线程执行.
//SynchronizationContext.Current.Post(_=>cont(), state);
}
}
catch (Exception ex)//Reume Failed.
{
m_error = ex;
}
}
catch (Exception ex)//Task Failed.
{
m_error = ex;
}
finally
{
m_completed = true;
}
}
}
public MyCpuTask()
{
m_TaskHandler = null;
}
public MyAwaiter GetAwaiter()
{
m_TaskHandler = () => { Console.WriteLine("Do task Begin..."); Thread.Sleep(2000); Console.WriteLine("Do task End..."); };
return new MyAwaiter(m_TaskHandler);
}
}
public class MyCpuTaskRtn<TResult>
{
public delegate TResult TaskHandlerDelegate();
private readonly TaskHandlerDelegate m_TaskHandler;
public class MyAwaiter : INotifyCompletion
{
private Exception m_error;
private Action m_continuation;
private readonly TaskHandlerDelegate m_task;
private volatile bool m_completed;
private TResult m_result;
public bool IsCompleted => m_completed;
public MyAwaiter(TaskHandlerDelegate t)
{
if (t == null) throw new ArgumentNullException(nameof(t));
m_task = t;
m_completed = false;
m_continuation = null;
m_error = null;
ThreadPool.QueueUserWorkItem(Callback);
}
public TResult GetResult()
{
if (!m_completed)
throw new InvalidOperationException("Async Operation has not completed yet.");
if (m_error != null)
{
Console.WriteLine("Async Operation Failed.");
ExceptionDispatchInfo.Capture(m_error).Throw();
}
Console.WriteLine("Async Operation Finished, Fetching Result Now...");
return m_result;
}
public void OnCompleted(Action continuation)
{
if (continuation == null)
throw new ArgumentNullException(nameof(continuation));
if (Interlocked.CompareExchange(ref m_continuation, continuation, null) != null)
throw new InvalidOperationException("This awaiter does not support multiple awaits.");
if (m_completed)
{
var cont = Interlocked.Exchange(ref m_continuation, null);
if (cont != null)
{
ThreadPool.QueueUserWorkItem(_ => cont());
}
}
}
private void Callback(object _)
{
try
{
m_result = m_task.Invoke();//Do Task
}
catch (Exception ex)
{
m_error = ex;//Do Task Failed
}
finally
{
m_completed = true;
var cont = Interlocked.Exchange(ref m_continuation, null);
if (cont != null)
{
try
{
cont();
}
catch (Exception ex)//Resume Failed.
{
if (m_error == null)
m_error = ex;
}
}
}
}
}
public MyCpuTaskRtn(TaskHandlerDelegate handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
m_TaskHandler = handler;
}
public MyAwaiter GetAwaiter()
{
return new MyAwaiter(m_TaskHandler);
}
public static MyCpuTaskRtn<TResult> Run(Func<TResult> func)
{
if (func == null)
throw new ArgumentNullException(nameof(func));
return new MyCpuTaskRtn<TResult>(() => func());
}
}
}
- Task与async/await机制的关系
.NET 4.0就出现Task类(2010.04),将耗时操作加入线程池来进行异步化,用成员函数Wait来阻塞等待结果,如果想要在任务结束后执行其他操作(所谓链式操作),需要用成员函数ContinueWith注册一个回调来进行,如果回调内部继续用Task和ContinueWith,这样很容易在多次嵌套后,陷入回调地狱,代码难以调试。.NET 5.0 (2012)中提出了async/wait机制,并为Task扩展了上述awaitable模型,让编译器自动生成维护异步模型的代码,简化了C#编写异步程序的过程的同时,也提高了稳定性。
深入探讨 C# 和 .NET 中 async/await 的历史、背后的设计决策和实现细节 - wxlevel - 博客园