首页 > 代码库 > [Linq Expression]练习自己写绑定

[Linq Expression]练习自己写绑定

源代码:TypeMapper.zip

 

背景

  项目中,我们会经常用到各种赋值语句,比如把模型的属性赋值给UI,把视图模型的属性拷贝给Entity。如果模型属性太多,赋值也会变成苦力活。所以,框架编程的思维中,出现了”绑定“。绑定不仅可以简化赋值,还可以结合验证,简化绑定过程中的验证。

  能实现绑定的框架很多,如AutoMapper,.Net自带的绑定机制,微软官方上还有一个利用绑定的Sample,等。

  那些成熟的框架一般功能全面,考虑周全,一般推荐首选。但对于一些小项目个别情况,或许它们就会显得有些笨重了。前些时候读Demo研究了一下Linq Expression , 自己写一个,练练手巩固下知识吧。

  代码主要使用了基本的LinqExpression中的赋值操作,代码也较短,可以作为学习示例也可以作为工具使用(可能要加一些功能了)。

 

介绍

  先看下效果:

  并没什么奇特的。

  再看看后台,没有赋值语句,只是简单告诉程序:谁该绑定给谁,怎么绑而已:

 public Form1()        {            InitializeComponent();            var typeMapper = new cp.lib.TypeMapper<ViewModel, Form1>()                .AddMapItem(vm => vm.Name, form => form.txtName.Text)                .AddMapItem(vm => vm.Amount, form => form.txtAmount.Text, a => a.ToString("#,###"))                .AddMapItem(vm => vm.IsValid, form => form.chkIsValid.Checked)                .AddMapItem(vm => vm.Type, form => form.cboType.SelectedIndex)                .AddMapItem(vm => vm.CreatedTime, form => form.dtpCreateTime.Value);            var model = new ViewModel()            {                Name = "How Are You",                Amount = 123456789,                IsValid = true,                Type = 3,                CreatedTime = new DateTime(2000, 10, 10)            };            typeMapper.Map(model,this);        }

  那么,你可能会问了,你写这么长一段“诡异”的代码,有什么好处呢?

  1 代码量变少。如果用单纯的赋值语句,和目前代码行数一样。一般而言,我们会实现双向绑定,即界面操作完成后,会把更新的值写回模型,然后重新存储。这意味着那时,你不得不把这些赋值语句反向重一遍。这样写,可以很方便的实现反向赋值。假想20个模型,每个20个属性,可以节省400行的代码量。

  2 模块化。模块化后,这块功能有了边界,用以和其它模块交互。一般绑定会和验证,默认值,操作撤销,未保存提醒等结合。单就验证来说,如果加入了验证规则,我们可以在绑定的时候,依次查阅这些规则,自动校验。而不用在页面后重复的“if(xxx.Text==String.Empty{...}”。

  3 便于阅读。现在读代码的感觉就是”把xxx的XXx属性绑定到xxx的Text属性上",这比直接读赋值语句要直观一些。虽然赋值语句比较简单,但也要经过人脑的翻译才能形成更直观的自然语言。所以程序风格中,一般提倡方法拆小(方法名可以充当注释)。

  4 便于维护。便于维护往往意味着业务改变,代码改变很小甚至不用变。由于现在有专门的类负责在模型和界面间赋值,如果将这些绑定规则外置(放在配置文件中),如果发生改变,那么以后更新配置文件就行了。就算不规则外置,我们也可以把已经模块化的这部分代码放在单独的其它地方(内聚),如果改变绑定,测试也只用测这小部分。

  当然,说了那么多,其实我写它的目的比较纯粹:练手,再就是希望能给新鲜码友以帮助。

 

实现

  注释比较详细,就不多说了。如有不懂,可查阅MSDN或留言。如果硬要说明的话,那就是:以缓存委托的形式进行绑定,性能会比反射好一点点~。

  如果要进一步查看怎么使用,可以看下单元测试。

using System;using System.Collections.Generic;using System.Linq;using System.Linq.Expressions;using System.Text;using System.Threading.Tasks;namespace cp.lib{    public class TypeMapper<T1,T2>        where T1:class        where T2:class,new()    {        #region 双向绑定,未实现。        /// <summary>        /// (预留属性)是否支持双向绑定。        /// </summary>        public bool IsTwoWay { get; set; }        public void MapBack(T2 targetObj, T1 sourceObj)        {            //--todo        }        public T1 MapBack(T2 targetObj )        {            //--todo            return default(T1);        }         #endregion        //缓存的已经编译好的(从源对象属性到目标对象属性的)赋值语句。        private List<Action<T1,T2>> _assginExpressions=new List<Action<T1,T2>>();        public TypeMapper()        {        }        /// <summary>        /// 使用字典类型创建一个新TypeMapper实例        /// </summary>        /// <param name="mapDictionary"></param>        public TypeMapper(Dictionary<string,string> mapDictionary)        {            foreach (var item in mapDictionary)            {                AddMapItem(item.Key, item.Value);            }        }        /// <summary>        /// 按设置好的映射项将源对象的属性或字段拷贝到目标对象。        /// </summary>        /// <param name="sourceObj">源对象</param>        /// <param name="targetObj">目标对象</param>        public void Map(T1 sourceObj,T2 targetObj)        {             foreach (var action in _assginExpressions)            {                action(sourceObj, targetObj);            }         }        /// <summary>        /// 按设置好的映射项从源对象的属性或字段创建新的目标对象        /// </summary>        /// <param name="sourceObj">源对象</param>        /// <returns>新的目标对象(T2类型)</returns>        public T2 Map(T1 sourceObj)        {            if (sourceObj == null)            {                return default(T2);            }            var targetObj = new T2();            Map(sourceObj, targetObj);            return targetObj;        }        /// <summary>        /// 按设置好的映射项将源对象的属性或字段拷贝到目标对象。        /// </summary>        /// <param name="sourceObjs">源对象</param>        /// <param name="targetObjs">目标对象</param>        public void Map(IEnumerable<T1> sourceObjs, IEnumerable<T2> targetObjs)        {            if (sourceObjs.Count() != targetObjs.Count())            {                throw new ArgumentException("sourceObjs和targetObjs数量不一致!");            }            var sourceEmumerator = sourceObjs.GetEnumerator();            var targetEnumerator = targetObjs.GetEnumerator();            while (sourceEmumerator.MoveNext())            {                targetEnumerator.MoveNext();                var sourceObj = sourceEmumerator.Current;                var targetObj = targetEnumerator.Current;                Map(sourceObj, targetObj);            }        }        /// <summary>        /// 按设置好的映射项从源对象的属性或字段创建新的目标对象        /// </summary>        /// <param name="sourceObj">源对象</param>        /// <returns>新的目标对象(IEnumerable T2类型)</returns>        public IEnumerable<T2> Map(IEnumerable<T1> sourceObjs)        {            foreach (var sourceObj in sourceObjs)            {                yield return Map(sourceObj);            }        }        /// <summary>        /// 增加映射项。映射项以Expression表式,如x=>x.Name。        /// </summary>         /// <exception>targetExp不为MemberAccess将引发参数异常。</exception>        /// <returns>返回当前对象</returns>        public TypeMapper<T1, T2> AddMapItem<TResult>(Expression<Func<T1,TResult>> sourceExp,Expression<Func<T2,TResult>> targetExp)        {            if (targetExp.Body.NodeType != ExpressionType.MemberAccess)            {                throw new ArgumentException("targetExp应该为MemberAccess类型!");            }            var assignExp = Expression.Assign(targetExp.Body, sourceExp.Body);            var lambda = Expression.Lambda<Action<T1, T2>>(assignExp, sourceExp.Parameters[0], targetExp.Parameters[0]);            _assginExpressions.Add(lambda.Compile());            return this;        }        /// <summary>        /// 增加映射项。映射项以Expression表式,如x=>x.Name。        /// </summary>         /// <exception>targetExp不为MemberAccess将引发参数异常。</exception>        /// <returns>返回当前对象</returns>        public TypeMapper<T1, T2> AddMapItem<TResult1, TResult2>(Expression<Func<T1, TResult1>> sourceExp, 
Expression<Func<T2, TResult2>> targetExp, Expression<Func<TResult1, TResult2>> formatFunc) { if (targetExp.Body.NodeType != ExpressionType.MemberAccess) { throw new ArgumentException("targetExp应该为MemberAccess类型!"); } var formatExp = Expression.Invoke(formatFunc, sourceExp.Body); var assignExp = Expression.Assign(targetExp.Body, formatExp); var lambda = Expression.Lambda<Action<T1, T2>>(assignExp, sourceExp.Parameters[0], targetExp.Parameters[0]); _assginExpressions.Add(lambda.Compile()); return this; } /// <summary> /// 使用属性(或字段)名增加映射项。 /// </summary> /// <param name="sourceProperty">源对象属性(或字段)名称</param> /// <param name="targetProperty">目标对象属性(或字段)名称</param> /// <returns>返回当前对象</returns> public TypeMapper<T1, T2> AddMapItem(string sourceProperty,string targetProperty) { var parameter1 = Expression.Parameter(typeof(T1),"o1"); var memeber1 = Expression.PropertyOrField(parameter1, sourceProperty); var parameter2 = Expression.Parameter(typeof(T2), "o2"); var memeber2 = Expression.PropertyOrField(parameter2, targetProperty); var assignExp = Expression.Assign(memeber2, memeber1); var lambda = Expression.Lambda<Action<T1, T2>>(assignExp, parameter1, parameter2); _assginExpressions.Add(lambda.Compile()); return this; } /// <summary> /// 按照类型T1,T2中相同名称和类型的属性或字段,自动添加所有映射项。 /// </summary> /// <returns>返回当前项</returns> public TypeMapper<T1, T2> AutoBuildMap() { var p1s = typeof(T1).GetProperties(); var p2s = typeof(T2).GetProperties(); foreach (var p1 in p1s) { foreach (var p2 in p2s) { //目前暂不处理Nullable<int> -> int的映射 if (p1.Name == p2.Name && p1.PropertyType == p2.PropertyType) { AddMapItem(p1.Name, p2.Name); } } } var f1s = typeof(T1).GetFields(); var f2s = typeof(T2).GetFields(); foreach (var f1 in f1s) { foreach (var f2 in f2s) { if (f1.Name == f2.Name && f1.FieldType == f2.FieldType) { AddMapItem(f1.Name, f2.Name); } } } return this; } }}

 

后话

  学而时习之。。。

 

[Linq Expression]练习自己写绑定