[LinqExpression]练习自己写绑定

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

源代码:TypeMapper.zip

背景

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

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

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

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

介绍

先看下效果:

并没什么奇特的。

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

publicForm1()

{

InitializeComponent();vartypeMapper =newcp.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);varmodel =newViewModel()

{

Name="How Are You",

Amount=123456789,

IsValid=true,

Type=3,

CreatedTime=newDateTime(2000,10,10)

};

typeMapper.Map(model,this);

}

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

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

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

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

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

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

实现

注释比较详细,就不多说了。

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

usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Linq.Expressions;usingSystem.Text;usingSystem.Threading.Tasks;namespacecp.lib

{publicclassTypeMapper<T1,T2>whereT1:classwhereT2:class,new()

{#region双向绑定,未实现。///<summary>///(预留属性)是否支持双向绑定。///</summary>publicboolIsTwoWay {get;set; }publicvoidMapBack(T2 targetObj, T1 sourceObj)

{//--todo}publicT1 MapBack(T2 targetObj )

{//--todoreturndefault(T1);

}#endregion//缓存的已经编译好的(从源对象属性到目标对象属性的)赋值语句。privateList<Action<T1,T2>> _assginExpressions=newList<Action<T1,T2>>();publicTypeMapper()

{

}///<summary>///使用字典类型创建一个新TypeMapper实例///</summary>///<param name="mapDictionary"></param>publicTypeMapper(Dictionary<string,string>mapDictionary)

{foreach(variteminmapDictionary)

{

AddMapItem(item.Key, item.Value);

}

}///<summary>///按设置好的映射项将源对象的属性或字段拷贝到目标对象。///</summary>///<param name="sourceObj">源对象</param>///<param name="targetObj">目标对象</param>publicvoidMap(T1 sourceObj,T2 targetObj)

{foreach(varactionin_assginExpressions)

{

action(sourceObj, targetObj);

}

}///<summary>///按设置好的映射项从源对象的属性或字段创建新的目标对象///</summary>///<param name="sourceObj">源对象</param>///<returns>新的目标对象(T2类型)</returns>publicT2 Map(T1 sourceObj)

{if(sourceObj ==null)

{returndefault(T2);

}vartargetObj =newT2();

Map(sourceObj, targetObj);returntargetObj;

}///<summary>///按设置好的映射项将源对象的属性或字段拷贝到目标对象。///</summary>///<param name="sourceObjs">源对象</param>///<param name="targetObjs">目标对象</param>publicvoidMap(IEnumerable<T1> sourceObjs, IEnumerable<T2>targetObjs)

{if(sourceObjs.Count() !=targetObjs.Count())

{thrownewArgumentException("sourceObjs和targetObjs数量不一致!");

}varsourceEmumerator =sourceObjs.GetEnumerator();vartargetEnumerator =targetObjs.GetEnumerator();while(sourceEmumerator.MoveNext())

{

targetEnumerator.MoveNext();varsourceObj =sourceEmumerator.Current;vartargetObj =targetEnumerator.Current;

Map(sourceObj, targetObj);

}

}///<summary>///按设置好的映射项从源对象的属性或字段创建新的目标对象///</summary>///<param name="sourceObj">源对象</param>///<returns>新的目标对象(IEnumerable T2类型)</returns>publicIEnumerable<T2> Map(IEnumerable<T1>sourceObjs)

{foreach(varsourceObjinsourceObjs)

{yieldreturnMap(sourceObj);

}

}///<summary>///增加映射项。映射项以Expression表式,如x=>x.Name。///</summary>///<exception>targetExp不为Memberaccess将引发参数异常。</exception>///<returns>返回当前对象</returns>publicTypeMapper<T1, T2> AddMapItem<TResult>(Expression<Func<T1,TResult>> sourceExp,Expression<Func<T2,TResult>>targetExp)

{if(targetExp.Body.NodeType !=ExpressionType.MemberAccess)

{thrownewArgumentException("targetExp应该为MemberAccess类型!");

}varassignExp =Expression.Assign(targetExp.Body, sourceExp.Body);varlambda = Expression.Lambda<Action<T1, T2>>(assignExp, sourceExp.Parameters[0], targetExp.Parameters[0]);

_assginExpressions.Add(lambda.Compile());returnthis;

}///<summary>///增加映射项。映射项以Expression表式,如x=>x.Name。///</summary>///<exception>targetExp不为MemberAccess将引发参数异常。</exception>///<returns>返回当前对象</returns>publicTypeMapper<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)

{thrownewArgumentException("targetExp应该为MemberAccess类型!");

}varformatExp =Expression.Invoke(formatFunc, sourceExp.Body);varassignExp =Expression.Assign(targetExp.Body, formatExp);varlambda = Expression.Lambda<Action<T1, T2>>(assignExp, sourceExp.Parameters[0], targetExp.Parameters[0]);

_assginExpressions.Add(lambda.Compile());returnthis;

}///<summary>///使用属性(或字段)名增加映射项。///</summary>///<param name="sourceProperty">源对象属性(或字段)名称</param>///<param name="targetProperty">目标对象属性(或字段)名称</param>///<returns>返回当前对象</returns>publicTypeMapper<T1, T2> AddMapItem(stringsourceProperty,stringtargetProperty)

{varparameter1 = Expression.Parameter(typeof(T1),"o1");varmemeber1 =Expression.PropertyOrField(parameter1, sourceProperty);varparameter2 = Expression.Parameter(typeof(T2),"o2");varmemeber2 =Expression.PropertyOrField(parameter2, targetProperty);varassignExp =Expression.Assign(memeber2, memeber1);varlambda = Expression.Lambda<Action<T1, T2>>(assignExp, parameter1, parameter2);

_assginExpressions.Add(lambda.Compile());returnthis;

}///<summary>///按照类型T1,T2中相同名称和类型的属性或字段,自动添加所有映射项。///</summary>///<returns>返回当前项</returns>publicTypeMapper<T1, T2>AutoBuildMap()

{varp1s =typeof(T1).GetProperties();varp2s =typeof(T2).GetProperties();foreach(varp1inp1s)

{foreach(varp2inp2s)

{//目前暂不处理Nullable<int> -> int的映射if(p1.Name == p2.Name && p1.PropertyType ==p2.PropertyType)

{

AddMapItem(p1.Name, p2.Name);

}

}

}varf1s =typeof(T1).GetFields();varf2s =typeof(T2).GetFields();foreach(varf1inf1s)

{foreach(varf2inf2s)

{if(f1.Name == f2.Name && f1.FieldType ==f2.FieldType)

{

AddMapItem(f1.Name, f2.Name);

}

}

}returnthis;

}

}

}

