[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 ...
 
随机推荐
- .NET Reflector反编译的方法
			
首先启动.NET Reflector,然后添加进入dll或exe.然后选择Export Source Code...,将反编译后的代码文件,生成到指定目录. 到这一步骤时,稍等一会,就能够在指定目录就 ...
 - jQuery遍历 slice()方法
			
今天做页面,遇到一个滚动的swipe,需要4个<li> 一组,然后在外层加个<ul>,方法如下: $('.xxxxx li').each(function(n){ $('.xx ...
 - 【python】os模块
			
1.os.name 输出字符串指示正在使用的平台.如果是window 则用'nt'表示,对于Linux/Unix用户,它是'posix'. 2.os.getcwd() 函数得到当前工作目录,即当前Py ...
 - 【MySQL】触发器学习
			
MySQL手册中对触发器的定义是: 触发程序是与表有关的命名数据库对象,当表上出现特定事件时,将激活该对象.表必须是永久性表,不能将触发程序与临时表与视图关联起来. 相同触发程序动作时间和事件的给定表 ...
 - 将windows系统装到USB存储设备
			
需求: 1)一般公司比较规范,计算机系统有严格的限制策略,如果自己不懂得如何更改或者没有权限更改,将极其不便. 2)计划在家里完成在公司未完成的事,甚至异地出差觉得携带笔记本不太方便,寻找更便携的设备 ...
 - 使用select io复用实现超时设置
			
在linux的socket编程中,经常会遇到超时设置的问题,例如请求方如果在Ks内不发送数据则服务器要断开连接停止服务.这里我使用select的io复用实现超时5s设置,具体代码片段如下: fd_se ...
 - DPDK内存管理-----(二)rte_mempool内存管理
			
DPDK以两种方式对外提供内存管理方法,一个是rte_mempool,主要用于网卡数据包的收发:一个是rte_malloc,主要为应用程序提供内存使用接口.本文讨论rte_mempool.rte_me ...
 - 线程池(C#)
			
转自:http://blog.sina.com.cn/s/blog_494305f30100ryw7.html 在这里你可以学到Microsoft研究CLR实现线程池的原理机制,从而更灵活的处理CLR ...
 - SDUST 作业10 Problem I 液晶显示
			
Description 你的朋友刚买了一台新电脑,他以前用过的最强大的计算工具是一台袖珍计算器.现在,看着自己的新电脑,他有点失望,因为他更喜欢计算器上的LC显示器.所以,你决定写一个LC显示风格的程 ...
 - 启动hadoop报ERROR org.apache.hadoop.hdfs.server.namenode.FSImage: Failed to load image from FSImageFile
			
不知道怎么回事,今天在启动集群时通过jps查看进程时始终有一个standby namenode进程无法启动.查看日志时报的是不能加载fsimage文件.日志截图如下: 日志报的很明显了是不能加载元数据 ...