/// <summary> /// Appends the fields and values defined by the given object of the given Class. /// </summary> /// <param name="builder">the builder to Append to</param> /// <param name="clazz">the class to Append details of</param> /// <param name="lhs">the left hand object</param> /// <param name="rhs">the right hand object</param> /// <param name="useTransients">whether to test transient fields</param> private static void ReflectionAppend( Object lhs, Object rhs, Type clazz, EqualsBuilder builder, bool useTransients) { /* * In Java version of this ReflectionAppend, we have to call * AccessibleObject.setAccessible() right after class.GetFields() to * make non-public fields accessible. In C#, it is easier to do. We simply * add BindingFlags.NonPublic, which makes non-public fields accessible * (subject to security manager restrictions, of course). */ FieldInfo[] fields = clazz.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); for (int i = 0; i < fields.Length && builder.isEqual; i++) { FieldInfo f = fields[i]; if ((f.Name.IndexOf('$') == -1) && (useTransients || !isTransient(f)) && !f.IsStatic) { try { builder.Append(f.GetValue(lhs), f.GetValue(rhs)); } /* * According to FieldInfo's documentation, getValue() can throw the * following exceptions: TargetException, NotSupportedException, * FieldAccessException and ArgumentException. * * TargetException is thrown if the field is non-static and obj is * a null reference. In our case, the field is non-static (because of * BindingFlags.Instance) but obj should never be null because of * null checks in the calling method (i.e. reflectionEquals()). * I guess we can just throw an unexpected exception. * * NotSupportedException is thrown if the field is marked Literal, but * the field does not have one of the accepted literal types. Literal * means that the field's value is a compile-time (static or early * bound) constant. I think this exception can be just eaten because * constants should always be equal in lhs and rhs and default value * of isEqual is true. * * FieldAccessException is thrown if the caller does not have * permission to access this field. If this code is fully trusted * (not sure how to verify this), access restrictions are ignored. * This means that private fields are accessible. If this code is not * fully trusted, it can still access private fields if this code * has been granted ReflectedPermission with the * ReflectionPermisionFlag.RestrictedMemberAccess flag and if the * grant set of the non-public members is restricted to the caller's * grant set, or subset thereof. Whew, that's a mouthful to say! * I guess it's best just to let FieldAccessException bubble up * to callers so that user can grant permissions, if desired. * * Finally, ArgumentException is thrown if the method is neither * declared nor inherited by the class of obj. This could happen * if lhs is a subclass of rhs (or vice-versa) and the field is * declared in the subclass. In Java, Field.get() would throw * IllegalArgumentException in this case. In Java version of * reflectionAppend(), IllegalArgumentException * bubbles up to reflectionEquals(), where it is dealt with. * It seems logical that use the same technique in the C# * version. That is, we allow ArgumentException to bubble up * to ReflectionEquals() and deal with it there. */ catch (TargetException te) { throw new Exception("Unexpected TargetException", te); } catch (NotSupportedException nse) { // eat it! } /* Don't catch FieldAccessException and ArgumentException so that * they can bubble up to caller. Alternatively, we could catch and * rethrow. */ //catch (FieldAccessException fae) { throw; } //catch (ArgumentException fae) { throw; } } } }
/// <summary> /// This method uses reflection to determine if the two Object /// are equal. /// /// It uses AccessibleObject.setAccessible to gain access to private /// fields. This means that it will throw a security exception if run under /// a security manager, if the permissions are not set up correctly. It is also /// not as efficient as testing explicitly. /// /// If the testTransients parameter is set to true, transient /// members will be tested, otherwise they are ignored, as they are likely /// derived fields, and not part of the value of the Object. /// /// Static fields will not be included. Superclass fields will be appended /// up to and including the specified superclass. A null superclass is treated /// as java.lang.Object. /// </summary> /// <param name="lhs">this object</param> /// <param name="rhs">the other object</param> /// <param name="testTransients">whether to include transient fields</param> /// <param name="reflectUpToClass">the superclass to reflect up to (inclusive), may be null</param> /// <returns>true if the two Objects have tested equals.</returns> public static bool ReflectionEquals(Object lhs, Object rhs, bool testTransients, Type reflectUpToClass) { if (lhs == rhs) { return(true); } if (lhs == null || rhs == null) { return(false); } // Find the leaf class since there may be transients in the leaf // class or in classes between the leaf and root. // If we are not testing transients or a subclass has no ivars, // then a subclass can test equals to a superclass. Type lhsClass = lhs.GetType(); Type rhsClass = rhs.GetType(); Type testClass; if (lhsClass.IsInstanceOfType(rhs)) { testClass = lhsClass; if (!rhsClass.IsInstanceOfType(lhs)) { // rhsClass is a subclass of lhsClass testClass = rhsClass; } } else if (rhsClass.IsInstanceOfType(lhs)) { testClass = rhsClass; if (!lhsClass.IsInstanceOfType(rhs)) { // lhsClass is a subclass of rhsClass testClass = lhsClass; } } else { // The two classes are not related. return(false); } EqualsBuilder equalsBuilder = new EqualsBuilder(); try { ReflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients); while (testClass.BaseType != null && testClass != reflectUpToClass) { testClass = testClass.BaseType; ReflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients); } } catch (ArgumentException e) { // In this case, we tried to test a subclass vs. a superclass and // the subclass has ivars or the ivars are transient and // we are testing transients. // If a subclass has ivars that we are trying to test them, we get an // exception and we know that the objects are not equal. return(false); } return(equalsBuilder.IsEquals()); }