<?xml version="1.0" encoding="utf-8" ?><rss version="2.0"><channel><title>DongPad</title><link>http://www.dongpad.com</link> <description>Every day is a new beginning!</description><copyright>2.0 beta 03</copyright> <language>zh-cn</language><item><title>C#和CLR的15个细节</title><description><![CDATA[<p>这两周一直在training，一共听了4个课程，相当的累啊。</p>  <p>今天先把Jeffrey Richter的培训课：C# and CLR中讲到的一些东西整理一下：</p>  <ol>   <li><strong>foreach的性能问题</strong>      <p>foreach(string s in rows) { foo(s); }的实现是：</p>   </li>    <li>     <pre>IEnumerator e = rows.GetEnumerator();
try {
  string s;
  while (e.MoveNext()) {
    s = (String) e.Current;
    foo(s);
  }
}
finally {
  IDisposable d = e as IDisposable;
  if (d != null) d.Dispose();
}</pre>

    <p>每一步都调用了e.MoveNext()和e.Current<strong>两个</strong>方法；而大多数时候，完全有可能优化为一次调用。显然这对性能是有影响的。虽然foreach对于数组作了单独的优化（编译成for循环），但这还是值得注意的。</p>

    <p>那么，怎么做比较快？</p>

    <p>对于List等Collection，可以用ForEach(Action<T> action)，FindAll(Predicate<T> match)，ConvertAll<U>(Converter<T, U> converter)等方法。它们比较快，但不是所有实现IEnumerable的类都提供。</p>

    <p>LINQ追求compatiblity，而不是performance。因此LINQ的实现完全采用了foreach。值得注意。</p>
  </li>
</ol>

