如何简化重复如果然后分配build设?

我有以下的方法:

protected override bool ModifyExistingEntity(Product entity, ProductModel item) { bool isModified = false; if (entity.Title != item.Title) { isModified = true; entity.Title = item.Title; } if (entity.ServerId != item.Id) { isModified = true; entity.ServerId = item.Id; } return isModified; } 

我想知道你是否可以提出一个更好的方法来实现这个方法。

问题是显而易见的:每个属性的5行几乎复制粘贴的代码太多了。 可能有一个解决scheme使用Func -s / Expression -s超出了我的视野。

你有一个时间耦合的情况,即混合检查实体是否随着赋值而改变。 如果你把这两者分开,你的代码变得更清洁:

 protected override bool ModifyExistingEntity(Product entity, ProductModel item) { bool isModified = this.IsEntityModified(entity, item); if (isModified) { this.UpdateEntity(entity, item); } return isModified; } private bool IsEntityModified(Product entity, ProductModel item) { return entity.Title != item.Title || entity.ServerId != item.ServerId; } private void UpdateEntity(Product entity, ProductModel item) { entity.Title = item.Title; entity.ServerId = item.Id; } 

Func<>或类似的东西做任何聪明和时髦的东西(TM)在这种情况下似乎没有什么帮助,因为它不会清楚地expression你的意图。

像这样的东西应该工作

 protected bool ModifyExistingEntity(Person entity, ProductModel item) { bool isModified = CompareAndModify(() => entity.Title = item.Title, () => entity.Title != item.Title); isModified |= CompareAndModify(() => entity.ServerId = item.Id, () => entity.ServerId != item.Id); return isModified; } private bool CompareAndModify(Action setter, Func<bool> comparator) { if (comparator()) { setter(); return true; } return false; } 

不知道这是否可读。 这是主观的。

我认为这个答案的扩展可能适用于你:

 public static bool SetIfModified<CLeft, T>(Expression<Func<CLeft, T>> exprLeft, CLeft leftType, T rightValue) { var getterLeft = exprLeft.Compile(); if (EqualityComparer<T>.Default.Equals(getterLeft(leftType), rightValue)) { var newValueLeft = Expression.Parameter(exprLeft.Body.Type); var assignLeft = Expression.Lambda<Action<CLeft, T>>(Expression.Assign(exprLeft.Body, newValueLeft), exprLeft.Parameters[0], newValueLeft); var setterLeft = assignLeft.Compile(); setterLeft(leftType, rightValue); return true; } else { return false; } } 

它需要一个expression式来检查值。 它编译并dynamic执行它。

