【问题标题】:how to copy properties from one object to another with different values C#如何将属性从一个对象复制到另一个具有不同值的对象C#
【发布时间】:2023-03-20 03:52:01
【问题描述】:

我想将给定对象ClassA 中的Properties 值复制到另一个名为ClassB 的对象实例中,这些类可能是也可能不是同一类型。

如果ClassB 中的属性有值,而ClassA 中的相应属性值为空,则不要复制该值,因此只复制其中ClassB 中的当前属性为空。

这不是一个克隆练习,目标对象 (ClassB) 已经用部分定义的值进行了实例化,我正在寻找一种可重用的方法来复制尚未设置的其余值。

想想我们有一个通用或默认测试数据值的测试场景,对于特定测试,我想设置一些特定字段,然后从通用测试数据对象完成设置其他属性。

我想我正在寻找一个基于 Reflection 的解决方案,因为这样我们就不需要知道要复制的特定类型,这将使​​它可以在许多不同的场景中重复使用。

例如。

public class Employee
{
    public int EmployeeID { get; set; }
    public string EmployeeName { get; set; }
    public Address ContactAddress { get; set; }
}

public class Address
{
    public string Address1 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
}

测试例如。

public void TestMethod1()
{
    Employee employee = new Employee();
    employee.EmployeeID = 100;
    employee.EmployeeName = "John";
    employee.ContactAddress = new Address();
    employee.ContactAddress.Address1 = "Park Ave";
    employee.ContactAddress.City = "New York";
    employee.ContactAddress.State = "NewYork";
    employee.ContactAddress.ZipCode = "10002";
 
    Employee employeeCopy = new Employee();
    employeeCopy.EmployeeID = 101;
    employeeCopy.EmployeeName = "Tom";
    employeeCopy.ContactAddress = new Address();

    CopyPropertiesTo(employee, employeeCopy);
}

我想得到结果

employeeCopy EmployeeID=101;
员工姓名="汤姆";
ContactAddress.Address1 = "公园大道";
ContactAddress.City = "纽约";
ContactAddress.State = "纽约";
ContactAddress.ZipCode = "10002"

所以在这种情况下,因为 employeeCopy.ContactAddress 中的所有字段都没有设置,所以应该只复制原始 employee 对象中的那些字段。

不知道怎么写方法:
CopyPropertiesTo(object sourceObject, object targetObject)

【问题讨论】:

  • 您正在尝试做的是一种叫做 Deep Copy 的东西。您应该查找它,并且可以通过多种方式执行。
  • 无论如何,你可以只使用 if then else。有什么问题?
  • 您是否尝试通过反思来做到这一点? (因为问题中的标签)。 Employee 是一个示例类还是与问题相关的类?
  • 我想如果ClassB有值,我会使用它,但是如果ClassB为null并且ClassA有值,复制ClassA的值,我不能写方法“CopyPropertiesTo(employee,employeeCopy)”,一些身体谁能帮帮我?谢谢!

标签: c# reflection


【解决方案1】:

执行此操作的一种方法是简单地检查“to”Employee 中的每个属性,如果是 null0,则为其分配来自“from”Employee 的值:

/// <summary>
/// Copies values in 'from' to 'to' if they are null in 'to'
/// </summary>
public static void CopyProperties(Employee from, Employee to)
{
    if (from == null) return;
    if (to == null) to = new Employee();

    if (to.EmployeeID == 0) to.EmployeeID = from.EmployeeID;
    if (to.EmployeeName == null) to.EmployeeName = from.EmployeeName;

    if (from.ContactAddress == null) return;
    if (to.ContactAddress == null) to.ContactAddress = new Address();

    if (to.ContactAddress.Address1 == null)
        to.ContactAddress.Address1 = from.ContactAddress.Address1;
    if (to.ContactAddress.City == null)
        to.ContactAddress.City = from.ContactAddress.City;
    if (to.ContactAddress.State == null)
        to.ContactAddress.State = from.ContactAddress.State;
    if (to.ContactAddress.ZipCode == null)
        to.ContactAddress.ZipCode = from.ContactAddress.ZipCode;
}

