﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>博客园-MVP播客</title><link>http://www.cnblogs.com/team/netcast/</link><description /><language>zh-cn</language><lastBuildDate>Sat, 28 Jan 2012 18:04:49 GMT</lastBuildDate><pubDate>Sat, 28 Jan 2012 18:04:49 GMT</pubDate><ttl>60</ttl><item><title>数组排序方法的性能比较（3）：LINQ排序实现分析</title><link>http://www.cnblogs.com/JeffreyZhao/archive/2010/01/27/1657030.html</link><dc:creator>Jeffrey Zhao</dc:creator><author>Jeffrey Zhao</author><pubDate>Tue, 26 Jan 2010 16:02:00 GMT</pubDate><guid>http://www.cnblogs.com/JeffreyZhao/archive/2010/01/27/1657030.html</guid><description><![CDATA[<p><p>上次我们<a href="http://www.cnblogs.com/JeffreyZhao/archive/2010/01/22/sort-array-linq-2-array-sort.html">分析了Array.Sort&lt;T&gt;方法的实现方式</a>，并了解到类库会为一些特例而使用高性能的排序方式——int数组便是这样一例，因此<a href="http://www.cnblogs.com/JeffreyZhao/archive/2010/01/21/sort-array-linq-1-notes-and-benchmark.html">从测试结果上来看</a>其性能特别高。不过从数据上看，即便是在普通的情况下，Array.Sort&lt;T&gt;的性能也比LINQ排序要高。不过也有朋友从测试中得出的结论正好相反，这又是为什么呢？那么现在，我们再来分析一下LINQ排序的实现方式吧，希望这样可以了解到两者性能差别的秘密。</p>  <p>只可惜，LINQ排序的代码在System.Core.dll程序集中，微软没有发布这部分源代码，我们只得使用.NET Reflector来一探究竟了。</p>  <h1>LINQ排序接口的定义、使用及扩展</h1>  <p>所谓LINQ排序，便是使用定义在System.Linq.Enumerable类中的几个扩展方法，它们是：</p>  <pre class="code"><span style="color: blue">public static </span><span style="color: #2b91af">IOrderedEnumerable</span>&lt;TSource&gt; OrderBy&lt;TSource, TKey&gt;(
    <span style="color: blue">this </span><span style="color: #2b91af">IEnumerable</span>&lt;TSource&gt; source, <span style="color: #2b91af">Func</span>&lt;TSource, TKey&gt; keySelector);

<span style="color: blue">public static </span><span style="color: #2b91af">IOrderedEnumerable</span>&lt;TSource&gt; OrderBy&lt;TSource, TKey&gt;(
    <span style="color: blue">this </span><span style="color: #2b91af">IEnumerable</span>&lt;TSource&gt; source, <span style="color: #2b91af">Func</span>&lt;TSource, TKey&gt; keySelector, <span style="color: #2b91af">IComparer</span>&lt;TKey&gt; comparer);

<span style="color: blue">public static </span><span style="color: #2b91af">IOrderedEnumerable</span>&lt;TSource&gt; OrderByDescending&lt;TSource, TKey&gt;(
    <span style="color: blue">this </span><span style="color: #2b91af">IEnumerable</span>&lt;TSource&gt; source, <span style="color: #2b91af">Func</span>&lt;TSource, TKey&gt; keySelector);

<span style="color: blue">public static </span><span style="color: #2b91af">IOrderedEnumerable</span>&lt;TSource&gt; OrderByDescending&lt;TSource, TKey&gt;(
    <span style="color: blue">this </span><span style="color: #2b91af">IEnumerable</span>&lt;TSource&gt; source, <span style="color: #2b91af">Func</span>&lt;TSource, TKey&gt; keySelector, <span style="color: #2b91af">IComparer</span>&lt;TKey&gt; comparer);</pre>

<p>为了使用时的方便，我往往会补充一些额外的接口，例如：</p>

<pre class="code"><span style="color: blue">public static </span><span style="color: #2b91af">IOrderedEnumerable</span>&lt;TSource&gt; OrderBy&lt;TSource, TKey&gt;(
    <span style="color: blue">this </span><span style="color: #2b91af">IEnumerable</span>&lt;TSource&gt; source, <span style="color: #2b91af">Func</span>&lt;TSource, TKey&gt; keySelector, <span style="color: blue">bool </span>decending)
{
    <span style="color: blue">return </span>decending ?
        source.OrderByDescending(keySelector) :
        source.OrderBy(keySelector);
}</pre>

<p>这样在使用时，便可以使用一个布尔值来表示排序的方向（升序或是降序）而不需要从两个方法之间“手动”选择一个。此外，构造一个IComparer&lt;TKey&gt;类型也实在有些麻烦，于是我按照Array.Sort&lt;T&gt;的做法重新继续扩展了一个使用委托对象作为“比较器”的接口：</p>

<pre class="code"><span style="color: blue">public static </span><span style="color: #2b91af">IOrderedEnumerable</span>&lt;TSource&gt; OrderBy&lt;TSource, TKey&gt;(
    <span style="color: blue">this </span><span style="color: #2b91af">IEnumerable</span>&lt;TSource&gt; source, <span style="color: #2b91af">Func</span>&lt;TSource, TKey&gt; keySelector, <span style="color: #2b91af">Comparison</span>&lt;TKey&gt; compare, <span style="color: blue">bool </span>decending)
{
    <span style="color: blue">return </span>decending ?
        source.OrderByDescending(keySelector, <span style="color: blue">new </span><span style="color: #2b91af">FunctorComparer</span>&lt;TKey&gt;(compare)) :
        source.OrderBy(keySelector, <span style="color: blue">new </span><span style="color: #2b91af">FunctorComparer</span>&lt;TKey&gt;(compare));
}</pre>

<p>至于FunctorComparer类的实现，由于过于简单就省略了吧，贴出来也只是占用地方而已。有了这个接口，在排序的时候我们就可以这样使用了：</p>

<pre class="code">employee.OrderBy(p =&gt; p.Manager, (m1, m2) =&gt; ... <span style="color: green">/* 比较逻辑 */</span>, <span style="color: blue">false</span>);</pre>

<p>不过，无论是哪个接口、重载还是扩展，它的（除this外）的第一个参数便是keySelector，它的含义便是选择（select）出排序的“依据”。这个参数不可省略（除非您提供扩展），因此即便是int数组这样的类型，需要排序时也必须指定“自己”为排序依据：</p>

<pre class="code">intArray.OrderBy(i =&gt; i);</pre>

