源代码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]练习自己写绑定的更多相关文章

  1. 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 ...

  2. Get Argument Values From Linq Expression

    Get Argument Values From Linq Expression If you even find yourself unpacking an expression in C#, yo ...

  3. C# ORM中Dto Linq Expression 和 数据库Model Linq Expression之间的转换

    今天在百度知道中看到一个问题,研究了一会便回答了: http://zhidao.baidu.com/question/920461189016484459.html 如何使dto linq 表达式转换 ...

  4. C# [LINQ] Linq Expression 获取多格式文件

    using System; using System.IO; using System.Linq; using System.Linq.Expressions; internal static str ...

  5. java 的 linq,不要再写令人讨厌的 for 了!

    package com.ly.linq; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator ...

  6. 查看LINQ Expression編譯後的SQL語法(转)

    在用了LINQ語法之後的一個月,我幾乎把SQL語法全部拋到腦後了,不過 LINQ好用歸好用,但是實際上操作資料庫的還是SQL語法,如果不知道LINQ語法 編譯過後產生怎樣的SQL語法,一不小心效能就會 ...

  7. The specified LINQ expression contains references to queries that are associated with different contexts

    今天在改写架构的时候,遇到这么个错误.当时单从字面意思,看上去错误是由join的两个不同的表来源不一致引起的. 其中的videoResult和deerpenList均来自与同一个edmx文件,所以两个 ...

  8. LinqToDB 源码分析——轻谈Linq查询

    LinqToDB框架最大的优势应该是实现了对Linq的支持.如果少了这一个功能相信他在使用上的快感会少了一个层次.本来笔者想要直接讲解LinqToDB框架是如何实现对Linq的支持.写到一半的时候却发 ...

  9. 编写高质量代码改善C#程序的157个建议[IEnumerable<T>和IQueryable<T>、LINQ避免迭代、LINQ替代迭代]

    前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议29.区别LINQ查询中的IEnumerable<T ...

随机推荐

  1. Javascript中的Bind 、Call和Apply

    看以下代码: var bind = Function.prototype.call.bind(Function.prototype.bind); 第一眼看上去,我能猜出它究竟是用来做什么的.它把x.y ...

  2. 【drp 11】使用Junit简单测试接口方法

    一.Junit简介 JUnit是一个Java语言的单元测试框架.它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个. JUn ...

  3. MongoDB 3: 使用中的问题,及其应用场景

    导读:用MongoDB去存储非关系型的数据,是一个比较正确的选择.但是,如果只是用MongoDB,那么也会出现一些问题.MongoDB,尤其使用的最佳场景,更多的时候,需要结合关系型数据库共同解决问题 ...

  4. MFC ComboBox的使用

    前言 Combo Box (组合框)控件很简单,可以节省空间.从用户角度来看,这个控件是由一个文本输入控件和一个下拉菜单组成的.用户可以从一个预先定义的列表里选择一个选项,同时也可以直接在文本框里面输 ...

  5. namespace的用法

    C++中采用的是单一的全局变量命名空间.在这单一的空间中,如果有两个变量或函数的名字完全相同,就会出现冲突.当然,你也可以使用不同的名字,但有时我们并不知道另一个变量也使用完全相同的名字:有时为了程序 ...

  6. 4种kill某个用户所有进程的方法

    在linux系统管理中,我们有时候需要kill掉某个用户的所有进程,初学者一般先查询出用户的所有pid,然后一条条kill掉,或者写好一个脚本,实际上方法都有现成的,这边有4种方法,我们以kill用户 ...

  7. leetcode 104

    104. Maximum Depth of Binary Tree Given a binary tree, find its maximum depth. The maximum depth is ...

  8. Android IOS WebRTC 音视频开发总结(十四)-- sip和xmpp异同

    这篇文章主要介绍XMPP与SIP,很多人容易混淆这两个概念,转载请说明出处(博客园RTC.Blacker). 简介:XMPP和SIP都是应用层协议,主要用于互联网上发送语音和即时通讯. SIP在RFC ...

  9. projecteuler Smallest multiple

    2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any rema ...

  10. Jquery获取selelct选中值

    误区: 一直以为jquery获取select中option被选中的文本值,是这样写的: $("#s").text();  //获取所有option的文本值 实际上应该这样: $(& ...