【讨论】:

  • 虽然这可行,但专门针对这些类型执行此操作比直接在测试中实现它需要更多的代码行。它也不满足 OP 对基于反射的解决方案的要求,大概 OP 有许多或其他类型可以将此逻辑应用于
  • @ChrisSchaller 是的,我已经尝试从 OP 那里得到澄清,但还没有收到他的消息。基于反射的解决方案会更加灵活,但也会以牺牲性能为代价。此外,对于某些类型,基于反射的深拷贝很容易出错。我设想这个方法属于Employee 类。
  • @ChrisSchaller 另外,如果这就是他们正在寻找的,那么这个问题将成为 thisthis 的重复项(除了检查第一个对象的属性是否设置为类型的默认值)。
  • 不是真的,那些是专门关于 clone 但这是有条件地设置属性,关于 Employee 的 OP 示例只是一个类,他们正在寻找基于反射的解决方案,所以他们这样做了不必对所有这些属性比较进行硬编码。没有人建议它是一个好主意,但它肯定可以做到。
  • @ChrisSchaller 正如我所说,“除了检查第一个对象的属性是否设置为类型的默认值”,这很容易添加。他们在哪里声明他们正在“寻找基于反射的解决方案,因此他们不必对所有这些属性比较进行硬编码”?可能是这样,但目前这只是一个假设(除非我错过了问题中的某些内容)。
【解决方案2】:
public static void CopyPropertiesTo(Employee EP1, Employee EP2){
    
    Type eType=typeof(Employee);
    PropertyInfo[] eProps = eType.GetProperties();

    foreach(var p in eProps){
        if(p.PropertyType != typeof(String) && p.PropertyType != typeof(Int32)){
            //Merging Contact Address
            Type cType=p.PropertyType;
            PropertyInfo[] cProps = cType.GetProperties();
            
            foreach(var c in cProps){
                //Check if value is null
                if (String.IsNullOrEmpty((EP2.ContactAddress.GetType().GetProperty(c.Name).GetValue(EP2.ContactAddress) as string))){
                    //Assign Source to Target
                    EP2.ContactAddress.GetType().GetProperty(c.Name).SetValue(EP2.ContactAddress, (EP1.ContactAddress.GetType().GetProperty(c.Name).GetValue(EP1.ContactAddress)));
                }
            }
        }
        else{
            //Check if value is null or empty
            if (String.IsNullOrEmpty((EP2.GetType().GetProperty(p.Name).GetValue(EP2) as string))){
                //Assign Source to Target
                EP2.GetType().GetProperty(p.Name).SetValue(EP2, (EP1.GetType().GetProperty(p.Name).GetValue(EP1)));
            }
        }
    }
}

不是最漂亮的,但这应该可以做到并允许您更改类中属性的名称/数量。我从来没有真正尝试过这样做,所以如果有人有一些反馈,我将不胜感激

查看以下链接以获取更多信息和示例 PropertyInfo GetType GetProperty

【讨论】:

  • 作为最佳实践,您希望避免过多的非 LINQ 方法链接,因为 1) 如果失败,很难知道在哪里 2) 难以阅读 3) 难以调试因为你看不到返回值
  • 感谢您的反馈!看看我是否可以用 LINQ 查询重写它
  • 没问题。顺便说一句,我并不是要您使用 LINQ 重新编写它,而是说通常在使用 LINQ 方法链接时是可以接受的。但毫无疑问,您可以在 LINQ 中完成上述操作。
  • 如果我们可以判断该属性是类,我认为这是一个很好的答案,但我不知道如何判断
  • 我不太清楚你所说的“法官”是什么意思?你能详细说明一下吗?
【解决方案3】:

如果为时不晚,这也是我的建议,但可能会有所帮助。

    public class Source
    {
        [DefaultValueAttribute(-1)]
        public int Property { get; set; }

        public int AnotherProperty { get; set; }
    }

    public class Dedstination
    {
        public int Property { get; set; }

        [DefaultValueAttribute(42)]
        public int AnotherProperty { get; set; }
    }

    public void Main()
    {
        var source = new Source { Property = 10, AnotherProperty = 76 };
        var destination = new Dedstination();

        MapValues(source, destination);
    }

    public static void MapValues<TS, TD>(TS source, TD destination)
    {
        var srcPropsWithValues = typeof(TS)
            .GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .ToDictionary(x => x.Name, y => y.GetValue(source));

        var dstProps = typeof(TD)
       .GetProperties(BindingFlags.Public | BindingFlags.Instance)
       .ToDictionary(key => key, value => value.GetCustomAttribute<DefaultValueAttribute>()?.Value
                                       ?? (value.PropertyType.IsValueType
                                       ? Activator.CreateInstance(value.PropertyType, null)
                                       : null));

        foreach (var prop in dstProps)
        {
            var destProperty = prop.Key;

            if (srcPropsWithValues.ContainsKey(destProperty.Name))
            {
                var defaultValue = prop.Value;
                var currentValue = destProperty.GetValue(destination);
                var sourceValue = srcPropsWithValues[destProperty.Name];

                if (currentValue.Equals(defaultValue) && !sourceValue.Equals(defaultValue))
                {
                    destProperty.SetValue(destination, sourceValue);
                }
            }
        }
    }

编辑:我编辑了我的解决方案,以消除对使用 DefaultValueAttribute 的依赖。现在,您可以从指定的属性或类型默认值中获取默认值。

之前的解决方法如下:

        // This solution do not needs DefaultValueAttributes 
        var dstProps = typeof(TD)
           .GetProperties(BindingFlags.Public | BindingFlags.Instance)
           .ToDictionary(x => x, x => x.PropertyType.IsValueType ? Activator.CreateInstance(x.PropertyType, null) : null);

        // This solution needs DefaultValueAttributes 
        var dstProps = typeof(TD)
           .GetProperties(BindingFlags.Public | BindingFlags.Instance)
           .ToDictionary(x => x, x => x.GetCustomAttribute<DefaultValueAttribute>()?.Value ?? null);

【讨论】:

  • 我喜欢使用DefaultValueAttribute,它是一个有趣的故障保护。每当我使用DefaultValueAttribute 时,我都会尝试将 Default Values 设置为属性定义(或 constructor)的一部分,但在通用的方法中我们无法做到那个假设。从现在开始,我将在我的基本逻辑中使用它:)
  • 两个快速指针,但是,当使用属性时,类名上的 attribute 后缀是多余的,如果你像 [DefaultValue(0)] 那样离开它,代码通常会更清晰阅读其次,0 int 的默认值,因此在 OP 场景中使用是多余的。它可用于WinForms 和其他设计表面框架,但如果该值已经为零(default),那么我们将看不到将其设置为0 值的效果。 - 只是说这不是一个很好的例子,但我喜欢这个主意。
  • 我同意属性后缀是多余的,但属性本身没有空构造函数,您需要指定默认值,然后您也可以通过反射访问该值
  • 是的,但是 int default 值是 0 所以如果属性存在或者我们只是跳过了属性,因为它根本没有属性。
  • @Chris Schaller - 将 DefaultValueAttribute 与 0、false 或 null 等值一起使用没有多大意义,但如果您想将它们与反射一起使用以从属性中获取默认值, 你必须。另一方面,如果你只用反射构建默认值,那么使用属性是没有意义的。
【解决方案4】:

可以通过序列化轻松实现深度克隆,但是仅跨非空字段复制需要更多条件逻辑,在这种情况下,我将其称为 Coalesce,因此我将方法命名为 @987654321 @。如果您愿意,您可以将其重构为扩展方法,但我不推荐它,而是将其放在静态帮助器类中。尽管这可能很有用,但我不鼓励将其作为生产业务运行时的“goto”。