<p>这也是LINQ排序和Array.Sort&lt;T&gt;的本质区别之一。</p>

<h1>OrderedEnumerable的实现</h1>

<p>无论是哪个接口，最终创建的都是OrderedEnumerable&lt;TElement, TKey&gt;类型，例如：</p>

<pre class="code"><span style="color: blue">public static </span><span style="color: #2b91af">IOrderedEnumerable</span>&lt;TSource&gt; OrderBy&lt;TSource, TKey&gt;(
    <span style="color: blue">this </span><span style="color: #2b91af">IEnumerable</span>&lt;TSource&gt; source, <span style="color: #2b91af">Func</span>&lt;TSource, TKey&gt; keySelector)
{
    <span style="color: blue">return new </span><span style="color: #2b91af">OrderedEnumerable</span>&lt;TSource, TKey&gt;(source, keySelector, <span style="color: blue">null</span>, <span style="color: blue">false</span>);
}</pre>

<p>OrderedEnumerable&lt;TElement, TKey&gt;的含义是“根据TKey排序TElement序列的结果”，它的构造函数仅仅是保留传入的参数：</p>

<pre class="code"><span style="color: blue">internal </span>OrderedEnumerable(
    <span style="color: #2b91af">IEnumerable</span>&lt;TElement&gt; source, <span style="color: #2b91af">Func</span>&lt;TElement, TKey&gt; keySelector, <span style="color: #2b91af">IComparer</span>&lt;TKey&gt; comparer, <span style="color: blue">bool </span>descending)
{
    <span style="color: green">// 省略参数校验</span>

    <span style="color: blue">base</span>.source = source;
    <span style="color: blue">this</span>.parent = <span style="color: blue">null</span>;
    <span style="color: blue">this</span>.keySelector = keySelector;
    <span style="color: blue">this</span>.comparer = (comparer != <span style="color: blue">null</span>) ? comparer : ((<span style="color: #2b91af">IComparer</span>&lt;TKey&gt;) <span style="color: #2b91af">Comparer</span>&lt;TKey&gt;.Default);
    <span style="color: blue">this</span>.descending = descending;
}</pre>

<p>可见，如果您没有提供比较器，类库会自动选用Comparer&lt;TKey&gt;.Default进行比较。这个类会尽可能地寻找可用的比较方式，在“万不得已”的情况下只得跑出异常。如果您对它的实现感兴趣可以自行阅读代码——甚至无需使用.NET Reflector。</p>

<p>事实上，在OrderedEnumerable&lt;TElement, TKey&gt;中并没有提供排序等关键性功能，它只是override了基类的GetEnumerableSorter方法，用于提供一个“排序器”。它的基类是OrderdEnumerable&lt;TElement&gt;，其含义是“排序TElement序列的结果”，它并不涉及到“排序方式”，而只是提供了一个抽象方法用于获得一个“排序器”——没错，这就是它的子类，如OrderedEnumerable&lt;TElement, TKey&gt;的职责了（还记得TKey的含义吗：“根据TKey进行排序”）。</p>

<p>不过，事实上除了OrderdEnumerable&lt;TElement, TKey&gt;以外也没有其他子类了，由于这些都是internal类型，因此我认为这样有些“过渡设计”。根据我们昨天“人肉反编译”的结果，可以得到OrderedEnumerable&lt;TElement&gt;的完整实现：</p>

<pre class="code"><span style="color: blue">internal abstract class </span><span style="color: #2b91af">OrderedEnumerable</span>&lt;TElement&gt; : <span style="color: #2b91af">IEnumerable</span>&lt;TElement&gt;...
{
    <span style="color: blue">internal </span><span style="color: #2b91af">IEnumerable</span>&lt;TElement&gt; source;

    <span style="color: blue">internal abstract </span><span style="color: #2b91af">EnumerableSorter</span>&lt;TElement&gt; GetEnumerableSorter(<span style="color: #2b91af">EnumerableSorter</span>&lt;TElement&gt; next);

    <span style="color: blue">public </span><span style="color: #2b91af">IEnumerator</span>&lt;TElement&gt; GetEnumerator()
    {
        <span style="color: blue">var </span>buffer = <span style="color: blue">new </span><span style="color: #2b91af">Buffer</span>&lt;TElement&gt;(<span style="color: blue">this</span>.source);
        <span style="color: blue">if </span>(buffer.count &lt;= 0) <span style="color: blue">yield break</span>;

        <span style="color: blue">var </span>sorter = <span style="color: blue">this</span>.GetEnumerableSorter(<span style="color: blue">null</span>);
        <span style="color: blue">var </span>map = sorter.Sort(buffer.items, buffer.count);

        <span style="color: blue">for </span>(<span style="color: blue">var </span>i = 0; i &lt; buffer.count; i++)
        {
            <span style="color: blue">yield return </span>buffer.items[map[i]];
        }
    }

    ...
}</pre>

<p>与我们平时接触到的排序算法不同，EnumerableSorter的Sort方法并不改变原数组，它只是生成根据buffer.items数组生成一个排序之后的“下标序列”——即map数组。当外部需要输出排序后的序列时，OrderedEnumerable&lt;TElement&gt;才会根据map中的下标顺序，依次输出buffer.items数组中的元素。</p>

<p>请注意，到目前为止我们还是没有接触到最终的排序实现。换句话说，现在我们还是不清楚LINQ排序性能高（或低）的关键。</p>

<h1>排序实现：EnumerableSorter</h1>

<p>LINQ排序的实现关键还是在于EnumerableSorter&lt;TElement&gt;，我们且看其Sort代码：</p>

<pre class="code"><span style="color: blue">internal abstract class </span><span style="color: #2b91af">EnumerableSorter</span>&lt;TElement&gt;
{
    <span style="color: blue">internal abstract int </span>CompareKeys(<span style="color: blue">int </span>index1, <span style="color: blue">int </span>index2);
    <span style="color: blue">internal abstract void </span>ComputeKeys(TElement[] elements, <span style="color: blue">int </span>count);

    <span style="color: blue">private void </span>QuickSort(<span style="color: blue">int</span>[] map, <span style="color: blue">int </span>left, <span style="color: blue">int </span>right)
    {
        ...
    }

    <span style="color: blue">internal int</span>[] Sort(TElement[] elements, <span style="color: blue">int </span>count)
    {
        <span style="color: blue">this</span>.ComputeKeys(elements, count);

        <span style="color: blue">int</span>[] map = <span style="color: blue">new int</span>[count];
        <span style="color: blue">for </span>(<span style="color: blue">int </span>i = 0; i &lt; count; i++)
        {
            map[i] = i;
        }

        <span style="color: blue">this</span>.QuickSort(map, 0, count - 1);
        <span style="color: blue">return </span>map;
    }
}</pre>