像这样使用它:

 public class Product { public string Title { get; set; } } public class ProductModel { public string Title { get; set; } } static void Main(string[] args) { Product lc = new Product(); ProductModel rc = new ProductModel(); rc.Title = "abc"; bool modified = SetIfModified(l => l.Title, lc, r.Title); // modified is true // lc.Title is "abc" } 

使用T4进行元编程

另一种方法 – 通常当我们重复代码实际上很简单 ,可能很快 。 在这种情况下,每个重复的块if不一样 – 它拥有一些知识 – 从一个属性到另一个属性的映射。

编写维护重复块是很烦人的。
避免编写有用的重复代码的一种方法是自动生成它。

用我的解决scheme,映射是直截了当的:

 var mappings = new []{ new Mapper("ProductModel", "Product") { "Title", // ProductModel.Title goes to Product.Title {"Id", "ServiceId"}, // ProductModel.Id goes to Product.ServiceId }, }; 

这是一个t4文本模板(Visual Studio的内置function):

 <#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> <# // Consider including the namespace in the class names. // You only need to change the mappings. var product = new Mapper("Product", "ProductEntity") { "Name", {"Id", "ServiceId"} }; var person = new Mapper("Person", "DbPerson") { "Employee", {"Name", "FullName"}, {"Addredd", "HomeAddress"} }; var mappings = new [] {product, person}; #> // !!! // !!! Do not modify this file, it is automatically generated. Change the .tt file instead. !!! // !!! namespace Your.Mapper { partial class Mapper { <# foreach(var mapping in mappings) { #>/// <summary> /// Set <paramref name="target"/> properties by copying them from <paramref name="source"/>. /// </summary> /// <remarks>Mapping:<br/> <#foreach(var property in mapping){ #>/// <see cref="<#=mapping.SourceType#>.<#=property.SourceProperty#>"/> → <see cref="<#=mapping.TargetType#>.<#=property.TargetProperty#>"/> <br/> <#} #>/// </remarks> /// <returns><c>true</c> if any property was changed, <c>false</c> if all properties were the same.</returns> public bool ModifyExistingEntity(<#=mapping.SourceType#> source, <#=mapping.TargetType#> target) { bool dirty = false; <# foreach(var property in mapping) { #>if (target.<#=property.TargetProperty#> != source.<#=property.SourceProperty#>) { dirty = true; target.<#=property.TargetProperty#> = source.<#=property.SourceProperty#>; } <#} #>return dirty; } <# } #> } } <#+ class Mapper : IEnumerable<PropertyMapper> { private readonly List<PropertyMapper> _properties; public Mapper(string sourceType, string targetType) { SourceType = sourceType; TargetType = targetType; _properties = new List<PropertyMapper>(); } public string SourceType { get; set; } public string TargetType { get; set; } public void Add(string fieldName) { _properties.Add(new PropertyMapper {SourceProperty = fieldName, TargetProperty = fieldName}); } public void Add(string sourceProperty, string targetProperty) { _properties.Add(new PropertyMapper { SourceProperty = sourceProperty, TargetProperty = targetProperty }); } IEnumerator<PropertyMapper> IEnumerable<PropertyMapper>.GetEnumerator() { return _properties.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return _properties.GetEnumerator(); } } class PropertyMapper { public string SourceProperty { get; set; } public string TargetProperty { get; set; } } #> 

该模板生成以下代码: https : //gist.github.com/kobi/d52dd1ff27541acaae10

优点:

  • 繁重的工作是在编译时完成的(实际上在编译之前一次) – 生成的代码很快。
  • 生成的代码被logging。
  • 易于维护 – 您可以在一个点上更改所有的映射器。
  • 生成的方法logging在案。
  • 没有复制粘贴错误 。
  • 这很有趣。

缺点:

  • 使用string来获取属性名称。 请记住 – 这不是生产代码 ,它只是用来生成代码。 可以使用真实的types和expression式树( 下面的例子 )。
  • 静态分析可能会错过模板中的使用(即使我们使用expression式,并不是所有的工具都会查看tt文件)。
  • 许多人不知道发生了什么事。
  • 如果您使用expression式,引用您的types是棘手的。

笔记:

  • 我已经命名了参数sourcetarget ,并且改变了它们的顺序,所以source始终是第一位的。

有一些担心,我使用string,而不是真正的属性。 虽然在这种情况下这是一个小问题(输出被编译),但是这里还有一个与你的真实对象一起工作的附加。

在顶部,添加(第三个应该是你的名字空间):

 <#@ assembly name="$(TargetPath)" #> <#@ import namespace="System.Linq.Expressions" #> <#@ import namespace="ConsoleApplicationT4So29913514" #> 

在底部添加:

 class Mapper<TSource, TTarget> : Mapper { public Mapper() : base(typeof(TSource).FullName, typeof(TTarget).FullName) { } private static string GetExpressionMemberAccess(LambdaExpression getProperty) { var member = (MemberExpression)getProperty.Body; //var lambdaParameterName = (ParameterExpression)member.Expression; var lambdaParameterName = getProperty.Parameters[0]; // `x` in `x => x.PropertyName` var labmdaBody = member.ToString(); //will not work with indexer. return labmdaBody.Substring(lambdaParameterName.Name.Length + 1); //+1 to remove the `.`, get "PropertyName" } public void Add<TProperty>(Expression<Func<TSource, TProperty>> getSourceProperty, Expression<Func<TTarget, TProperty>> getTargetProperty) { Add(GetExpressionMemberAccess(getSourceProperty), GetExpressionMemberAccess(getTargetProperty)); } /// <summary> /// The doesn't really make sense, but we assume we have <c>source=>source.Property</c>, <c>target=>target.Property</c> /// </summary> public void Add<TProperty>(Expression<Func<TSource, TProperty>> getProperty) { Add(GetExpressionMemberAccess(getProperty)); } } 

用法:

 var mappings = new Mapper[] { new Mapper<Student,StudentRecord> { {s=>s.Title, t=>t.EntityTitle}, {s=>s.StudentId, t=>t.Id}, s=>s.Name, s=>s.LuckyNumber, }, new Mapper<Car,RaceCar> { c=>c.Color, c=>c.Driver, {c=>c.Driver.Length, r=>r.DriverNameDisplayWidth}, }, }; 

整个文件应该是这样的: https : //gist.github.com/kobi/6423eaa13cca238447a8
输出仍然看起来一样: https : //gist.github.com/kobi/3508e9f5522a13e1b66b

笔记:

  • expression式只用于获取属性名称作为string,我们不编译它们或运行它们。
  • 在C#6中,我们将使用nameof()运算符 ,这是expression式和​​非魔术string之间的一个很好的折衷。

没有魔术棒来简化这一点。

您可以让实体本身提供一个IsModified属性,然后由属性设置器进行设置,例如:

 public string Title { get { return _title; } set { if (value != _title) { _title = value; IsModified = true; } } } 

如果这是太多的工作,你的解决scheme是好的。

如果你想使它可读,你可以创build一个真正简单的用法,避免重复代码:

 protected override bool ModifyExistingEntity(Product entity, ProductModel item) { return new Modifier<Product>(entity) .SetIfNeeded(e => e.Title, item.Title); .SetIfNeeded(e => e.ServerId, item.Id); .EntityWasModified; } 

执行:

我从帕特里克·霍夫曼(Patrick Hofman)那里拿了一些代码来从getterexpression式中产生一个setter。

 public class Modifier<TEntity> { public Modifier(TEntity entity) { Entity = entity; } public TEntity Entity { get; private set; } public bool EntityWasModified { get; private set; } public Modifier<TEntity> SetIfNeeded<TProperty>(Expression<Func<TEntity, TProperty>> entityPropertyGetter, TProperty modelValue) { var getter = entityPropertyGetter.Compile(); var setter = GetSetterExpression(entityPropertyGetter).Compile(); if (!object.Equals(getter(Entity), modelValue)) { setter(Entity, modelValue); EntityWasModified = true; } return this; } private static Expression<Action<TEntity, TProperty>> GetSetterExpression(Expression<Func<TEntity, TProperty>> getterExpression) { var newValue = Expression.Parameter(getterExpression.Body.Type); return Expression.Lambda<Action<TEntity, TProperty>>( Expression.Assign(getterExpression.Body, newValue), getterExpression.Parameters[0], newValue); } } 

您可能需要caching.Compile的结果以提高性能。

看看我自己的(几乎和别人一样怪)解决scheme

 [TestMethod] public void DifferentTitleAndId_ExpectModified() { var entity = new Product { Id = 0, ServerId = 0, Title = "entity title" }; var model = new ProductModel { Id = 1, Title = "model title" }; bool isModified = ModifyExistingEntity(entity, model); Assert.IsTrue(isModified); } protected bool ModifyExistingEntity(Product entity, ProductModel model) { return IsModified(entity.Title, model.Title, x => entity.Title = x) | IsModified(entity.ServerId, model.Id, x => entity.ServerId = x); } protected bool IsModified<T>(T value1, T value2, Action<T> setter) { return IsModified(() => value1, () => value2, () => setter(value2)); } protected bool IsModified<T>(Func<T> valueGetter1, Func<T> valueGetter2, Action setter) { if (!Equals(valueGetter1(), valueGetter2())) { setter(); return true; } return false; } 

我已经看到了这个问题的最复杂的答案,但我认为你会最适合用一个相当简单,毫无意义的简单解决scheme。

我假设您在代码库中使用某种数据映射器模式,而Product是您的DAL / Domain实体,ProductModel是您的应用程序级对象。 在这种情况下,我只需要一个比较两者的方法(稍后可以将它们移动到一个单独的图层上),如果它们不相等,则可以使用map。

但是这提出了一个问题,你为什么只是在更新的时候担心,如果它改变了? 每次更新都可能是可以接受的。

另外,你也许不应该将一个实体传递给一个方法,期望它被更新。

我会改变逻辑如下:

 protected bool UpdateIfChanged(Product entity, ProductModel item) { var areEqual = CompareProductAndProductModel(entity, item); if(!areEqual) UpdateProduct(MapProductModelToProduct(item)); return !areEqual; } internal bool CompareProductAndProductModel(Product product, ProductModel productModel) { return product.Title == productModel.Title && product.ServerId == productModel.Id; //could be abstracted to an equality comparer if you were inclined } 

这个答案与其他答案最大的不同在于它不修改产品实体。 而是比较ProductProductModel ,但是如果检测到更改,则会使用ProductModel创build一个新的 Product ,然后将其传递给实际执行更新工作的另一个DAL方法。 我相信这可能是最可维护的方法,因为您不必处理更改传递给它们的对象状态的方法(即使方法和调用方存在于不同的地方也是隐式的耦合),这意味着您不必在debugging过程中逐步执行代码时,会在心理上跟踪对实体的状态更改。

“更好”在这方面是主观的。 既然你是在抱怨行数,我有一个更简洁的方法:

 protected override bool ModifyExistingEntity(Product entity, ProductModel item) { bool isModified = false; isModified |= (entity.Title!= item.Title) ? (entity.Title = item.Title) == item.Title : false; isModified |= (entity.ServerId != item.Id) ? (entity.ServerId = item.Id) == item.Id : false; return isModified; } 

继续@bstenzel答案,不应该这样做的伎俩?

 protected override bool ModifyExistingEntity(Product entity, ProductModel item) { bool isEntityModified = entity.Title != item.Title || entity.ServerId != item.ServerId; entity.Title = item.Title; entity.ServerId = item.Id; return isEntityModified; } 

清洁和简单。

我想你正在寻找一种方法来比较两个对象的属性的内容。 您的示例包含两个属性,但我希望您的真实代码包含更多(作为Product实体可能有很多属性)。

你应该先写一个比较你的两个对象的方法。 为了您的参考,这里是关于这个问题的一些SO问题:

  1. 正确实施不同types但语义上相同的两个对象的比较
  2. 比较c#中的对象属性

你的方法看起来像:

 public static bool IsEqualTo<TSource>(this TSource sourceObj, TDestination destinationObj) where TSource : class where TDestination : class { // Your comparison code goes here } 

然后你将不得不编写第二个方法来在你的对象之间复制数据。 这个问题可以引导你通过(检查Marc回答):

  1. 如何在C#.NET中深度复制不同types的对象

你的方法看起来像:

 public static bool CopyDataTo<TSource>(this TSource sourceObj, TDestination destinationObj) where TSource : class where TDestination : class { // Your data copy code goes here } 

你最终的代码看起来就像

 protected override bool ModifyExistingEntity(Product entity, ProductModel item) { if (!entity.IsEqualTo(item)) { item.CopyDataTo(entity); return true; } return false; } 

我不确定Product类是否可以扩展,如果你正在寻找一个“花哨的”答案,或者只是一个简单的答案,我也不确定…但如果是这样,你可以做一些事情, Product类中的逻辑; 恕我直言,你最终有一个相当可读的方法:

 protected override bool ModifyExistingEntity(Product entity, ProductModel item) { entity.SetTitle(item.Title); entity.SetServerId(item.Id); return entity.WasModified(); } 

额外的好处是,你巧妙地将行为封装到Product (以及validation等)

 public partial class Product { public void SetTitle(string title) { if(this.Title!=title) //and other validation, etc { this.Title = title; Modified(); } } public void SetServerId(int serverId) { if(this.ServerId!=serverId) { this.ServerId=serverID; Modified(); } } private bool _wasModified; private void Modified() { //Or implement INotifyPropertyChanged if you like _wasModified=true; } public bool WasModified() { return _wasModified; } } 

当然,如果你不需要任何“业务逻辑”,这实际上只是一个未经检查的映射操作,这里任何一个非常聪明的答案都可以:

如果你只是想要更短的代码行,你可以把它压缩到这个C风格的分配:

 ModifyExistingEntity( Product entity, ProductModel item ) { bool isModified = false; isModified |= ( entity.Title != item.Title ) && retTrue( entity.Title = item.Title ); isModified |= ( entity.ServerId != item.Id ) && retTrue( entity.ServerId = item.Id ); return isModified; } static bool RetTrue<T>(T dummy) { return true; } // Helper method. 

由于您很可能不想为每个实体和模型对编写代码,因此您应该依靠reflection来映射实体和模型之间相同的属性,然后将这些值与属性Infos进行比较。

编辑:添加修改后的值的复制和locking多个线程。

 class Program { static void Main(string[] args) { if (ModifyExistingEntity(new Product { Name = "bar" }, new ProductModel { Name = "test" })) Console.WriteLine("Product modified"); if (ModifyExistingEntity(new Customer { Number = 1001 }, new CustomerModel { Number = 1002 })) Console.WriteLine("Customer was modified"); if (!ModifyExistingEntity(new Customer { Number = 1001 }, new CustomerModel { Number = 1001 })) Console.WriteLine("Customer was not modified"); Console.ReadKey(); } protected static bool ModifyExistingEntity<TEntity, TModel>(TEntity entity, TModel model) { var isModified = false; GetProperties(entity, model).ForEach( propertyInfo => { var item2Value = propertyInfo.Item2.GetValue(model, null); if (Equals(propertyInfo.Item1.GetValue(entity, null), item2Value)) return; propertyInfo.Item1.SetValue(entity, item2Value); isModified = true; }); return isModified; } private static readonly object MappingLock = new object(); private static readonly Dictionary<Tuple<Type, Type>, List<Tuple<PropertyInfo, PropertyInfo>>> Mapping = new Dictionary<Tuple<Type, Type>, List<Tuple<PropertyInfo, PropertyInfo>>>(); protected static List<Tuple<PropertyInfo, PropertyInfo>> GetProperties<TEntity, TModel>(TEntity entity, TModel model) { lock (MappingLock) { var key = new Tuple<Type, Type>(typeof (TEntity), typeof (TModel)); if (Mapping.ContainsKey(key)) { return Mapping[key]; } var modelProperties = typeof (TModel).GetProperties(); var newMapping = (from propertyInfo in typeof (TEntity).GetProperties() let modelPropertyInfo = modelProperties.SingleOrDefault(mp => mp.Name == propertyInfo.Name) select new Tuple<PropertyInfo, PropertyInfo>(propertyInfo, modelPropertyInfo)) .ToList(); Mapping.Add(key, newMapping); return newMapping; } } } 

这是macros观使用适合的情况:

 #define CheckAndAssign(dst,src) (dst != src && (dst = src, true)) return ( CheckAndAssign (entity.Title, item.Title) | CheckAndAssign (entity.ServerId, item.Id)); #undef CheckAndAssign 

那么,只要语言是C,C ++,Objective-C或其他任何与macros。 我希望在C ++中,你可以把它变成一个模板。