[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 ...
随机推荐
- java map缓存
/** * 缓存池 * @author xiaoquan * @create 2015年3月13日 上午10:32:13 * @see */ public class CachePool { ...
- 新写的c++日志库:log4K
网是开源的c/c++日志库也不少,但用起来总觉得不方便,于是动手写了一个C++日志框架Log4K. 测试代码: #include "log4k.h" #pragma comment ...
- MITM to crack Https connections
Everybody knows that https is http over SSL, and https is a secure way for protecting confidential d ...
- XML处理
//生成XML XmlDocument xmlDoc = new XmlDocument(); XmlElement root = xmlDoc.CreateElement("Data&qu ...
- Android IOS WebRTC 音视频开发总结(五四)-- WebRTC标准之父谈WebRTC
本文主要是整理自国内首届WebRTC大会上对Daniel的一些专访,转载必须说明出处,欢迎关注微信公众号blacker,更多说明详见www.rtc.help 说明:以下内容主要整理自InfoQ的专访, ...
- 十一、Struts2封装请求参数的方式
十一.Struts2封装请求参数的方式 方式一.Action 本身作为model对象,通过成员setter封装(一个名字为params的拦截器干的) 注意:表单中的名称要和动作类中的名称一致(这是必须 ...
- 魔兽塔防游戏android源码
魔兽塔防是一款经典的游戏,当年在pc机器上玩过魔兽的人应该都玩过类似的游戏,他仿照魔兽,建塔拦截敌人入侵,发挥你的智慧让敌人走最远的路,将他们消灭在路上.... 源码下载:http://code.66 ...
- memcache和memcahced的区别
Memcache是什么?Memcache是一个自由和开放源代码.高性能.分配的内存对象缓存系统.用于加速动态web应用程序,减轻数据库负载.它可以应对任意多个连接,使用非阻塞的网络IO.由于它的工作机 ...
- WAMP环境下访问PHP提示下载PHP文件
原因是服务器没有加载到PHP文件 到http.conf下加载 AddType application/x-httpd-php .php AddType application/x-httpd-php ...
- js日期格式化方法 dateFormatFn
var dateFormatFn=function(val,fmt){ var _this = new Date(val); console.log(_this,_this.getFullYear() ...