<p>从之前的分析中得知，Sort方法的作用是返回一个排好序的下标数组。它会调用ComputeKeys抽象方法“事先”进行Key（也就是排序依据）的计算。然后再使用快速排序来排序map数组。在QuickSort中，它使用CompareKeys方法来获得“两个下标”所对应的元素的先后顺序。仅此而已，没什么特别的。甚至我在这里都不打算分析ComputeKeys和CompareKeys两个方法的实现，因为他们实在过于直接：前者会把source序列中的元素依次调用keySelector委托，以此获得一个与source对应的TKey数组，而后者便是根据传入的下标来比较TKey数组中对应的两个元素的大小。</p>

<p>不过，我还是强烈建议您阅读一下EnumerableSorter&lt;TElement&gt;及其子类EnumerableSorter&lt;TElement, TKey&gt;的实现，以此了解LINQ to Object是如何优雅地支持以下表达式的：</p>

<pre class="code"><span style="color: blue">var </span>sorted = <span style="color: blue">from </span>p <span style="color: blue">in </span>people
             <span style="color: blue">orderby </span>p.Age
             <span style="color: blue">orderby </span>p.ID <span style="color: blue">descending
             select </span>p;</pre>

<p>这个表达式的含义是“将Person序列首先根据Age属性进行升序排列，如果Age相同则再根据ID降序排”——类库在实现时使用了类似于“职责链模式”的做法，颇为美观。</p>

<h1>LINQ排序与Array.Sort&lt;T&gt;的性能比较</h1>

<p>如果您仔细阅读EnuerableSorter的QuickSort方法，会发现它使用的快速排序算法并不“标准”。快速排序的性能关键之一是选择合适的pivot元素，但是QuickSort方法总是选择最中间的元素——(left + right) / 2。此外，它也没有在元素小于一定阈值时使用更高效的插入排序。因此，从理论上来说，QuickSort方法使用的快速排序算法，其性能不如Array.Sort&lt;T&gt;。</p>

<p>不过，根据姜敏兄的测试结果，LINQ排序的性能超过Array.Sort&lt;T&gt;，这又是怎么回事呢？事实上，虽然姜兄的这个测试存在很大的问题（代码写错了），最后得到的结论“性能高低和元素类型有关”的结论也不确切，但是它也的确能体现一些问题。这个问题事实上已经由<a href="http://www.cnblogs.com/Ivony/">Ivony...</a>老大解释过了，不过为了信息完整思维连贯，我在这里再进行详细说明一下。</p>

<p>从理论上来说，Array.Sort&lt;T&gt;和LINQ排序的时间复杂度是相同的，因此性能“似乎不会有太大不同”，但是从实验结果上看差距还是十分明显的。因为从实际上看，Array.Sort&lt;T&gt;对于特殊类型有特殊处理，此外LINQ排序会有复制元素的开销，因此我之前我认为“找不到LINQ排序的性能有优势的理由”。可惜这句话已经站不住脚了，我们来观察一下两种排序方式在实现上的主要区别：</p>

<ul>
  <li>Array.Sort&lt;T&gt;：使用IComparer&lt;T&gt;对象比较两个元素的大小。 </li>

  <li>LINQ排序：首先根据keySelector获得TKey序列，然后在排序时使用IComparer&lt;TKey&gt;比较两个TKey元素的大小。 </li>
</ul>

<p>那么，以此您是否可以判断出以下两个排序方法的性能高低？</p>

<pre class="code"><span style="color: blue">public class </span><span style="color: #2b91af">Person
</span>{
    <span style="color: blue">public int </span>Age { <span style="color: blue">get</span>; <span style="color: blue">set</span>; }
}

<span style="color: blue">public class </span><span style="color: #2b91af">PersonComparer </span>: <span style="color: #2b91af">IComparer</span>&lt;<span style="color: #2b91af">Person</span>&gt;
{
    <span style="color: blue">public int </span>Compare(<span style="color: #2b91af">Person </span>x, <span style="color: #2b91af">Person </span>y)
    {
        <span style="color: blue">return </span>x.Age - y.Age;
    }
}</pre>

<pre class="code"><span style="color: #2b91af">Person</span>[] people = ...

<span style="color: blue">var</span> byLinq = people.OrderBy(p =&gt; p.Age).ToList();
<span style="color: blue">var </span>byArray = <span style="color: #2b91af">Array</span>.Sort(people, <span style="color: blue">new </span><span style="color: #2b91af">PersonComparer</span>());</pre>

<p>在实际测试之前我无法做出判断，因为它们其实各有千秋：</p>

<ul>
  <li>Array.Sort&lt;T&gt;：虽然不需要进行额外的元素复制，但是调用PersonComparer.Compare方法的开销较大——访问Age属性相当于调用get_Age方法（如果没有内联的话——不过从实际结果看的确被内联了）。</li>

  <li>LINQ排序：虽然需要进行额外的元素复制，而且需要事先计算出排序用的键值（Age属性），但是在排序时只需直接比较int即可，效率较高。</li>
</ul>

<p>这其实也就是某些测试中发现LINQ排序性能较高的“秘密”。为什么同样排序Person序列时，我的测试（<a href="http://gist.github.com/282796">http://gist.github.com/282796</a>）表明Array.Sort&lt;T&gt;较快，而其他一些朋友却得到LINQ排序较快的结果呢？这是因为我的Person类直接使用了公开字段而不是属性，这样避免了方法调用的开销（因为有内联，这点应该不是问题）。此外，另一些朋友的PersonComparer在比较两个int时使用了x.Age.CompareTo方法——这又比直接进行int减法要慢上一些了。</p>

<p>那么，还有影响两者性能的因素吗？我们有办法提高数组排序的性能吗？毕竟很多时候我们需要直接排序，而不是生成新的序列。下次我们再来讨论这些问题吧。</p>