对这些类型的解决方案使用 Reflection 通常是最低效的机制,但它为我们提供了很大的灵活性,并且非常适合模拟、原型设计和快速单元测试表达式。

  • 虽然不在此示例中,但可以轻松添加检查以排除高级场景的 [Obsolete] 属性

以下示例使用Property Name 比较,因此您不必传入相同类型的对象。请注意,已创建 IsNullIsValueType 方法来封装这些概念,从而简化您可能希望对此方法进行的调整。

  • 此方法还在继续之前检查属性是否可以读取/写入,这允许我们支持源对象上的只读属性,当然我们不会尝试写入只读属性。
  • 解析和写入的最终值包含在抑制任何错误的 try catch 语句中,需要进行一些调整才能使这样的代码普遍工作,但对于简单的类型定义应该可以正常工作。
/// <summary>
/// Deep Copy the top level properties from this object only if the corresponding property on the target object IS NULL.
/// </summary>
/// <param name="source">the source object to copy from</param>
/// <param name="target">the target object to update</param>
/// <returns>A reference to the Target instance for chaining, no changes to this instance.</returns>
public static void CoalesceTo(object source, object target, StringComparison propertyComparison = StringComparison.OrdinalIgnoreCase)
{
    var sourceType = source.GetType();
    var targetType = target.GetType();
    var targetProperties = targetType.GetProperties();
    foreach(var sourceProp in sourceType.GetProperties())
    {
        if(sourceProp.CanRead)
        {
            var sourceValue = sourceProp.GetValue(source);

            // Don't copy across nulls or defaults
            if (!IsNull(sourceValue, sourceProp.PropertyType))
            {
                var targetProp = targetProperties.FirstOrDefault(x => x.Name.Equals(sourceProp.Name, propertyComparison));
                if (targetProp != null && targetProp.CanWrite)
                {
                    if (!targetProp.CanRead)
                        continue; // special case, if we cannot verify the destination, assume it has a value.
                    else if (targetProp.PropertyType.IsArray || targetProp.PropertyType.IsGenericType // It is ICollection<T> or IEnumerable<T>
                                                                && targetProp.PropertyType.GenericTypeArguments.Any()
                                                                && targetProp.PropertyType.GetGenericTypeDefinition() != typeof(Nullable<>) // because that will also resolve GetElementType!
                            )
                        continue; // special case, skip arrays and collections...
                    else
                    {
                        // You can do better than this, for now if conversion fails, just skip it
                        try
                        {
                            var existingValue = targetProp.GetValue(target);
                            if (IsValueType(targetProp.PropertyType))
                            {
                                // check that the destination is NOT already set.
                                if (IsNull(existingValue, targetProp.PropertyType))
                                {
                                    // we do not overwrite a non-null destination value
                                    object targetValue = sourceValue;
                                    if (!targetProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType))
                                    {
                                        // TODO: handle specific types that don't go across.... or try some brute force type conversions if neccessary
                                        if (targetProp.PropertyType == typeof(string))
                                            targetValue = targetValue.ToString();
                                        else 
                                            targetValue = Convert.ChangeType(targetValue, targetProp.PropertyType);
                                    }

                                    targetProp.SetValue(target, targetValue);
                                }
                            }
                            else if (!IsValueType(sourceProp.PropertyType))
                            {
                                // deep clone
                                if (existingValue == null)
                                    existingValue = Activator.CreateInstance(targetProp.PropertyType);

                                CoalesceTo(sourceValue, existingValue);
                            }
                        }
                        catch (Exception)
                        {
                            // suppress exceptions, don't set a field that we can't set
                        }

                    }
                }
            }
        }
    }
}

