如何简化重复的 if-then-assign 构造?

Posted

技术标签:

【中文标题】如何简化重复的 if-then-assign 构造?【英文标题】:How to simplify repeating if-then-assign construction? 【发布时间】:2015-07-06 22:55:29 【问题描述】:

我有以下方法:

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 行几乎复制粘贴的代码太多了。可能有一个使用Func-s / Expression-s 的解决方案超出我的视野。

【问题讨论】:

尽管我喜欢这个问题并且希望看到一个好的答案,但这不应该在 codereview 上吗? 作为一种方式,这里需要某种对象-对象属性映射,试试看codeproject.com/Articles/742461/…***.com/questions/17675408/…***.com/questions/20410234/… 可以更改的属性数量?还是静态的? @SergeyMetlov 我认为这里没有魔法,你必须做某种映射(if 条件、配置、属性..)somewere ;) @SergeyMetlov 有时,当人们想要if 条件的简化版本时,代码的可读性会降低。 【参考方案1】:

您有一个时间耦合的情况,即您正在混合检查实体是否已与分配发生变化。如果将两者分开,您的代码会变得更加简洁:

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),在这种情况下似乎没有帮助,因为它不会清楚地传达你的意图。

【讨论】:

使用此解决方案,您必须以两种不同的方法执行维护,而不是靠紧挨着的几行。 这取决于...首先我认为它使代码更清晰:我只更新对象如果它被修改!这听起来很直观。第二:如果在 Object 上调用 setter 有任何副作用,它们将不会被调用。如果有一些计算值在分配时重新计算,我们跳过它们。因此,如果我有一个包含 1000 个对象的视图状态,只有一个被更改并且我为所有对象调用它,它可以产生很大的性能差异! 我不确定你在说什么。我是说使用此代码进行维护(添加/删除属性)会更加困难,因为属性访问分布在两种方法上。 我很难理解在某种抽象的可维护性意义上故意使代码不那么清晰的概念。在我的书中,更清晰的代码总是更易于维护。而且,如果您真的害怕像描述的那样搞砸更改,那么您可能不会以测试驱动方式工作。不过,您应该这样做。 @bstenzel 你说得对,代码不那么清晰而且更脆弱。话虽如此,我确实认为如果目的是保持完全相同的方法行为,这可能是最好的方法。改进后代码看起来仍然很奇怪的事实需要超越方法。例如,为什么将实体传递给方法以使该方法更改其属性?或者,为什么仅在实体已更改时才发生更新?可能有更深的气味。【参考方案2】:

这样的东西应该可以工作

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;

不确定这是否可读。这是主观的。

【讨论】:

这应该可以。我绝对不想在我的代码库中看到它;P 这使得逻辑“if-then-assign”更具可读性(CompareAndModify),但使使用它的代码(ModifyExistingEntity)不可读。最好通过构建一个隐藏复杂、无聊或事物的实现来提高代码的可读性;)重复仍然存在! @Guillaume 我同意。读者会双倍接受。如果您担心可读性,我推荐bstenzel's answer。我只是根据 OP 的这种担忧回答“问题很明显:每个属性 5 行几乎复制粘贴的代码太多了” 这是另一个版本:static bool CompareAndModify&lt;TProp&gt;(TProp oldValue, TProp newValue, Action&lt;TProp&gt; setAction) if (EqualityComparer&lt;TProp&gt;.Default.Equals(oldValue, newValue)) return false; setAction(newValue); return true; 应该这样调用:isModified |= CompareAndModify(entity.ServerId, item.Id, x =&gt; entity.ServerId = x); 在此调用中,我只需输入一次item.Id 和两次entity.ServerId。为了改善这一点,我必须使用 Patrick Hofman 的回答中的表达式树。 Yuck.. 加上这与 OP 担心的问题相同;大部分是复制/粘贴的代码行,几乎完全相同。【参考方案3】:

我认为this answer 的扩展可能对你有用:

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;
    

