在内部使对象与另一个对象相等

时间:2018-11-12 21:39:59

标签: c# reflection copy deep-copy

给出类型为A的对象MyObj和类型也相同的对象B,在MyObj中定义方法的最佳方法是什么?将采用一个MyObj参数,并将当前对象设置为该参数。

示例:

class MyObj 
{ 
    /*lots of fields*/
    public void SetEqual(MyObj other)
    {
        /* What should go here, so that when the method ends, this is equal to other in every field OR they are ReferenceEqual */
    }
}

目前,我唯一想到的方法是反射或手动将每个值设置为相等(不便于重构)-对于当前对象,无法将当前对象作为另一个别名(类似于ref的别名)显而易见的原因是要获得引用相等,所以值相等是唯一的方法-是否有更有效,更务实的方法(反射缓慢;手工笨拙)?

2 个答案:

答案 0 :(得分:1)

好吧,我想到了4种方法。 (11/13/2018)编辑:添加了方法4

方法1

用于从彼此手动检索每个属性并将其存储到对象的当前实例中的代码。

优点:

  • (相对)快速
  • 明确控制要移转的属性(以防万一, 想排除一些属性)
  • 显式控制每个属性的深度与浅浅

缺点:

  • 维护
  • 当将来的开发人员(也许您,也许不是)向类添加新的字段/属性时,很容易错过

它可能看起来像这样:

class MyObj 
{ 
    public void SetEquals(MyObj other)
    {
        if (object.ReferenceEquals(this, other)) return; // We are equal by reference, so do nothing.
        if (other == null) return; // Throw ArgumentException? Up to you.
        this.Property1 = other.Property1;
        this.Property2 = other.Property2;
        this.Property3 = other.Property3;
        // ...
    }
}

方法2

编写一个自定义的反射帮助器类。我可能会使其静态化,并使用一些公共的静态方法,并在字典或由Type键键入的内容中内部存储反射类型和必要数据,并包含反射信息,因此避免在每次调用时对同一类型重复反射。首次将其用于任何给定类型时,它的计算量仍然会更高,但是之后,只要使用某种缓存,它就会更快。此外,您可以研究创建一个自定义属性,以指示您的反射帮助器类让OverrrideObjectByValue忽略特定的属性/字段。

优势

  • 几乎不需要维护
  • 可以使用装饰属性/字段的属性来编写 来指导反射器如何工作

缺点

  • 速度慢(至少用于初始反射,但是如果缓存,则 更快)
  • 如果您对反射的经验很少甚至没有经验,就可以编写复杂的文字
  • 使用它来支持嵌套类型内嵌套类型的深层副本与浅层副本可能会成为一个固有的递归问题,而复杂的属性系统将支持对深层或浅层案例的粒度控制

您可以这样做...

using System.Reflection;
public static class OverrideObjectValues
{
    private static Dictionary<Type, Tuple<PropertyInfo[], FieldInfo[]>> cachedLookup = new Dictionary<Type, Tuple<PropertyInfo[], FieldInfo[]>>

    // Copies fields and properties from the right object into the left object.
    // Could be extended to support attribute-level customization
    // guiding this reflector on properties/fields to ignore,
    // And whether to perform a deep or shallow copy of reference types
    // for instance properties of types left and right.
    public static void OverrideValues(object left, object right)
    {
        // They are equal by reference, we're done.
        // This also handles the case that both left and right are null.
        if (object.ReferenceEquals(left, right)) return;

        // One or the other is null; we can't do this.
        // Alternatively, throw an ArgumentException here?
        if (left == null || right == null) return;

        // The types mismatch; we can't do this.
        // Alternatively, throw an ArgumentException here?
        // Note: We could modify this to support the case where
        // Left or Right inherits from the other, but that becomes
        // more complex, and is beyond the scope of what
        // you're asking for.
        if (left.GetType() != right.GetType()) return;

        Type leftType = left.GetType();

        if (!cachedLookup.ContainsKey(leftType))
        {
            // Add type to cache
            cachedLookup.Add(leftType, new Tuple<PropertyInfo[], FieldInfo[]>(leftType.GetProperties(), leftType.GetFields()));
        }

        // Iterate around each property, and copy-by-value from right into left.
        // Do the same for each field, for the type we cached in the dictionary.
        // You can add support to exclude properties/fields which are decorated
        // with custom attributes. If you do support guiding by custom attributes,
        // I'd exclude these types in the lookup/cache step in the dictionary before this point.
        // You could even add support to differentiate between structs and classes,
        // and do deep / shallow copies accordingly...
    }
}

方法3

当您要使用A的实例MyObject的值覆盖B的实例MyObject时,可以使用赋值运算符按字面意义使它们相等参考。请注意:这样做意味着它们是相同的实例,这意味着对A的更改将反映在B中,因为AB是内存中的同一对象。

优势

  • 最快
  • 将来最容易理解(假设您知道引用类型的功能)
  • 维护

缺点

  • 不是深层副本

就这么简单

// Populate list of objects.
List<MyObj> objects = GetObjectsSomehow(); 

// Copy by reference object at index 4 over object at index 5.
objects[5] = objects[4];

众所周知,方法3的示例不是原始数据的深层复制/覆盖,而是将两者进行了复制(在这种情况下,我将它们存储在列表中) )相同-通过引用。如果对象是不可变的,这尤其有用,因为您不会违反不可变性的整个原理...

方法4

(在评论后添加了此方法) 这种方法实际上只是语法糖,实际上最好留给赋值运算符,但是如果出于某种原因真的想要一种方法,您可以可以这样做...

优势

  • 听起来像是您要找的东西?
  • 与方法3基本相同,使用赋值运算符通过引用设置相等...

缺点

  • 简单a = b时使用笨拙而不必要的方法;足以...

一些只有基本数据类型的任意自定义类型:

public class CustomType
{
    public string Name { get; set; }
    public int ID { get; set; }
}

然后您可以使用扩展方法创建一些静态类...

public static class CopyUtilities
{
    public static void MakeReferenceEqual<T>(this T left, ref T right) where T : class
    {
        if (object.ReferenceEquals(left, right)) return; // we're reference-equal, so be done.
        right = left;
    }
}

然后您可以这样使用:

CustomType a = new CustomType();
a.ID = 42;
a.Name = "Myself";

CustomType b = null;
a.MakeReferenceEqual(ref b);

// a.ID == b.ID
// a.Name == b.Name
// a == b, by reference.

答案 1 :(得分:0)

我认为您误会了引用等于的真正含义。

当两个变量都引用 same 对象(*)时,它们被称为引用相等。两个对象不是相等的引用,因为那是荒谬的。如果它们是两个不同的对象,则根据定义它们永远不能被引用相等。

现在,方法SetEqual可以做的是,当相等具有值语义时,使两个不同的对象相等。只有您才能知道特定类型的相等值语义是什么。

(*)请注意,根据该定义,值类型永远不能引用相等的值,因为值类型变量不存储引用。