<h1>相关文章</h1> 
<ul> 
	<li> 
	<a href="http://www.cnblogs.com/JeffreyZhao/archive/2010/01/21/sort-array-linq-1-notes-and-benchmark.html"> 
	数组排序方法的性能比较（1）：注意事项及试验</a> </li> 
	<li> 
	<a href="http://www.cnblogs.com/JeffreyZhao/archive/2010/01/22/sort-array-linq-2-array-sort.html"> 
	数组排序方法的性能比较（2）：Array.Sort&lt;T&gt;实现分析</a> </li> 
	<li>
	数组排序方法的性能比较（3）：LINQ排序实现分析</li> 
	<li> 
	<a href="http://www.cnblogs.com/JeffreyZhao/archive/2010/01/28/sort-array-linq-4-linq-style-array-sort.html"> 
	数组排序方法的性能比较（4）：LINQ方式的Array排序</a> </li> 
	<li> 
	<a href="http://www.cnblogs.com/JeffreyZhao/archive/2010/01/29/sort-array-linq-5-object-size-and-performance.html"> 
	数组排序方法的性能比较（5）：对象大小与排序性能</a> </li> 
</ul> <img src="http://www.cnblogs.com/JeffreyZhao/aggbug/1657030.html?type=1" width="1" height="1" alt=""/><p>作者: <a href="http://www.cnblogs.com/JeffreyZhao/" target="_blank">Jeffrey Zhao</a> 发表于 2010-01-27 00:02 <a href="http://www.cnblogs.com/JeffreyZhao/archive/2010/01/27/1657030.html" target="_blank">原文链接</a></p><p>评论: 26　<a href="http://www.cnblogs.com/JeffreyZhao/archive/2010/01/27/1657030.html#pagedcomment" target="_blank">查看评论</a>　<a href="http://www.cnblogs.com/JeffreyZhao/archive/2010/01/27/1657030.html#commentform" target="_blank">发表评论</a></p><hr/><p>最新新闻：<br/>· <a href="http://news.cnblogs.com/n/129385/" target="_blank">Jon Rubinstein 暂别科技舞台：再见，HP</a><span style="color:gray">(2012-01-28 21:36)</span><br/>· <a href="http://news.cnblogs.com/n/129384/" target="_blank">塞班3设备更新换代 诺基亚Belle出货</a><span style="color:gray">(2012-01-28 21:34)</span><br/>· <a href="http://news.cnblogs.com/n/129383/" target="_blank">推土机Windows 7补丁权威测试</a><span style="color:gray">(2012-01-28 19:24)</span><br/>· <a href="http://news.cnblogs.com/n/129382/" target="_blank">诺基亚在印度：品牌优势，渠道特点，跟中国的对比</a><span style="color:gray">(2012-01-28 19:21)</span><br/>· <a href="http://news.cnblogs.com/n/129381/" target="_blank">世界最早的计算机密码和黑客</a><span style="color:gray">(2012-01-28 19:20)</span><br/></p><p>编辑推荐：<a href="http://kb.cnblogs.com/page/120592/" target="_blank">Ruby之父松本行弘的编程人生</a><br/></p><p>网站导航：<a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://home.cnblogs.com/" target="_blank">我的园子</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://home.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://q.cnblogs.com/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://kb.cnblogs.com" target="_blank">知识库</a></p>]]></description></item><item><title>您选择简单的组合，还是完整的集成？</title><link>http://www.cnblogs.com/JeffreyZhao/archive/2009/10/28/1590921.html</link><dc:creator>Jeffrey Zhao</dc:creator><author>Jeffrey Zhao</author><pubDate>Tue, 27 Oct 2009 17:09:00 GMT</pubDate><guid>http://www.cnblogs.com/JeffreyZhao/archive/2009/10/28/1590921.html</guid><description><![CDATA[<p><p>其实这是两种文化，很多人说前者属于Unix文化，后者属于Windows文化。前者好比是一系列分散的小工具，它们互相配合完成任务，其典型代表是Unix Shell。而后者好比是一个完整的工具箱，包含了完成某件特定工作所需的几乎全部功能，其典型代表是我们再熟悉不过的Visual Studio。当然，既然是文化，就能够互相借鉴和采纳，因此我们在Unix环境中也可以使用如Eclipse和IntelliJ IDEA，而Windows环境中也有cygwin和Powershell。</p>  <p>可能一下子说起这个有点过于直接了，那么我们先来看一下小例子。我们.NET开发人员一定知道，一个项目在VS里编译好以后会出现obj目录和bin目录，其中包含了编译过程中的输出和编译结果。假如我们想要把整个解决方案打包给别人的话，我们一般要把这两个目录删除，它们的体积还是很可观的。那么，你会怎么做？是一个一个进入项目目录里删除，还是用某种方便的做法？这个问法可能有些愚蠢，因为我相信大部分朋友在经历多次“手动删除”之后，都会厌烦，都会设法有什么自动化的方式。不过此时其实也有两种做法。</p>  <p>例如，有人开发了这么一个简单的项目：<a href="http://code.google.com/p/treetrim/">TreeTrim</a>。这个小工具有个功能，便是在资源管理器的右键菜单中增加“清理”的功能，这样我们便可以方便地删除obj和bin目录。不过，我们也可以选择另一种方式，例如“命令行”。Windows传统的命令行功能比较弱，和Unix Shell相比简直天上地下。于是有人把Unix Shell移植到Windows平台上，是为cygwin。微软在两年前也推出了PowerShell，虽然从“已有功能”上讲远不如cygwin完整，但PowerShell毕竟是为Windows量身定制的，可能更容易被Windows程序员接受（如命令的alias，文件分隔符，.NET支持等等）。而PowerShell的关键还在于“Shell”，一个命令（cmdlet）之间通信的“工作平台”，我们需要的功能基本上都可以通过命令之间的扩展或组合表达出来。</p>  <p>例如在PowerShell中，我们如果要列出当前目录（及子目录）下所有的bin和obj文件夹，可以这样写：</p>  <pre class="code">Get-ChildItem -include obj,bin -recurse </pre>

<p>那么，我们又该如何将列出的目录删除呢？可以这样：</p>

<pre class="code">Get-ChildItem -include obj,bin -recurse | ForEach-Object { Remove-Item -path $_.FullName -recurse }</pre>

<p>我们通过“管道”，也就是“|”符号组成一个命令。管道的作用是将前一个命令的输出，作为后一个命令的输入。例如上面的例子，Get-ChildItem命令得到的所有目录，会作为参数传递给ForEach-Object命令，后者再为每一项（即目录）执行一个RemoveItem命令进行删除（大括号中的$_即表示当前那一项）。看上去这个命令很复杂，输入很麻烦，不过好在PowerShell的命令行有自动补全功能，而且其实几乎每个命令都有一个或多个别名（alias），或者说是“简写”形式。例如上面这行命令也可以这样写：</p>