/// <summary>
/// Check if a boxed value is null or not
/// </summary>
/// <remarks>
/// Evaluate your own logic or definition of null in here.
/// </remarks>
/// <param name="value">Value to inspect</param>
/// <param name="valueType">Type of the value, pass it in if you have it, otherwise it will be resolved through reflection</param>
/// <returns>True if the value is null or primitive default, otherwise False</returns>
public static bool IsNull(object value, Type valueType = null)
{
    if (value is null)
        return true;

    if (valueType == null) valueType = value.GetType();

    if (valueType.IsPrimitive || valueType.IsEnum || valueType.IsValueType)
    {
        // Handle nullable types like float? or Nullable<Int>
        if (valueType.IsGenericType)
            return value is null;
        else
            return Activator.CreateInstance(valueType).Equals(value);
    }

    // treat empty string as null!
    if (value is string s)
        return String.IsNullOrWhiteSpace(s);

    return false;
}
/// <summary>
/// Check if a type should be copied by value or if it is a complexe type that should be deep cloned
/// </summary>
/// <remarks>
/// Evaluate your own logic or definition of Object vs Value/Primitive here.
/// </remarks>
/// <param name="valueType">Type of the value to check</param>
/// <returns>True if values of this type can be straight copied, false if they should be deep cloned</returns>
public static bool IsValueType(Type valueType)
{
    // TODO: any specific business types that you want to treat as value types?

    // Standard .Net Types that can be treated as value types
    if (valueType.IsPrimitive || valueType.IsEnum || valueType.IsValueType || valueType == typeof(string))
        return true;

    // Support Nullable Types as Value types (Type.IsValueType) should deal with this, but just in case
    if (valueType.HasElementType // It is array/enumerable/nullable
        && valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(Nullable<>))
        return true;


    return false;
}

因为我们在这里使用反射,所以我们无法利用 Generics 可以为我们提供的优化。如果您想将此应用到生产环境,请考虑使用 T4 模板来编写此逻辑的通用类型版本作为您的业务类型的扩展方法。

深度克隆 -

你会注意到我特别跳过了数组和其他 IEnumerable 结构...有一大堆蠕虫支持它们,最好不要让一种方法尝试 Deep 复制,所以将嵌套调用 CoalesceTo 取出,然后对树中的每个对象调用 clone 方法。

数组/集合/列表的问题在于,在克隆之前,您需要确定一种方法将源中的集合与目标中的集合同步,您可以根据 Id 字段或像[KeyAttribute] 这样的某种属性,但这种实现需要高度特定于您的业务逻辑,并且超出了这篇已经很可怕的帖子的范围;)

DecimalDateTime 这样的类型在这些类型的场景中是有问题的,它们不应该与 null 进行比较,而是我们必须将它们与它们的默认类型状态进行比较,同样我们不能使用泛型 @987654330 @ 运算符或值在这种情况下,因为类型只能在运行时解析。

所以我已经更改了您的类,以包含此逻辑如何处理 DateTimeOffset 的示例:

public class Employee
{
    public int EmployeeID { get; set; }
    public string EmployeeName { get; set; }
    public DateTimeOffset Date { get; set; }
    public float? Capacity { get; set; }
    Nullable<int> MaxShift { get; set; }
    public Address ContactAddress { get; set; }
}

public class Address
{
    public string Address1 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
}

public static  void TestMethod1()
{
    Employee employee = new Employee();
    employee.EmployeeID = 100;
    employee.EmployeeName = "John";
    employee.Capacity = 26.2f;
    employee.MaxShift = 8;
    employee.Date = new DateTime(2020,1,22);
    employee.ContactAddress = new Address();
    employee.ContactAddress.Address1 = "Park Ave";
    employee.ContactAddress.City = "New York";
    employee.ContactAddress.State = "NewYork";
    employee.ContactAddress.ZipCode = "10002";

    Employee employeeCopy = new Employee();
    employeeCopy.EmployeeID = 101;
    employeeCopy.EmployeeName = "Tom";
    employeeCopy.ContactAddress = new Address();

    CoalesceTo(employee, employeeCopy);
}

这会产生以下对象图:

{
  "EmployeeID": 101,
  "EmployeeName": "Tom",
  "Date": "2020-01-22T00:00:00+11:00",
  "Capacity":26.2,
  "MaxShift":8,
  "ContactAddress": {
    "Address1": "Park Ave",
    "City": "New York",
    "State": "NewYork",
    "ZipCode": "10002"
  }
}