说明

写Linq表达式,就像你在告诉电脑怎么写代码,如谁赋值给谁,调用什么方法,参数是什么,然后是什么。表达式的操作始终返回表达式,最后使用Compile方法,将其转化为委托(function 或 Action)。

Expression<Func<>>和Func<>参数都可以直接传入Lambda表达式,这说明它们之间存在隐式转换。仔细查看Linq中的一些扩展方法的参数类型,虽然它们是Expression,但我们写法还是Func<>的Lambda。将参数声明为Expression,会将表达式视为一个语句,而非方法,可以更方便的操作表达式的元素。

代码中,先手动构建Linq的赋值表达式,然后将其编译为赋值Action缓存起来,表达式编译后其实和下面的Action等同:

AddMapItem(v=>v.Name,form=>form.txtName.Text);||Action<ViewModel,Form1>(

(v,form)=>form.txtName.Text=v.Name

);

AddMapItem(v=>v.Amount,form=>form.txtAmount.Text,a=>a.ToString());||Action<ViewModel,Form1>(

(v,form)=>form.txtAmount.Text=(v.Amount).ToString()

);

使用Linq表达式构建绑定项,比直接使用属性名的方式好一点点: 因为敲v.Name时有智能提示,如果直接输入"Name",比较费时,如果属性长一点还容易出错。属性变了,也享受不到VS自动重构重命名的好处。当然,直接使用属性名添加绑定项更灵活,如果要将规则放在config文件中,就会更有用一些了。

一般而言,反射赋值的性能相对较低。代码中将那些赋值语句以委托的形式缓存,只是为了优化提高一下性能而已。

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航