<pre class="code">dir -i obj,bin -r | % { del -path $_.FullName -r }</pre>

<p>效果也是完全一样的。</p>

<p>其实我本来没打算谈这些，因为无论是PowerShell的使用，还是“文化”都已经被许多人嚼烂了。我今天写这个东西的原因是，我想要做的一件事情正好落入了这样的“俗套”，于是不禁“多想”了一些。这个事情便是所谓的“项目模板”。</p>

<p>Visual Studio很强大，支持自定义项目模板或元素模板，打个zip包丢到指定目录里就行了。使用项目模板创建项目，便可以创建出预定义好的许多内容，例如文件，程序集的引用等等。在使用模板创建项目或元素的时候，我们还可以给出一个“向导”，例如创建ASP.NET MVC项目的时候，会提示您是否创建配套的单元测试项目。之后，对项目的Views目录点击鼠标右键还会出现“Create View”选项，选择后还会有新的向导窗口，让您输入更多的配置等等。这些配置项会用来替换模板中某些文件内容里的占位符，于是生成的结果就会有所不同了。总之，ASP.NET MVC秉承了微软一贯的风格，除了没有把项目直接帮你做完，已经给了我们许许多多的帮助。于是我们很多程序员就像被父母宠坏的孩子，只想着衣来伸手饭来张口。一旦遇到问题，就开始嚷嚷微软提供的东西怎么那么差，问题那么多，又没有技术含量——也就好像不懂事的小孩遇到困难就去责怪父母没有把一切都安排好。</p>

<p>我不是这样的“孩子”，但有了微软这样的榜样，我也总是想着如何做好一个“父母”。比如，我最近在准备TechEd的讲座示例，也想着MvcPatch项目的使用方式，于是我也想像ASP.NET MVC那样创建一个模板。目标很简单：让用户输入基础的程序集和命名空间，然后由此确定每个项目的相关属性。但是我不会啊，我从来没有做过这个东西。记得我好像在05还是06年的MSDN Magazine里看到过相关的文章，但从来没有亲手做过任何一个模板。于是我打开了文档，琳琅满目，最后……发现这真不是一件容易的事情，又要设计文件内容，又要编写窗体。可能装了某些插件之后可以方便，但是我也真不想把时间花在这个东西上面，因为这对我来说又有什么长进呢？</p>

<p>不过后来我忽然想到，其实我要的功能完全只是个“项目文件”加上“占位符替换”而已，这个工作完全也可以由PowerShell命令完成：</p>

<pre class="code">Get-ChildItem -path src -recurse | Where-Object { $_.mode -like '*a*' } | ForEach-Object { (Get-Content $_.FullName) | ForEach-Object { $_ -replace &quot;ProjectAssemblyNameBase&quot;, &quot;RealWorldMvc&quot; -replace &quot;ProjectNamespaceBase&quot;, &quot;TechEd.RealWorldMvc&quot; } | Set-Content $_.FullName }</pre>

<p>不过这样的完整写法很冗余，适合写在脚本中。如果只是单行命令，不追求长期的可读性，也可以使用它的等价简写形式：</p>

<pre class="code">dir -p src -r | ? { $_.mode -like '*a*' } | % { (gc $_.FullName) | % { $_ -replace &quot;ProjectAssemblyNameBase&quot;, &quot;RealWorldMvc&quot; -replace &quot;ProjectNamespaceBase&quot;, &quot;TechEd.RealWorldMvc&quot; } | sc $_.FullName }</pre>

<p>就此，任务也就完成了。可能我只需要再准备的脚本，提示用户输入一些字符即可。在这样的场景下，使用复杂的项目模板的确会更加方便一些，对开发人员的支持也更好。但是从性价比角度来说，这种基于PowerShell解决方案难道不是更直接一些吗？</p>