【讨论】:

  • 我需要在具有数组或集合属性的 DEEP 对象图中使用这种类型的 coalesce 重新迭代一个主要障碍,如果你想要支持 1:m 属性,您需要确定一种将源中的集合与目标中的集合同步的方法,以及如何在复制对象之前映射正确的对象。您可以根据 Id 字段或某种属性(如 [KeyAttribute])制定约定,但这种实现将变得高度特定于您的业务逻辑。如果您可以避免枚举,或者将它们替换为 VALUES
  • 当我测试时,如果我有 Nullable,它仍然报告错误,我的代码:float?检查
  • 感谢@qiuqp 把它捡起来,看IsNull 方法的变化,这是将IsNull 的逻辑提取到自己的方法中的主要原因,核心逻辑仍然是同样,我们只是改进了什么是null的定义。您可能会发现其他导致问题的场景,IsValueType 是另一个未来可能需要改进的开关逻辑。
  • 谢谢,我修改了 IsNull 方法,这是我的代码: if (valueType.IsPrimitive || valueType.IsEnum || valueType.IsValueType) return value.Equals(Activator.CreateInstance(valueType));
  • 这是之前的逻辑,但我已经在我的解决方案中更改了它以匹配您的请求,它有一个关于可空类型的特定注释。
【解决方案5】:

在复制完成和/或实现 ICloneable 接口后对新实例进行更改。 https://docs.microsoft.com/en-us/dotnet/api/system.icloneable?view=netcore-3.1

【讨论】:

  • 首先肯定不修改非null 属性会更好吗?如果您更改的属性之一是子对象图怎么办?那会很棘手
  • 不完全确定子对象图的含义,但它们的结构并不复杂。如果它需要对泛型类型可重用,那么我不确定是否会有很好的解决方案耸耸肩
  • Address 不是一个简单的值类型,它是一个 reference 类型,所以Employee 代表一个对象图,尽管只有 2 层深。 OP 本质上是在描述 merge 操作,因此跳过目标中的非空属性会更好。我并不是说你错了,只是可能需要额外的努力。
【解决方案6】:

如果您首先进行完整的深度克隆,这些类型的问题通常会更容易且资源消耗更少,而不是尝试进行深度复制,并且然后设置你的价值观。

关于 deep clone 的 SO 上有很多帖子,我的偏好只是使用 JSON.Net 进行序列化然后反序列化。

public static T Clone<T>(T value, Newtonsoft.Json.JsonSerializerSettings settings = null)
{
    var objectType = value.GetType();
    var cereal = Newtonsoft.Json.JsonConvert.SerializeObject(value, settings);
    return (T)Newtonsoft.Json.JsonConvert.DeserializeObject(cereal, objectType, settings);
}

但是,此代码需要 Newtonsoft.Json nuget 包参考。

克隆对象会设置所有通用/默认值首先,然后我们只修改此特定测试或代码块所需的那些属性。

public void TestMethod1()
{
    Employee employee = new Employee();
    employee.EmployeeID = 100;
    employee.EmployeeName = "John";
    employee.ContactAddress = new Address();
    employee.ContactAddress.Address1 = "Park Ave";
    employee.ContactAddress.City = "New York";
    employee.ContactAddress.State = "NewYork";
    employee.ContactAddress.ZipCode = "10002";
 
    // Create a deep clone of employee
    Employee employeeCopy = Clone(employee);

    // set the specific fields that we want to change
    employeeCopy.EmployeeID = 101;
    employeeCopy.EmployeeName = "Tom";

}

如果我们愿意改变我们的方法,我们通常可以找到更简单的解决方案,这个解决方案将具有相同的输出,就像我们有条件地复制属性值一样,但不进行任何比较。

如果您有条件复制的其他原因,在本文的其他解决方案中称为 MergeCoalesce,那么 my other answer using reflection 将完成这项工作,但它没有这个强大。

