[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 = ,
IsValid = true,
Type = ,
CreatedTime = new DateTime(, , )
};
typeMapper.Map(model,this);
}
那么,你可能会问了,你写这么长一段“诡异”的代码,有什么好处呢?
1 代码量变少。如果用单纯的赋值语句,和目前代码行数一样。一般而言,我们会实现双向绑定,即界面操作完成后,会把更新的值写回模型,然后重新存储。这意味着那时,你不得不把这些赋值语句反向重一遍。这样写,可以很方便的实现反向赋值。假想20个模型,每个20个属性,可以节省400行的代码量。
2 模块化。模块化后,这块功能有了边界,用以和其它模块交互。一般绑定会和验证,默认值,操作撤销,未保存提醒等结合。单就验证来说,如果加入了验证规则,我们可以在绑定的时候,依次查阅这些规则,自动校验。而不用在页面后重复的“if(xxx.Text==String.Empty{...}”。
3 便于阅读。现在读代码的感觉就是”把xxx的XXx属性绑定到xxx的Text属性上",这比直接读赋值语句要直观一些。虽然赋值语句比较简单,但也要经过人脑的翻译才能形成更直观的自然语言。所以程序风格中,一般提倡方法拆小(方法名可以充当注释)。
4 便于维护。便于维护往往意味着业务改变,代码改变很小甚至不用变。由于现在有专门的类负责在模型和界面间赋值,如果将这些绑定规则外置(放在配置文件中),如果发生改变,那么以后更新配置文件就行了。就算不规则外置,我们也可以把已经模块化的这部分代码放在单独的其它地方(内聚),如果改变绑定,测试也只用测这小部分。
当然,说了那么多,其实我写它的目的比较纯粹:练手,再就是希望能给新鲜码友以帮助。
实现
注释比较详细,就不多说了。
如果要进一步查看怎么使用,可以看下单元测试。
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[], targetExp.Parameters[]);
_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[], targetExp.Parameters[]);
_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表达式,就像你在告诉电脑怎么写代码,如谁赋值给谁,调用什么方法,参数是什么,然后是什么。表达式的操作始终返回表达式,最后使用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文件中,就会更有用一些了。
一般而言,反射赋值的性能相对较低。代码中将那些赋值语句以委托的形式缓存,只是为了优化提高一下性能而已。
[Linq Expression]练习自己写绑定的更多相关文章
- ling join 报错The specified LINQ expression contains references to queries that are associated with different cont
The specified LINQ expression contains references to queries that are associated with different cont ...
- Get Argument Values From Linq Expression
Get Argument Values From Linq Expression If you even find yourself unpacking an expression in C#, yo ...
- C# ORM中Dto Linq Expression 和 数据库Model Linq Expression之间的转换
今天在百度知道中看到一个问题,研究了一会便回答了: http://zhidao.baidu.com/question/920461189016484459.html 如何使dto linq 表达式转换 ...
- C# [LINQ] Linq Expression 获取多格式文件
using System; using System.IO; using System.Linq; using System.Linq.Expressions; internal static str ...
- java 的 linq,不要再写令人讨厌的 for 了!
package com.ly.linq; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator ...
- 查看LINQ Expression編譯後的SQL語法(转)
在用了LINQ語法之後的一個月,我幾乎把SQL語法全部拋到腦後了,不過 LINQ好用歸好用,但是實際上操作資料庫的還是SQL語法,如果不知道LINQ語法 編譯過後產生怎樣的SQL語法,一不小心效能就會 ...
- The specified LINQ expression contains references to queries that are associated with different contexts
今天在改写架构的时候,遇到这么个错误.当时单从字面意思,看上去错误是由join的两个不同的表来源不一致引起的. 其中的videoResult和deerpenList均来自与同一个edmx文件,所以两个 ...
- LinqToDB 源码分析——轻谈Linq查询
LinqToDB框架最大的优势应该是实现了对Linq的支持.如果少了这一个功能相信他在使用上的快感会少了一个层次.本来笔者想要直接讲解LinqToDB框架是如何实现对Linq的支持.写到一半的时候却发 ...
- 编写高质量代码改善C#程序的157个建议[IEnumerable<T>和IQueryable<T>、LINQ避免迭代、LINQ替代迭代]
前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议29.区别LINQ查询中的IEnumerable<T ...
随机推荐
- ansible 访问内网服务器
ssh https://medium.com/@paulskarseth/ansible-bastion-host-proxycommand-e6946c945d30#.rauzlfv0z http: ...
- play framework 框架安装及myeclipse 导入项目
下载 play framework 框架. 解压你你要解压的目录 E:\play-1.2.7 相对其他的WEB框架.play的配置是相当简单的.没有那么多配置文件的搞法.上手比较快,就是相关的资料比较 ...
- IIS URL重写找不到页面 (URLRewriter.dll伪静态)
在网站上点右键 属性 进入主目录菜单 点击配置 找到.html扩展名 编辑 将 检查文件是否存在 的钩去掉! OK
- HTML 5缓存机制:Cache Manifest配置实例
Cache Manifest是HTML 5的一种缓存机制,文章作者直接用博客当测试环境,虽然[color=#444444 !important]应用起来非常简单,但效果却出奇的好.缓存后的速度,简直是 ...
- Windowsphone本地应用信息与市场信息的获取
本地信息都存放在 WMAppManifest 里面,获取就不用说了...知道位置 就知道怎么获取了.. 主要是讲那个 市场上面的详情怎么获取,就是API调用显示在这个页面里面的详情: public v ...
- HTML 表单总结http://images2015.cnblogs.com/blog/1001203/201607/1001203-20160730200559841-2144892373.png
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- nodejs前端自动化构建
http://99jty.com/?p=1257 http://www.jankerli.com/?p=1628 http://www.cnblogs.com/zhepama/archive/2013 ...
- Winform开发常用控件之Checkbox和CheckedListBox
Winform的开发基本都是基于控件事件的,也就是事件驱动型的. 多选框的放置和值的获取有很多种,这里介绍几个简单常用的方法 1.直接放置Checkbox,并获取Checkbox的值 上图 做法也非常 ...
- wcf调用oracle存储过程
public IList<ACCP_RAIN> QueryAll(string beginTime, string endTime, string type) { beginTime = ...
- Centos 7配置ntp时间同步
1.NTP时钟同步方式说明 NTP在linux下有两种时钟同步方式,分别为直接同步和平滑同步: 1)直接同步 使用ntpdate命令进行同步,直接进行时间变更.如果服务器上存在一个1 ...