之前的坑——图书借阅管理“书堆”

不准备埋这个坑了。

当时做这个“书堆”的起因是前年底想在部门内搞起图书分享活动,各人把自己的书贡献出来,通过一个web平台管理已有的图书,也能够轻松地进行图书流通轨迹的记录。图书信息通过豆瓣API拿到,图片也是直接引用的豆瓣。

后来就变成坑了,原因是发现除了我之外,没人有这个需求,连平时看书的都不多。这是第一版的界面:

shudui00

虽然没派上用场,但个人来说通过挖坑学了不少东西,尤其是Tornado平台,借着写书堆的机会学习了它的实现。还有nginx以及后续对其架构和代码的学习。去年底时候又拿出来改了改,增加了点功能(本机代理、全文检索、缓存……等),UI也修了修:

shudui01

shudui02

用sscli学习.NET实现

在Blog上记录过两篇和.NET中GC机制相关的学习笔记(这里这里),但都是从概念上泛泛而谈,所学习、参考和引用的资料也都是从概念上进行介绍,并没有涉及到代码层面的实现。由于工作中也用不到,所以没有继续关注下去。

最近则是在学习.NET Framework Library时想要更清楚地了解.NET对象的内存布局,才知道其实微软早就在10年前发布了开源的CLI实现sscli(Shared Source Common Language Infrastructure),代号Rotor。,并且于06年发布了它的2.0版本。尽管在之后这个项目就没再更新(有消息说已经下线),而且微软后续对于.NET Framework的更新使得现在的实现肯定和之前的大相径庭,但对于这个层面的代码来说,光是sscli2.0就够一般的学习者喝一壶,其中VM、JIT Compiler以及更上层的GC机制等实现,也远远还没有到过时的程度。如果非要追最新的实现,可以参考.NET CF 4.0,它现在从CLR层次就是开源的。

对于sscli,关注它的人并不多,毕竟这是底层到直接涉及托管程序运行、管理的框架。平时大家只要知道怎么编写和载入Assembly、让托管程序或网页跑起来,满足老板/PM/甲方的需求就行了,至于个中具体流程,没有必要关心。甚至于说,只要通读《CLR via C#》后清楚.NET各种概念的实现和关系、知道IL一级的实现,再加上了解大部分运行在CLR上层的机制,就能应付90%以上的工作场景。但sscli还是有必要看看:

  • 解决一些疑难杂症,例如奇怪的内存泄露、CPU满载问题。
  • 更清楚地了解原理。就拿刚才提到的Object内存布局问题来说,利用概念层次的讲解文章《浅析.NET中的引用类型和值类型(上)》以及文中提到的《Pro .NET Performance》这本书,结合sscli具体实现才能做到有自信掌握看破这一话题的能力。
  • 满足自己的好奇心……不是开玩笑,例如GC实现中的各种算法、safe point、hijacking什么的,你说知道具体实现又有何用?单纯是想知道而已。尽管如此,我觉得这类“Under the hood”学习应该算是程序员的自我修养,也是进行“写程序的人”和“程序员”之鉴别的标准之一。

下载了sscli2.0的代码之后,会发现除了clr文件夹内的核心实现外,还有.net相关工具集,以及一部分(老的).NET Framework代码,例如HttpRequest、Xml等实现。这些代码中有最近正在学的正则表达式引擎的完整实现,算是意外收获。另外还附送一个完整的jscript引擎。具体内容可以看docs文档。

接着可以参考下面几篇文章进行build和debug:

当然,windbg是不能少的,用来调试具体的托管程序,结合dump出来的信息,查看对应的cli代码,可以从将知识学习从逻辑层面过渡到实际的物理层面。

有关sscli整体学习,我还是不推荐自己抠代码,毕竟类似gc_heap这种类,光声明就好几千行,难以下手。看看大牛们的文章,例如CLR探索系列这个教程。里面从最基本的windbg载入sos调试程序开始,一步步介绍CLR基本概念,从CLR初始化到载入托管exe和dll,再到托管PE格式、GC机制等等,脉络清晰,图文并茂。另外还有一些针对具体问题的blog,例如《深入了解CLR的加载过程》等,结合阅读会事半功倍。

其他一些学习资源:

成为底层达人,就可以像这些大牛一样玩一些有趣的游戏了,比如自己定义新的IL Opcode或者是用托管代码让CLR挂掉之类。至于能不能在物质层面转化这些知识,就要看个人造化,但“天道酬勤”总是没错的。

在Windows Phone页面间传递自定义数据

之前因为需要在页面间传递比字符串、数值更复杂的结构数据,所以写了这么个帮助类,放到了github上。

对外的调用方法:

public static void Go( string pageName, T data, params KeyValuePair[] parameters )
public static T ExtractData( Dictionary queryStrings )

调用形如

class MyData {}

var myData = new MyData();
NavigationHelper.Go( "MyPage.xaml", myData );
myData = NavigationHelper.ExtractData( NavigationContext.QueryString );

具体的数据转换、存储工作在PageDataHandler里面,目前直接调用了Json.Net的序列化功能(简单包装了一层)。现在有两个版本的PageDataHandler,分别用文件和Uri参数存储。

需要注意的是并不是所有类都可以进行序列化——例如.Net自己的Dictionary。所以我又加了一个SerializableSafeModel,作为处理这种情况的基类。里面包含一个可以序列化的Dictionary,接收原始对象和不能序列化的Dictionary作为参数,手动将其转移到可序列化的对象内。

对了,如果想知道为什么还专门给这玩意写篇blog的话,答案就是如果不写,到下周一就会出现连续三篇blog都是介绍稻香村二十四节气时令点心的情况,想想都不能忍。

有关引用其他工程UserControls造成XamlParseException异常的问题