【讨论】:

    【解决方案7】:
    [TestClass]
    public class UnitTest11
    {
        [TestMethod]
        public void TestMethod1()
        {
    
            Employee employee = new Employee();
            employee.EmployeeID = 100;
            employee.EmployeeName = "John";
            employee.Date = DateTime.Now;
            employee.ContactAddress = new Address();
            employee.ContactAddress.Address1 = "Park Ave";
            employee.ContactAddress.City = "New York";
            employee.ContactAddress.State = "NewYork";
            employee.ContactAddress.ZipCode = "10002";
    
            Employee employeeCopy = new Employee();
            employeeCopy.EmployeeID = 101;
            employeeCopy.EmployeeName = "Tom";
            employeeCopy.ContactAddress = new Address();
            employeeCopy.ContactAddress.City = "Bei Jing";
            //copy all properties from employee to employeeCopy
            CoalesceTo(employee, employeeCopy);
    
            Console.ReadLine();
        }
    
        /// Deep Copy the top level properties from this object only if the corresponding property on the target object IS NULL.
        /// </summary>
        /// <param name="source">the source object to copy from</param>
        /// <param name="target">the target object to update</param>
        /// <returns>A reference to the Target instance for chaining, no changes to this instance.</returns>
        public static void CoalesceTo(object source, object target, StringComparison propertyComparison = StringComparison.OrdinalIgnoreCase)
        {
            var sourceType = source.GetType();
            var targetType = target.GetType();
            var targetProperties = targetType.GetProperties();
            foreach (var sourceProp in sourceType.GetProperties())
            {
                if (sourceProp.CanRead)
                {
                    var sourceValue = sourceProp.GetValue(source);
    
                    // Don't copy across nulls or defaults
                    if (!IsNull(sourceValue, sourceProp.PropertyType))
                    {
                        var targetProp = targetProperties.FirstOrDefault(x => x.Name.Equals(sourceProp.Name, propertyComparison));
                        if (targetProp != null && targetProp.CanWrite)
                        {
                            if (!targetProp.CanRead)
                                continue; // special case, if we cannot verify the destination, assume it has a value.
                            else if (targetProp.PropertyType.IsArray || targetProp.PropertyType.IsGenericType // It is ICollection<T> or IEnumerable<T>
                                                                        && targetProp.PropertyType.GenericTypeArguments.Any()
                                                                        && targetProp.PropertyType.GetGenericTypeDefinition() != typeof(Nullable<>) // because that will also resolve GetElementType!
                                    )
                                continue; // special case, skip arrays and collections...
                            else
                            {
                                // You can do better than this, for now if conversion fails, just skip it
                                try
                                {
                                    var existingValue = targetProp.GetValue(target);
                                    if (IsValueType(targetProp.PropertyType))
                                    {
                                        // check that the destination is NOT already set.
                                        if (IsNull(existingValue, targetProp.PropertyType))
                                        {
                                            // we do not overwrite a non-null destination value
                                            object targetValue = sourceValue;
                                            if (!targetProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType))
                                            {
                                                // TODO: handle specific types that don't go across.... or try some brute force type conversions if neccessary
                                                if (targetProp.PropertyType == typeof(string))
                                                    targetValue = targetValue.ToString();
                                                else
                                                    targetValue = Convert.ChangeType(targetValue, targetProp.PropertyType);
                                            }
    
                                            targetProp.SetValue(target, targetValue);
                                        }
                                    }
                                    else if (!IsValueType(sourceProp.PropertyType))
                                    {
                                        // deep clone
                                        if (existingValue == null)
                                            existingValue = Activator.CreateInstance(targetProp.PropertyType);
    
                                        CoalesceTo(sourceValue, existingValue);
                                    }
                                }
                                catch (Exception)
                                {
                                    // suppress exceptions, don't set a field that we can't set
                                }
    
                            }
                        }
                    }
                }
            }
        }
    
        /// <summary>
        /// Check if a boxed value is null or not
        /// </summary>
        /// <remarks>
        /// Evaluate your own logic or definition of null in here.
        /// </remarks>
        /// <param name="value">Value to inspect</param>
        /// <param name="valueType">Type of the value, pass it in if you have it, otherwise it will be resolved through reflection</param>
        /// <returns>True if the value is null or primitive default, otherwise False</returns>
        public static bool IsNull(object value, Type valueType = null)
        {
            if (value is null)
                return true;
    
            if (valueType == null) valueType = value.GetType();
    
            if (valueType.IsPrimitive || valueType.IsEnum || valueType.IsValueType)
                return value.Equals(Activator.CreateInstance(valueType));
    
            // treat empty string as null!
            if (value is string s)
                return String.IsNullOrWhiteSpace(s);
    
            return false;
        }
        /// <summary>
        /// Check if a type should be copied by value or if it is a complexe type that should be deep cloned
        /// </summary>
        /// <remarks>
        /// Evaluate your own logic or definition of Object vs Value/Primitive here.
        /// </remarks>
        /// <param name="valueType">Type of the value to check</param>
        /// <returns>True if values of this type can be straight copied, false if they should be deep cloned</returns>
        public static bool IsValueType(Type valueType)
        {
            // TODO: any specific business types that you want to treat as value types?
    
            // Standard .Net Types that can be treated as value types
            if (valueType.IsPrimitive || valueType.IsEnum || valueType.IsValueType || valueType == typeof(string))
                return true;
    
            // Support Nullable Types as Value types (Type.IsValueType) should deal with this, but just in case
            if (valueType.HasElementType // It is array/enumerable/nullable
                && valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(Nullable<>))
                return true;
    
    
            return false;
        }
    }
    
    
    public class Employee
    {
        public int EmployeeID { get; set; }
        public string EmployeeName { get; set; }
        public DateTimeOffset Date { get; set; }
        public float? check { get; set; }
        public Address ContactAddress { get; set; }
    }
    
    public class Address
    {
        public string Address1 { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string ZipCode { get; set; }
    }
    

    非常感谢大家,尤其是@Chris Schaller,我在上面发布了代码

    【讨论】:

      【解决方案8】:
      private Employee Check(Employee employee,Employee employeeCopy)
              {
      if (employeeCopy.EmployeeID==0 && employee.EmployeeID !=0)
        {
           employeeCopy.EmployeeID = employee.EmployeeID;
        }
      if (employeeCopy.EmployeeName == null && employee.EmployeeName != null)
        {
           employeeCopy.EmployeeName = employee.EmployeeName;
        }
      if (employeeCopy.ContactAddress == null)
      {
      if (employeeCopy.ContactAddress.Address1 == null && employee.ContactAddress.Address1 != null)
        {
           employeeCopy.ContactAddress.Address1 = employee.ContactAddress.Address1;
        }
      if (employeeCopy.ContactAddress.City == null && employee.ContactAddress.City != null)
       {
           employeeCopy.ContactAddress.City = employee.ContactAddress.City;
       }
      if (employeeCopy.ContactAddress.State == null && employee.ContactAddress.State != null)
       {
           employeeCopy.ContactAddress.State = employee.ContactAddress.State;
       }
      if (employeeCopy.ContactAddress.ZipCode == null && employee.ContactAddress.ZipCode != null)
       {
          employeeCopy.ContactAddress.ZipCode = employee.ContactAddress.ZipCode;
       }
      }
                  return employeeCopy;
      
      }
      

      这就是你要找的吗?

      【讨论】:

      • 是的,谢谢,但是接缝不太好,可以用反射吗?
      • @qiuqp 对不起,我对Reflection不是很熟悉
      • 仅供参考,如果 ContactAddressnull,则会抛出此错误
      • 别担心,我会确保 ContactAddress 不为空
      • @RufusL 我喜欢你添加if (from == null) return; if (to == null) to = new Employee();
      猜你喜欢
      • 1970-01-01
      • 2016-09-20
      • 1970-01-01
      • 1970-01-01
      • 2016-10-13
      • 1970-01-01
      • 1970-01-01
      • 2014-12-15
      • 2011-02-07
      相关资源
      最近更新 更多