<p>本来还想补充一些话，最后打算还是<a href="http://www.cnblogs.com/JeffreyZhao/archive/2009/10/28/more-on-composition-or-integration.html">独立开篇</a>吧。</p><img src="http://www.cnblogs.com/JeffreyZhao/aggbug/1590921.html?type=1" width="1" height="1" alt=""/><p>作者: <a href="http://www.cnblogs.com/JeffreyZhao/" target="_blank">Jeffrey Zhao</a> 发表于 2009-10-28 01:09 <a href="http://www.cnblogs.com/JeffreyZhao/archive/2009/10/28/1590921.html" target="_blank">原文链接</a></p><p>评论: 39　<a href="http://www.cnblogs.com/JeffreyZhao/archive/2009/10/28/1590921.html#pagedcomment" target="_blank">查看评论</a>　<a href="http://www.cnblogs.com/JeffreyZhao/archive/2009/10/28/1590921.html#commentform" target="_blank">发表评论</a></p><hr/><p>最新新闻：<br/>· <a href="http://news.cnblogs.com/n/129385/" target="_blank">Jon Rubinstein 暂别科技舞台：再见，HP</a><span style="color:gray">(2012-01-28 21:36)</span><br/>· <a href="http://news.cnblogs.com/n/129384/" target="_blank">塞班3设备更新换代 诺基亚Belle出货</a><span style="color:gray">(2012-01-28 21:34)</span><br/>· <a href="http://news.cnblogs.com/n/129383/" target="_blank">推土机Windows 7补丁权威测试</a><span style="color:gray">(2012-01-28 19:24)</span><br/>· <a href="http://news.cnblogs.com/n/129382/" target="_blank">诺基亚在印度：品牌优势，渠道特点，跟中国的对比</a><span style="color:gray">(2012-01-28 19:21)</span><br/>· <a href="http://news.cnblogs.com/n/129381/" target="_blank">世界最早的计算机密码和黑客</a><span style="color:gray">(2012-01-28 19:20)</span><br/></p><p>编辑推荐：<a href="http://kb.cnblogs.com/page/120592/" target="_blank">Ruby之父松本行弘的编程人生</a><br/></p><p>网站导航：<a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://home.cnblogs.com/" target="_blank">我的园子</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://home.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://q.cnblogs.com/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://kb.cnblogs.com" target="_blank">知识库</a></p>]]></description></item><item><title>是什么造成了MVP的尴尬现状？我们该怎么办？</title><link>http://www.cnblogs.com/JeffreyZhao/archive/2009/07/02/1515079.html</link><dc:creator>Jeffrey Zhao</dc:creator><author>Jeffrey Zhao</author><pubDate>Wed, 01 Jul 2009 17:49:00 GMT</pubDate><guid>http://www.cnblogs.com/JeffreyZhao/archive/2009/07/02/1515079.html</guid><description><![CDATA[<p><p>MVP项目其实微软的“专家认证”活动之一，不过它并非根据培训或考试而来，而是根据个人在社区表现评选出来的内容。从这一方面来说，MVP应该是广受社区群众爱戴的群体。</p> <p>记得在02、03年的时候，微软MVP在我眼中也是非常“高大”的，似乎也就那么几个人，他们个个大牛，受人景仰。似乎是在前年的MVP峰会上，微软的朋友在讲台上兴奋的宣布，中国区的现任MVP数量从一开始的几个人，到后来的100人左右，又变成了220多（现在似乎有250人左右），这表示国内社区越来越活跃，有突出贡献的人越来越多。当然，这么说的前提是微软没有把MVP的门槛放低。在这一点上，微软的朋友自己可能也是这么认为的，因为虽然MVP数量增长迅猛，但是他们占整体从业人员的比例却逐年降低。从某些角度看来，MVP的质量应该不差。</p> <p>但是后来等我也评上MVP的时候，发现事实似乎并非如此。微软眼中的“社区英雄”，反而在社区中的评价越来越差。可能在一两年前某些朋友（包括我）在博客上发表了“当选感受”，并表示“再接再厉”之后，社区中的朋友总是报以积极而热烈的回应，例如说一些恭喜，说一些羡慕，说一些榜样之类的话。而就<a href="http://www.cnblogs.com/netgodcn/archive/2009/07/01/microsoft_mvp_2009_07.html">2009年7月新当选的MVP</a>来看，似乎绝大部分的声音都是“质疑”（这两个字已经过于文绉绉，以至于不能正确描述许多人的评价，因此如果您去亲自看一下，就会有更深体会）。除去一些明显是来扯蛋的喷子，从这些评价中还是能看到一些问题。如最直接的，为什么大家好像普遍不满如今当选的MVP。</p> <p>有些原因可能是MVP人数越来越多，越来越多的朋友也可以接触到不同的MVP，这样MVP的光环也就慢慢褪去。我经常说，别看我在博客上社区里生龙活虎牛逼哄哄，真和我共事的时候你就会发现其实老赵这也不会那也不会的——“距离产生美”么！作为技术人员，评价一个“专家”的主要标准就是技术。遇到了MVP多了就会发现，好像好多MVP的技术“<a href="http://www.cnblogs.com/netgodcn/archive/2009/07/01/microsoft_mvp_2009_07.html#1572984">也不过如此</a>”。其实，MVP的评价标准在于“做出多少贡献”，而并非在于“水平有多高”。如果A有100分的水平，而在社区里分享了30%；B有60分的水平，却在社区里分享了80%，那么在MVP评选时，估计十有八九会选择B而不是水平更高的A。我认为这其实很公平，就像<a href="http://www.cnblogs.com/cathsfz/">陈猫</a>同学认为“<a href="http://www.cnblogs.com/frand/archive/2009/06/14/1503059.html#1557241">高手应该注重分享和双赢</a>”而不是“<a href="http://www.cnblogs.com/frand/archive/2009/06/14/1503059.html">把诀窍藏着掖着害怕别人赶上</a>”，而我也在<a href="http://www.cnblogs.com/fangbaiyi/archive/2009/05/14/1456469.html#1527709">谈论北大青鸟老师水平的时候说到</a>“老师的个人能力再好也没用，关键在于教给学生多少”。如果您觉得MVP的技术水平不够，不妨还是去看看到底他做了什么贡献。当然，我并不是说没有水平的MVP都是分享达人，完全混出来的MVP自然也有，不过这点我们一会儿再说。</p> <p>也有朋友质疑一些当选者的资格，认为他们并没有做出什么贡献，其实这点有可能也略有偏颇。因为MVP的选拔是面向全国社区的，而一般对于某个人也只是接触少数几个社区，对于从其他社区中当选的MVP的贡献自然并不了解。如博客园的网友经常质疑CSDN的质量，因而也不太去关心，那么平心而论，既然您不去关注它，又能以什么理由质疑那里选出的MVP呢？类似情况还比如MVP分为许许多多个方向，您关注.NET，可能就不太了解Windows、Office或Mobile方面的情况；您关注开发技术，可能就不太了解IT Pro的情况；您关注线上社区，可能就不太了解线下（企业内训、写书、翻译等等）的情况。因此，质疑某个MVP的资质应该也是需要有充分理由的。例如，您可以去网上搜一下他的情况，说不定您就会对某个人有更进一步的了解了。</p> <p>当然这也有MVP当选者的问题。我一直不太相信所谓“真正的高手是默默无闻的”这种说法，在这个互联网如此发达的时代，还会有多少高手如“隐士”般的存在呢？扫地僧之类的人物基本上只能在小说里才能出现。如果某个高手作出了一定贡献，那一定不差，那么为什么没有人看到并把它公开出来呢？再比如，您崇拜的那些国外大牛们，有多少是不热衷于社区贡献的呢？他们仅仅通过一本书就让他们成为专家并享有如此声誉吗？业界著名大牛<a href="http://www.codinghorror.com/blog/">Jeff Atwood</a>曾经说过（大意）：“许多人并非因为他们的技术，而是他们的博客才让我感到尊敬”。如果您是一个合格的MVP当选者，为什么您的贡献会不为人所知？为什么您会遭人质疑？作为MVP应该是一个社区英雄，但是为什么您无法得到社区的认可？</p><p>同样还有，为什么您当选了MVP之后作出的贡献就少了呢？MVP给您带来了荣誉和机会，但是您的责任也应该随之而来，您的举动负担起这些责任了吗？</p> <p>再者就是微软的错误。既然MVP的本意是社区英雄，那么为什么最终的评选没有交给社区呢？原本应该是全民参与的选举活动，现在大部分的朋友都不知道这整个评选的过程、细节、方式，这难道不是个明显的问题吗？我也明白，MVP评选委员会是根据参选者提交的，自己在上一年中的贡献而做出的判断，那么能否把这份申请表中列举的“贡献”公开出来呢？这样可以在一定程度上消除社区对MVP的质疑，也可以让MVP们的影响力进一步提高，从而也可以让MVP项目的声望加强。MVP项目的声望，难道不是靠MVP们的表现积累起来的吗？</p> <p>最后还是国内行业现状，这需要大家一起努力。例如有些朋友认为，为什么发了一大堆意义不大的文章就可以当选，为什么随意回答一些问题灌灌水就可以当选，为什么写本烂书，或进行一次翻译也可以当选？从这方面看，社区，论坛，出版社……可以说行业里的各种组织都负有一定的责任。而社区中的广大同志也应该有自己的责任，例如我们是否在追捧一些“伪高手”亦或是浮躁之风？我们遇到了不正确的情况，有没有使用正确的方式来维护我们的利益（在没有尝试之前就说“没用”本身就是不正确的做法）？整个社区的氛围是一起推动和改善的，它并不是几个所谓“社区英雄”的个人能力可以做到的。我们每一个做法都会对社区产生多多少少的影响，如果我们想生活一个优秀的社区里，我们就不能自己来破坏它。</p> <p>MVP项目虽然是微软的活动，但是MVP们应该是得到社区承认的群体。如果MVP得不到社区的承认，而社区中真正的英雄并不是MVP；如果英雄们得不到微软的“福利”和帮助，无法得到更有效的方式来回馈社区；如果社区群体无法以身为MVP感到自豪，甚至感到不屑和反对——这无论对于社区还是微软来讲都是一种悲哀。大大的悲哀。</p> <p>我也在想，是什么造成了MVP的尴尬现状？我们又该怎么办？如果您有什么建议，能否说出来一起讨论一下？</p><img src="http://www.cnblogs.com/JeffreyZhao/aggbug/1515079.html?type=1" width="1" height="1" alt=""/><p>作者: <a href="http://www.cnblogs.com/JeffreyZhao/" target="_blank">Jeffrey Zhao</a> 发表于 2009-07-02 01:49 <a href="http://www.cnblogs.com/JeffreyZhao/archive/2009/07/02/1515079.html" target="_blank">原文链接</a></p><p>评论: 242　<a href="http://www.cnblogs.com/JeffreyZhao/archive/2009/07/02/1515079.html#pagedcomment" target="_blank">查看评论</a>　<a href="http://www.cnblogs.com/JeffreyZhao/archive/2009/07/02/1515079.html#commentform" target="_blank">发表评论</a></p><hr/><p>最新新闻：<br/>· <a href="http://news.cnblogs.com/n/129385/" target="_blank">Jon Rubinstein 暂别科技舞台：再见，HP</a><span style="color:gray">(2012-01-28 21:36)</span><br/>· <a href="http://news.cnblogs.com/n/129384/" target="_blank">塞班3设备更新换代 诺基亚Belle出货</a><span style="color:gray">(2012-01-28 21:34)</span><br/>· <a href="http://news.cnblogs.com/n/129383/" target="_blank">推土机Windows 7补丁权威测试</a><span style="color:gray">(2012-01-28 19:24)</span><br/>· <a href="http://news.cnblogs.com/n/129382/" target="_blank">诺基亚在印度：品牌优势，渠道特点，跟中国的对比</a><span style="color:gray">(2012-01-28 19:21)</span><br/>· <a href="http://news.cnblogs.com/n/129381/" target="_blank">世界最早的计算机密码和黑客</a><span style="color:gray">(2012-01-28 19:20)</span><br/></p><p>编辑推荐：<a href="http://kb.cnblogs.com/page/120592/" target="_blank">Ruby之父松本行弘的编程人生</a><br/></p><p>网站导航：<a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://home.cnblogs.com/" target="_blank">我的园子</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://home.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://q.cnblogs.com/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://kb.cnblogs.com" target="_blank">知识库</a></p>]]></description></item><item><title>MVP on dot NET - Episode 1</title><link>http://www.cnblogs.com/cathsfz/archive/2008/01/07/1028568.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Mon, 07 Jan 2008 03:38:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2008/01/07/1028568.html</guid><description><![CDATA[<p><p>主持：<a href="http://www.beanpeng.com">Bean</a>、<a href="http://jeffreyzhao.cnblogs.com">Jeffrey</a>、<a href="http://dotnet.catchen.biz">Cat</a></p>
<ol>
    <li>根据<a href="http://blogs.pcworld.com/techlog/archives/006130.html">PC World</a>的统计，2007年IE7与FF的增长相当，这意味着从IE6转分别转到IE7与FF的比例为50/50。</li>
    <li>Mac效果般的Lightbox效果 - 来自<a href="http://www.panic.com/candybar/">CandyBar</a>软件首页（留意页面底部的软件截图）</li>
    <li>纯粹用C#编写的<a href="http://osnews.com/story.php/19100/SharpOS-Releases-First-Milestone">SharpOS发布了第一个里程碑版本</a> - 0.0.1</li>
    <li><a href="http://neosmart.net/blog/2008/redesigned-microsoft-website-to-use-silverlight/">NeoSmart Technologies</a>报道，Microsoft将使用Silverlight重新设计其官方网站</li>
    <li>国内的Silverlight网站 - <a hrfe="http://mu.baidu.com/">百度MP3</a></li>
    <li>在Windows Mobile手机上使用Windows Vista的<a href="http://www.cnbeta.com/article.php?sid=46238">Side Show</a>功能！</li>
    <li><a href="http://www.sprymedia.co.uk/article/Design">Design</a> - 一个很cool的纯JavaScript的Web页面设计辅助工具</li>
    <li>使用来自webkit团队的<a href="http://www.codinghorror.com/blog/archives/001023.html">Sunspider</a>测试各种浏览器的JavaScript引擎性能</li>
    <li><a href="http://www.alistapart.com/articles/previewofhtml5">HTML 5 Preview</a> - XHTML 2.0还有发展前途吗？
    </li>