需要一个表达式来检查值。它动态编译和执行它。

像这样使用它:

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"


【讨论】:

Expression.Compile 很贵。因此,您可能需要缓存已编译的表达式,以防有人使用这种方法。 你可能是对的。编辑代码以满足他的确切要求是可行的。【参考方案4】:

使用 T4 进行元编程

另一种方法 - 当我们复制实际上简单并且可能非常快的代码时,经常发生这种情况。在这种情况下,每个重复的 if 块都不相同 - 它包含 一点 知识 - 从一个属性到另一个属性的映射。

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

使用我的解决方案,映射很简单:

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 的内置功能):

<#@ 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

优点:

繁重的工作在编译时完成(实际上是在编译前一次) - 生成的代码很快。 生成的代码已记录在案。 易于维护 - 您可以在一个点上更改所有映射器。 已记录生成的方法。 没有copy-paste bugs。 这很有趣。

缺点:

使用字符串获取属性名称。请记住 - 这不是生产代码,它只是用于生成代码。可以使用真实类型和表达式树(下面的示例)。 静态分析可能会错过模板中的用法(即使我们使用表达式,也不是所有工具都会查看 tt 文件)。 很多人不知道发生了什么。 如果您使用的是表达式,则很难引用您的类型。

注意事项:

我已将参数命名为 sourcetarget,并更改了它们的顺序,因此 source 始终是第一个。

有人担心我使用的是字符串而不是真实属性。虽然在这种情况下这是一个小问题(输出已编译),但这里有一个适用于您的真实对象的附加内容。

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

<#@ 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

注意事项:

表达式仅用于将属性名称作为字符串获取,我们不会编译或运行它们。 在 C# 6 中,我们将使用 nameof() operator,这是表达式和​​无魔法字符串之间的一个很好的折衷。

【讨论】:

我喜欢你的方法。我们使用 XSLT 做类似的事情,这就像一个魅力。 我觉得 Data Mapper 也是这里最好的方法,但是 t4 模板将它与字符串魔术联系得太多了。我认为像 AutoMapper 这样的东西更适合 IMO。 @Moarboilerplate - 这是一个很好的观点,我在“缺点”下提到了一点。字符串在这里是为了简单起见,但还有其他选择。我有一个使用泛型和表达式的半工作版本。在 C# 6 中,我们还将拥有 nameof operator,这使得使用字符串变得不那么痛苦。 @moarboilerplate - 我走得太远了,添加了使用表达式的模板。【参考方案5】:

没有魔杖可以简化这一点。

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

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

如果工作量太大,您的解决方案很好。

【讨论】:

如果必须修改实体,最好实现 INotifyPropertyChanged,然后在发生属性更改时处理该事件并设置 isModified=true。 这是一个选项,但工作量更大。我相信问题的重点是尽可能少地完成这项工作。【参考方案6】:

如果你想让它可读,你可以为此目的创建一个类,非常简单的用法避免重复代码:

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 那里获取了一些代码来从 getter 表达式生成一个 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);
    

您可能希望缓存.Compile 的结果以提高性能。

【讨论】:

【参考方案7】:

查看我自己的(几乎和其他人一样奇怪)的解决方案