今天将之前写的一个模块移植到了Windows Store App平台的项目上。模块中有UserControl,也有Page。通过Activator生成一个UserControl实例的时候出现了异常:

“System.Reflection.TargetInvocationException”类型的异常在 mscorlib.dll 中发生,但未在用户代码中进行处理

其他信息: Exception has been thrown by the target of an invocation.

查看InnerException,发现是XAML parsing failed。感觉很奇怪,因为还没有添加到主解决方案时候测试是好用的。不仅如此,直接Frame.Navigate()到Page,也会引发异常,这次直接是XamlParseException了,依然是XAML parsing failed。

抱着试试看的心理,把用到的UserControl整个从界面到code behind都注释掉了,还是有异常。再试,新建一个UserControl,还是有。

那么问题肯定出在模块内的UserControl上了,从this.InitializeComponent();跟进去,看到了奇怪的代码:

global::Windows.UI.Xaml.Application.LoadComponent(this, new global::System.Uri("ms-appx:///Carta_UiComponents/LazyLoadListControl.xaml"), global::Windows.UI.Xaml.Controls.Primitives.ComponentResourceLocation.Nested);

“Carta_UiComponents”,这里程序集名称中的点号被翻译成了下划线。就算手动改成点号也没有,因为.g.i.cs属于codgen文件,自动生成。放狗一搜,发现之前就有不少人碰到过相同的问题:Migrating Windows RP application to Windows 8 RTM – XAMLParseException关于Visual Studio 2012 RTM 中创建windows 8 style类型的应用出现的XamlParseException 异常http://blog.excastle.com/2012/09/06/xamlparseexception-in-winrt/,而且都提到在微软给出的Hotfix页面上,实际上并没有patch下载,所以目前来说最简单的解决方案就是——新建项目后把默认程序集名字里面的点都去掉,WTF!

.NET反射应用两例:非硬编码PropertyChanged和TryParse<T>

非硬编码PropertyChanged

之前使用某WP7开发框架时候注意到他们的INotifyPropertyChanged的实现中 ,PropertyChanged不用传字符串,而是可以直接用Property本身作为参数。当时没有太注意。上周在处理一个Bug时候发现,问题出在Property的Set中,传进去的property name是错的,导致界面无法按照期望的更新。于是想到了之前那个框架的做法,找出来后一劳永逸解决问题。

改进后的PropertyChanged则可以利用作为参数存在的expression的返回值提取必要的信息出来:

        protected void NotifyPropertyChange( Expression> expression ) {
            if ( PropertyChanged != null ) {
                var propertyName = ( ( MemberExpression )expression.Body ).Member.Name;
                PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
            }
        }

其实方案也并非完美,因为需要传递一个lambda expression进去,只不过参数为空,返回值是property本身:

        public bool AppBarVisible {
            get {
                return _appBarVisible;
            }
            set {
                _appBarVisible = value;
                NotifyPropertyChange( () => AppBarVisible );
            }
        }

但总比之前用字符串的方式好得多,因为这样一来,写错了property name,编译会不通过。在Visual Studio中,点中Property后,所有相同的对象也都会自动高亮,避免写串了的情况。

TryParse<T>

也是为了解决项目中遇到问题的副产品:页面需要解析query strings,每次都写各种类型的TryParse太麻烦了,于是想到直接在Dictionary<string, string>上加一个extension method,用于直接以期望类型返回请求字符串。

最开始写的是:

TryParseInt( this Dictionary queryStrings, string key ) {...}
TryParseDouble( this Dictionary queryStrings, string key ) {...}
TryParseBool( this Dictionary queryStrings, string key ) {...}

等写完第三个才觉得自己有病:难道有几十种type就都写成TryParseType吗!?这种情况用泛型很合适:

        public static T GetValue( this Dictionary queryString, string key ) {
            if ( queryString.ContainsKey( key ) ) {
                string value;
                queryString.TryGetValue( key, out value );
                T...(1)
            }
            return default( T );
        }

不过,(1)处应该怎么利用T进行TryParse呢?直接用T是拿不到方法的。但typeof(T)可以拿到T的Type实例,配合反射机制就能取到期望的方法了:

        private static readonly string TRY_PARSE_FUNC = "TryParse";
        public static bool TryParse( this string value, out T result ) {
            result = default( T );

            if ( !string.IsNullOrEmpty( value ) ) {
                Type type = typeof( T );
                BindingFlags flags = BindingFlags.Public | BindingFlags.Static;
                Type[] paramTypes = new Type[] { typeof( string ), type.MakeByRefType() };
                MethodInfo method = type.GetMethod( TRY_PARSE_FUNC, flags, null, paramTypes, null );

                if ( method != null ) {
                    object[] parameters = new object[] { value, null };
                    if ( ( bool )method.Invoke( null, parameters ) ) {
                        result = ( T )parameters[1];
                        return true;
                    }
                }
            }
            return false;
        }

代码比较直观,不做过多解释。使用的时候只要在刚才的方法(1)处加上下面的调用即可:

                T ret;
                if ( value.TryParse( out ret ) ) {
                    return ret;
                }

显然,这种问题不只我碰到,一定会是个群众基础广泛的话题。在Generic TryParse这个提问中,还可以看到其他有趣的解决方案。

最简单粗暴的可以直接:

TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(input);

以及它的变种:直接传入type,不用泛型。试了一下,在Windows Phone平台上,TypeDescriptor拿不到。除此之外,还有将实际的Parse方法作为function delegate传入的做法,视觉上也不差。

如果有兴趣的话,还可以参考这个Mad Reflection C#的系列文章Generic Parsing,共分四部分,除了TryParse<T>之外,还完成了Parse<T>、ParseDefault<T>和Nullable类型Parse的处理,这个话题上可谓总结完整了,推荐一看。