在内部使对象等于另一个
Make object equal to another, internally
给定一个类型为 MyObj
的对象 A
和一个同样类型的对象 B
,在 [=12= 中定义方法的最佳方法是什么] 这将采用 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
关键字) ,出于明显的原因,为了获得引用相等,所以值相等是唯一的方法 - 是否有更有效和务实的方法来做到这一点(反射很慢;手动很笨重),或者没有?
我认为您误解了 reference equals 的真正含义。
当两个变量都引用 相同 对象时,称它们引用相等(*)。两个对象引用不相等,因为那是荒谬的;如果它们是两个不同的对象,那么根据定义它们永远不能被引用相等。
现在,您的方法 SetEqual
可以做的是在相等具有值语义时使两个不同的对象相等。只有您自己知道您的特定类型的相等值语义是什么样的。
(*)请注意,根据该定义,值类型永远不能引用等于任何值,因为值类型变量不存储引用。
嗯,我想到了 4 种方法。
(11/13/2018)编辑:添加方法 4
方法一:
用于从其他 属性 手动检索每个个体并将其存储到对象的当前实例中的代码。
优点:
- (相对)快
- 明确控制哪些属性被拉过来(如果你
想排除一些属性)
- 对每个属性
的深度与浅度的明确控制
缺点:
- 维护
- 当未来的开发者(也许是你,也许不是)将新的 field/property 添加到 class
时很容易错过
它可能看起来像这样:
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;
// ...
}
}
方法二:
编写自定义反射助手 class。我可能会将它设为静态,使用一些 public 静态方法,并在内部将反射类型和必要数据存储在字典或由 Type 键入的内容中,并包含反射信息,这样你就可以避免重复反射键入每个调用。第一次将它用于任何给定类型时,它在计算上仍然会更昂贵,但之后,只要您使用某种缓存,它就会更快。此外,您可以考虑创建一个自定义属性来指示您的反射助手-class 以便 OverrrideObjectByValue 忽略特定的 properties/fields.
优势:
- 几乎没有维护
- 可以用你修饰的属性来写properties/fields
用于指导反射器如何工作
缺点:
- 慢(至少对于初始反射来说是这样,但是如果你缓存它就会
更快)
- 如果您对反射几乎没有经验,那么编写起来很复杂
- 编写它以支持嵌套类型中嵌套类型的深层与浅层复制可能会成为一个固有的递归问题,复杂的属性系统支持深层与浅层情况的粒度控制
你可以这样做...
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...
}
}
方法三:
当你想用 MyObject
的实例 B
的值覆盖 MyObject
的实例 A
时,你可以只使用赋值运算符来实现它们引用相等。请注意:这样做意味着它们是同一个实例,这意味着对 A
的更改会反映在 B
中,因为 A
和 B
是内存中的同一个对象。
优势:
- 最快
- 将来最容易理解(假设您知道引用类型的功能)
- 维护
缺点:
- 不是深拷贝
就这么简单:
// 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 的这个示例不是原始数据的深层复制/覆盖,而是制作两者(在这种情况下我是将它们存储在列表中)相同 - 通过引用。如果对象是不可变的,这将特别有用,因为您知道,这并不违反整个不变性原则...
方法四:
(评论后添加此方法)
这个方法实际上只是语法糖,最好只保留为赋值运算符,但如果出于某种原因你真的想要一个方法,你可以可以这样做...
优势:
- 听起来这可能就是您要找的东西?
- 与使用方法 3 相同(基本上),通过引用设置相等,使用赋值运算符...
缺点:
- 一个简单的
a = b
;就够了...
一些只有基本数据类型的任意自定义类型:
public class CustomType
{
public string Name { get; set; }
public int ID { get; set; }
}
然后你可以有一些带有扩展方法的静态 class...
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.
给定一个类型为 MyObj
的对象 A
和一个同样类型的对象 B
,在 [=12= 中定义方法的最佳方法是什么] 这将采用 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
关键字) ,出于明显的原因,为了获得引用相等,所以值相等是唯一的方法 - 是否有更有效和务实的方法来做到这一点(反射很慢;手动很笨重),或者没有?
我认为您误解了 reference equals 的真正含义。
当两个变量都引用 相同 对象时,称它们引用相等(*)。两个对象引用不相等,因为那是荒谬的;如果它们是两个不同的对象,那么根据定义它们永远不能被引用相等。
现在,您的方法 SetEqual
可以做的是在相等具有值语义时使两个不同的对象相等。只有您自己知道您的特定类型的相等值语义是什么样的。
(*)请注意,根据该定义,值类型永远不能引用等于任何值,因为值类型变量不存储引用。
嗯,我想到了 4 种方法。 (11/13/2018)编辑:添加方法 4
方法一:
用于从其他 属性 手动检索每个个体并将其存储到对象的当前实例中的代码。
优点:
- (相对)快
- 明确控制哪些属性被拉过来(如果你 想排除一些属性)
- 对每个属性 的深度与浅度的明确控制
缺点:
- 维护
- 当未来的开发者(也许是你,也许不是)将新的 field/property 添加到 class 时很容易错过
它可能看起来像这样:
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;
// ...
}
}
方法二:
编写自定义反射助手 class。我可能会将它设为静态,使用一些 public 静态方法,并在内部将反射类型和必要数据存储在字典或由 Type 键入的内容中,并包含反射信息,这样你就可以避免重复反射键入每个调用。第一次将它用于任何给定类型时,它在计算上仍然会更昂贵,但之后,只要您使用某种缓存,它就会更快。此外,您可以考虑创建一个自定义属性来指示您的反射助手-class 以便 OverrrideObjectByValue 忽略特定的 properties/fields.
优势:
- 几乎没有维护
- 可以用你修饰的属性来写properties/fields 用于指导反射器如何工作
缺点:
- 慢(至少对于初始反射来说是这样,但是如果你缓存它就会 更快)
- 如果您对反射几乎没有经验,那么编写起来很复杂
- 编写它以支持嵌套类型中嵌套类型的深层与浅层复制可能会成为一个固有的递归问题,复杂的属性系统支持深层与浅层情况的粒度控制
你可以这样做...
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...
}
}
方法三:
当你想用 MyObject
的实例 B
的值覆盖 MyObject
的实例 A
时,你可以只使用赋值运算符来实现它们引用相等。请注意:这样做意味着它们是同一个实例,这意味着对 A
的更改会反映在 B
中,因为 A
和 B
是内存中的同一个对象。
优势:
- 最快
- 将来最容易理解(假设您知道引用类型的功能)
- 维护
缺点:
- 不是深拷贝
就这么简单:
// 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 的这个示例不是原始数据的深层复制/覆盖,而是制作两者(在这种情况下我是将它们存储在列表中)相同 - 通过引用。如果对象是不可变的,这将特别有用,因为您知道,这并不违反整个不变性原则...
方法四:
(评论后添加此方法) 这个方法实际上只是语法糖,最好只保留为赋值运算符,但如果出于某种原因你真的想要一个方法,你可以可以这样做...
优势:
- 听起来这可能就是您要找的东西?
- 与使用方法 3 相同(基本上),通过引用设置相等,使用赋值运算符...
缺点:
- 一个简单的
a = b
;就够了...
一些只有基本数据类型的任意自定义类型:
public class CustomType
{
public string Name { get; set; }
public int ID { get; set; }
}
然后你可以有一些带有扩展方法的静态 class...
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.