[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;

【讨论】:

【参考方案8】:

我已经看到了这个问题最复杂的答案,但我认为你最适合使用一个相当直接、严肃的简单解决方案。

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

但这提出了一个问题,您为什么只担心在更改后才更新?每次都简单地更新可能是可以接受的。

此外,您可能不应该将实体传递给方法,期望它会得到更新。

我会改变逻辑如下:

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

这个答案与其他答案最大的不同是 它不会修改 Product 实体。 相反,它会比较 ProductProductModel,但如果检测到更改,它会然后使用ProductModel 创建一个new Product,然后将其传递给另一个实际执行更新工作的 DAL 方法。我相信这可能是最易于维护的方法,因为您不必处理改变传入对象状态的方法(即使方法和调用者存在于不同的位置,也可以隐式耦合),这意味着您不必在调试期间单步执行代码时,在心理上跟踪实体的状态变化。

【讨论】:

【参考方案9】:

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

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;

【讨论】:

您可以使用布尔运算符 && 使其更短:isModified |= (entity.Title != item.Title) &amp;&amp; set( entity.Title = item.Title ); 其中 set 是一个辅助函数,它接受一个参数并返回 true。 @Falco 我同意我们可以通过这种方式优化总字符数,但不能优化代码总行数;这是主要的抱怨之一。我看到减少总行数的唯一方法是将所有这些组合成一个返回语句,例如: return ((entity.Title!= item.Title) ? (entity.Title = item.Title) == item.标题:假)|| ((entity.ServerId != item.Id) ? (entity.ServerId = item.Id) == item.Id : false); 我想要优化的和 OP 最想要的不是复制和粘贴。我越经常在每一行上写变量名,就越容易出错,如果我复制并粘贴该行并忘记更改我必须输入正确属性名的 5 个位置之一。最好的解决方案是一个,其中两个属性名称在每一行中只出现一次......【参考方案10】:

继续@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;

干净简单。

【讨论】:

【参考方案11】:

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

您应该首先编写一个比较两个对象的方法。这里有一些关于此事的 SO 问题供您参考:

    Properly implement comparison of two objects with different type but semantically equivalent Comparing object properties in c#

你的方法看起来像:

public static bool IsEqualTo<TSource>(this TSource sourceObj, TDestination destinationObj)
    where TSource : class
    where TDestination : class

    // Your comparison code goes here

然后您将不得不编写第二种方法来在对象之间复制数据。这些问题可以指导您完成(查看 Marc 答案):

    How to deep copy between objects of different types in C#.NET

你的方法看起来像:

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;

【讨论】:

【参考方案12】:

我不确定 Product 类是否对您来说是可扩展的,我也不确定您是在寻找“花哨”的答案还是只是一个更简单的答案……但如果是这样,您可以解决一点,把逻辑放在Product 类本身;恕我直言,您最终得到了一个相当易读的方法:

protected override bool ModifyExistingEntity(Product entity, ProductModel item)

    entity.SetTitle(item.Title);
    entity.SetServerId(item.Id);
    return entity.WasModified();

额外的好处是您可以将行为巧妙地封装到Product(以及验证等)中

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;
    

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

【讨论】:

【参考方案13】:

如果您只想要更短的代码行,您可以将其压缩为这种 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.

【讨论】:

这个想法是,通过这种对称分配,很难错过更改复制粘贴属性之一,因为它会立即看起来不对称 辅助方法缺少返回类型 (bool)。我认为这应该是一种避免将值类型装箱到object 框中的通用方法。所以它变成了:static bool RetTrue&lt;T&gt;(T dummy) return true; 【参考方案14】:

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

编辑:为多个线程添加了修改值的复制和锁定。

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;
        
    

【讨论】:

代码作为练习很好!但是 - OP 的代码不比较对象:它设置它们的属性 并返回一个脏位。此外,映射不是一对一的属性,OP 有ServerIdId,更糟糕的是,可能还有其他不应该映射的属性。我还要注意 Mapping.Add 如果从多个线程中使用可能会引发异常。 已注明,因此添加了修改属性值的复制。没有注意到属性名称不匹配。猜猜 OP 可以自己为不兼容的对象编写映射。【参考方案15】:

这是适合使用宏的情况:

#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 或其他任何带有宏的语言。我希望在 C++ 中你能以某种方式把它变成一个模板。

【讨论】:

这个问题只标记了c#.net

以上是关于如何简化重复的 if-then-assign 构造?的主要内容,如果未能解决你的问题,请参考以下文章

派生类调用默认构造函数[重复]

ArgumentNullException - 如何简化?

如何从try-catch简化return语句[重复]

我的代码中有许多重复的功能如何简化它们

如何在promise中简化这个有角度的重复代码?

如何避免孩子重复构造函数