<ol>
  <li><strong>yield的实现原理</strong>

    <p>实现一个支持IEnumerable的对象时，一般会用到yield关键字，这样foreach遍历这个对象时，可以做到lazy evaluation。例如：</p>

    <pre>class MyCollection: IEnumerable<char> {
  private string s; ...
  public IEnumerable<char> GetEnumerator() {
    for (int i=0; i<s.Length; i++) {
      yield return s[i];
    }
  }
}</pre>

    <p>执行到yield时函数返回，下次调用时，接着上次运行的位置继续运行。这个continuation的效果是怎么做的呢？</p>

    <p>包含yield的函数都会被编译器做成一个<strong>状态机</strong>。每调一次，就接着上次的状态继续运行。简单有效啊。我一直以为要有什么特殊的办法呢。</p>
  </li>

  <li><strong>exception handling的实现决定了throw的performance较差。</strong>

    <p>可以用Int32.TryParse代替try{Int32.Parse…}catch{…}，稍快一点。类似地建议使用Dictionary.TryGetValue。</p>
  </li>

  <li><strong>.Net CLR执行引擎对应于MSCorWks.dll和MSCorEE.dll这两个文件。
      <br /></strong></li>

  <li><strong>.Net 3.0, 3.5没有对CLR作任何修改。</strong>

    <p>所有增加的东西（比如LINQ）都是syntactic sugar，只改了C#编译器而已。</p>
  </li>

  <li><strong>AppDomain
      <br /></strong>

    <p>如果把.Net虚拟机看成一个虚拟操作系统，AppDomain的概念则类似于操作系统中的进程。</p>

    <p>可以用代码创建一个AppDomain，然后动态加载/卸载assembly，还可以设置权限，相当于提供了一个沙箱。</p>

    <p>跨AppDomain的调用类似于RPC。</p>

    <p>调用某个AppDomain内部的obj.foo(x)时，.net会自动帮你做出一个proxy object，你所调用的obj其实是一个proxy object。传给foo的参数x会先被被marshal，以保证AppDomain被安全隔离。</p>

    <p>谁用AppDomain？SQL Server用这个技术实现managed存储过程。IIS会把不同的Web Application放在不同的AppDomain里，以实现动态装卸。</p>
  </li>

  <li><strong>动态载入Assembly的陷阱</strong>

    <p>Sytem.Reflection.Assembly.LoadFrom(pathName)并不会载入pathName所指定的dll，而是看看pathName那个dll的名字、版本，然后到系统默认位置去找。（陷阱啊）</p>
  </li>

  <li><strong>C#里用reflection创建一个新对象</strong>

    <p>用Activator.CreateInstance。（奇怪的名字啊。）</p>
  </li>

  <li><strong>C#泛型之“where”</strong>

    <p>可以用“where”来限定T的接口。例如</p>

    <p>static T min<T>(T arg1, T arg2) where T: IComparable<T> {…}</p>

    <p>不写where的话，就不能调arg1.CompareTo(arg2)。</p>

    <p>为啥不把T换成IComparable？一是为保证arg1, arg2一定是同一个类型，二是泛型的效率更高。（JIT会为不同类型的T各生成一份native code，从而避免了boxing）</p>

    <p>更多where的细节：</p>

    <p>* 要想调T t1 = new T()，必须声明where T: new()或者where T: struct</p>

    <p>* 要写T t2 = null，必须声明where T: class</p>

    <p>* T z = default(T)是一个特殊的用法，会把T的每个bit都置为0。</p>

    <p>* 假设定义了Foo<T>(T x, T y)，则if (x==null) … 是可以通过的，虽然C#中value type的值不允许为null（例如int a=null是错的）。这是因为，此时的语义是一致的，反正if里面的操作不被执行就是了，所以编译器对这种特殊情况网开一面。</p>

    <p>* if (x==y)不行，除非写了where T: baseclass。（这里我也没理解为啥。。。>_<好像说是不知道应该用reference比较还是value比较？）</p>
  </li>

  <li><strong>匿名函数的背后。。。</strong>

    <p>在C# 2.0以后可以用匿名的delegate，如ThreadPool.QueueWorkItem(delegate (Object obj) { Console.WriteLine(obj); })</p>

    <p>但编译器的实现会带来一点点overhead，会生成一个小小的静态WaitCallback对象，可以用Reflector看生成的代码。（不要打开Reflector的optimization，否则就看不到了）</p>

    <p>如果是自己写的话，可以选择每次动态建立一个WaitCallback对象然后销毁。当然这样做性能可能差一些，但这里的idea是：编译器会自动做一些事，但不一定是你所希望的。在使用这些高级feature前，最好先搞清楚背后发生了什么。</p>

    <p>另一个细节：如果匿名函数中使用了外层函数的局部变量（即所谓的function closure），会导致创建额外的shared-state object，把用到的局部变量做成一个新对象传给匿名函数。</p>

    <p>上述描述同样适用于lambda函数。因为C#的lambda函数就是匿名函数，改了改语法而已。</p>
  </li>

  <li><strong>Nullable type
      <br /></strong>

    <p>虽然C#要求value type的值不能是null，但写数据库程序时经常遇到某个值是null的情况。为此，C#2.0引入了Nullable type。例如，int? x = null。</p>

    <p>int? x其实就是一个缩写，等价于Nullable<int> x。Nullable是预定义的一个类，简单地对x作了封装。（因为增加了一个类，显然对性能稍微有点影响）</p>

    <p>这个小改动的实现其实很麻烦，需要修改CLR。为什么？因为原先的x是一个value type，现在则变成了一个object，看这个：</p>

    <pre>void M(Object o) {
  if (o=null) {Bar();}
}
void F() {
  int? x = null;
  M(x);
}</pre>

    <p>如果CLR不专门做修正的话，上面的Bar()不会被执行。（思考题：想一想为什么~）</p>

    <p>另外，C#还引入了一个默认值运算符“??”，称为null-coalescing operator。</p>

    <p>一句话，x ?? value是 (x==null) ? value: x的简写。</p>
  </li>

  <li><strong>属性(property)的简单声明</strong>

    <p>public int x {get; private set;}是个很好用的句式。</p>

    <p>注意，public int x {get;}是错误的，不能通过编译。</p>
  </li>

  <li><strong>Extension method
      <br /></strong>

    <pre>static class MyExtMethods {
  static public GetFirstLetter(this string s) {return s[0];}
}</pre>

    <p>然后就可以用string s = “hello”; char ch = s.GetFirstLetter()了。</p>

    <p>原理很简单，编译器把上面那句话翻译成MyExtMethods.GetFirstLetter(s)。LINQ就用到了这个技术。</p>
  </li>

  <li><strong>匿名类型的背后。。。</strong>

    <p>var o = new {name = “Xiangpeng”, id = 123 };</p>

    <p>在这背后是编译器生成的一个匿名类，包含了两个<strong>只读</strong>属性，形如public int id { get {return _id;} }为什么不做成可读写的呢？</p>

    <p>很微妙。匿名类自动生成了GetHashCode()，返回的是对所有属性的hash code做XOR的结果。如果允许修改属性值，那么Hash code的值就会变化；而这个可能会出问题~保险起见，只读吧。</p>
  </li>

  <li><strong>每个thread占1M物理内存
      <br /></strong>

    <p>在Win32编程中thread的1M stack空间是Reserve的，直到真正用时才占用物理内存；而在.net中，这1M空间直接被commit。</p>

    <p>还好，可以在新建thread时指定stack size。不过这也比较危险，设小了怕不够。实际上，最好尽量避免创建thread——太多的thread要么导致CPU竞争和context switch，要么都block着浪费内存。建议是：能用ThreadPool就用ThreadPool。</p>
  </li>
</ol>

<p>累死了，写了这么多……Jeffrey还有一个更牛的课：.net threading。里面的东西更多……要不要再总结一下呢。。。<img alt="Thinking" src="http://messenger.msn.com/MMM2006-04-19_17.00/Resource/emoticons/72_72.gif" /></p>

<p>感谢作者的分享,原文参见:<a href="http://www.zhaoxiangpeng.com/articles/c和clr的15个细节.html" target="_blank">c#和clr的15个细节</a>!</p>]]></description><author>Jack</author><link>http://www.dongpad.com/CSharp-20090628-193.html</link><pubdate>2009-6-28 14:27:05</pubdate></item></channel></rss>