</ol>
<p>直接下载地址：<a href="http://www.podtrac.com/pts/redirect.mp3?http://downloads.cnblogs.com/netcast/mvp-on-dot-net-1-40bit.mp3">http://www.podtrac.com/pts/redirect.mp3?http://downloads.cnblogs.com/netcast/mvp-on-dot-net-1-40bit.mp3</a></p>
<p><strong>Update: </strong>本期已将采样率改为22.050kHz，使用Google Reader订阅的用户应该能够直接使用Web上的播放控件正常播放而不会出现快进的问题。如果大家在播放上还碰到什么问题了，请留言告诉我们，谢谢。</p>
<img src="http://www.cnblogs.com/cathsfz/aggbug/1028568.html?type=1" width="1" height="1" alt=""/><p>作者: <a href="http://www.cnblogs.com/cathsfz/" target="_blank">Cat Chen</a> 发表于 2008-01-07 11:38 <a href="http://www.cnblogs.com/cathsfz/archive/2008/01/07/1028568.html" target="_blank">原文链接</a></p><p>评论: 35　<a href="http://www.cnblogs.com/cathsfz/archive/2008/01/07/1028568.html#pagedcomment" target="_blank">查看评论</a>　<a href="http://www.cnblogs.com/cathsfz/archive/2008/01/07/1028568.html#commentform" target="_blank">发表评论</a></p><hr/><p>最新新闻：<br/>· <a href="http://news.cnblogs.com/n/129385/" target="_blank">Jon Rubinstein 暂别科技舞台：再见，HP</a><span style="color:gray">(2012-01-28 21:36)</span><br/>· <a href="http://news.cnblogs.com/n/129384/" target="_blank">塞班3设备更新换代 诺基亚Belle出货</a><span style="color:gray">(2012-01-28 21:34)</span><br/>· <a href="http://news.cnblogs.com/n/129383/" target="_blank">推土机Windows 7补丁权威测试</a><span style="color:gray">(2012-01-28 19:24)</span><br/>· <a href="http://news.cnblogs.com/n/129382/" target="_blank">诺基亚在印度：品牌优势，渠道特点，跟中国的对比</a><span style="color:gray">(2012-01-28 19:21)</span><br/>· <a href="http://news.cnblogs.com/n/129381/" target="_blank">世界最早的计算机密码和黑客</a><span style="color:gray">(2012-01-28 19:20)</span><br/></p><p>编辑推荐：<a href="http://kb.cnblogs.com/page/120592/" target="_blank">Ruby之父松本行弘的编程人生</a><br/></p><p>网站导航：<a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://home.cnblogs.com/" target="_blank">我的园子</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://home.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://q.cnblogs.com/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://kb.cnblogs.com" target="_blank">知识库</a></p>]]></description></item><item><title>MVP on dot NET - Pilot Episode</title><link>http://www.cnblogs.com/cathsfz/archive/2008/01/01/1022112.html</link><dc:creator>Cat Chen</dc:creator><author>Cat Chen</author><pubDate>Tue, 01 Jan 2008 04:33:00 GMT</pubDate><guid>http://www.cnblogs.com/cathsfz/archive/2008/01/01/1022112.html</guid><description><![CDATA[<p><p>主持：<a href="http://www.beanpeng.com">Bean</a>、<a href="http://jeffreyzhao.cnblogs.com">Jeffrey</a>、<a href="http://dotnet.catchen.biz">Cat</a></p>
<ol>
    <li><a href="http://mezzoblue.com/archives/2007/12/19/8_2_x2/">8 + 2 = ☺</a> - <a href="http://blogs.msdn.com/ie/archive/2007/12/19/internet-explorer-8-and-acid2-a-milestone.aspx">IE8通过了Acid2测试</a></li>
    <li><a href="http://thinkgos.com/">gOS</a>发布，日后我们仅仅使用Web也能生存下去吗？客户端过剩的CPU资源用来干什么？</li>
    <li>文档都放上线！我们用<a href="https://share.adobe.com/adc/adc.do">Adobe SHARE</a>还是<a href="http://workspace.office.live.com/">Office Live Workspace</a>？</li>
    <li>Linq to SQL、ADO.NET Entity Framewor，以及关于ORM的更多&#8230;&#8230;</li>
    <li><a href="http://www.asp.net/downloads/3.5-extensions/">ASP.NET MVC Framework</a>发布，与ASP.NET WebForm对比又如何？</li>
    <li>iPhone Web App开发 - 有点点像WPF噢！</li>
