.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的处理,这个话题上可谓总结完整了,推荐一看。