</ol>
<p>P.S.podcast订阅的图片教程稍后发布。</p>
<p>直接下载地址：<a href="http://www.podtrac.com/pts/redirect.mp3?http://downloads.cnblogs.com/netcast/mvp-on-dot-net-0-40bit.mp3">http://www.podtrac.com/pts/redirect.mp3?http://downloads.cnblogs.com/netcast/mvp-on-dot-net-0-40bit.mp3</a></p>

<img src="http://www.cnblogs.com/cathsfz/aggbug/1022112.html?type=1" width="1" height="1" alt=""/><p>作者: <a href="http://www.cnblogs.com/cathsfz/" target="_blank">Cat Chen</a> 发表于 2008-01-01 12:33 <a href="http://www.cnblogs.com/cathsfz/archive/2008/01/01/1022112.html" target="_blank">原文链接</a></p><p>评论: 19　<a href="http://www.cnblogs.com/cathsfz/archive/2008/01/01/1022112.html#pagedcomment" target="_blank">查看评论</a>　<a href="http://www.cnblogs.com/cathsfz/archive/2008/01/01/1022112.html#commentform" target="_blank">发表评论</a></p><hr/><p>最新新闻：<br/>· <a href="http://news.cnblogs.com/n/129385/" target="_blank">Jon Rubinstein 暂别科技舞台：再见，HP</a><span style="color:gray">(2012-01-28 21:36)</span><br/>· <a href="http://news.cnblogs.com/n/129384/" target="_blank">塞班3设备更新换代 诺基亚Belle出货</a><span style="color:gray">(2012-01-28 21:34)</span><br/>· <a href="http://news.cnblogs.com/n/129383/" target="_blank">推土机Windows 7补丁权威测试</a><span style="color:gray">(2012-01-28 19:24)</span><br/>· <a href="http://news.cnblogs.com/n/129382/" target="_blank">诺基亚在印度：品牌优势，渠道特点，跟中国的对比</a><span style="color:gray">(2012-01-28 19:21)</span><br/>· <a href="http://news.cnblogs.com/n/129381/" target="_blank">世界最早的计算机密码和黑客</a><span style="color:gray">(2012-01-28 19:20)</span><br/></p><p>编辑推荐：<a href="http://kb.cnblogs.com/page/120592/" target="_blank">Ruby之父松本行弘的编程人生</a><br/></p><p>网站导航：<a href="http://www.cnblogs.com" target="_blank">博客园首页</a>&nbsp;&nbsp;<a href="http://home.cnblogs.com/" target="_blank">我的园子</a>&nbsp;&nbsp;<a href="http://news.cnblogs.com" target="_blank">新闻</a>&nbsp;&nbsp;<a href="http://home.cnblogs.com/ing/" target="_blank">闪存</a>&nbsp;&nbsp;<a href="http://q.cnblogs.com/" target="_blank">博问</a>&nbsp;&nbsp;<a href="http://kb.cnblogs.com" target="_blank">知识库</a></p>]]></description></item></channel></rss>
