การเปรียบเทียบคุณสมบัติของวัตถุใน c # [ปิด]


111

นี่คือสิ่งที่ฉันคิดขึ้นเป็นวิธีการในคลาสที่สืบทอดมาจากคลาสอื่น ๆ ของฉัน แนวคิดก็คือช่วยให้สามารถเปรียบเทียบคุณสมบัติของ Objects ประเภทเดียวกันได้อย่างง่ายดาย

ตอนนี้มันใช้งานได้ - แต่เพื่อประโยชน์ในการปรับปรุงคุณภาพของรหัสของฉันฉันคิดว่าฉันจะโยนมันออกไปเพื่อตรวจสอบข้อเท็จจริง จะดีขึ้น / มีประสิทธิภาพมากขึ้น / ฯลฯ ได้อย่างไร?

/// <summary>
/// Compare property values (as strings)
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool PropertiesEqual(object comparisonObject)
{

    Type sourceType = this.GetType();
    Type destinationType = comparisonObject.GetType();

    if (sourceType == destinationType)
    {
        PropertyInfo[] sourceProperties = sourceType.GetProperties();
        foreach (PropertyInfo pi in sourceProperties)
        {
            if ((sourceType.GetProperty(pi.Name).GetValue(this, null) == null && destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null) == null))
            {
                // if both are null, don't try to compare  (throws exception)
            }
            else if (!(sourceType.GetProperty(pi.Name).GetValue(this, null).ToString() == destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null).ToString()))
            {
                // only need one property to be different to fail Equals.
                return false;
            }
        }
    }
    else
    {
        throw new ArgumentException("Comparison object must be of the same type.","comparisonObject");
    }

    return true;
}

2
ดูเพิ่มเติมที่: stackoverflow.com/questions/3060382/…และบางทีstackoverflow.com/questions/986572/…
Marc Gravell

3
คุณรู้หรือไม่ว่าไซต์ SE นี้: codereview.stackexchange.com
wip

มีไลบรารีการเปรียบเทียบวัตถุอยู่สองสามไลบรารี: CompareNETObjects , DeepEqual , AutoCompare , ZCompare ...
nawfal

... และตันของความเท่าเทียมกันทั่วไป implementors Comparer บางแห่งซึ่ง ได้แก่ : MemberwiseEqualityComparer , Equ , SemanticComparison , EqualityComparer , DeepEqualityComparer , ความเท่าเทียมกัน , Equals.Fody กลุ่มหลังอาจถูก จำกัด ในขอบเขตและความยืดหยุ่นในสิ่งที่พวกเขาสามารถบรรลุได้
nawfal

ฉันโหวตให้ปิดคำถามนี้เป็นนอกประเด็นเพราะเป็นของการตรวจสอบโค้ด
Xiaoy312

คำตอบ:


160

ฉันกำลังมองหาตัวอย่างโค้ดที่จะทำบางอย่างที่คล้ายกันเพื่อช่วยในการทดสอบหน่วยการเขียน นี่คือสิ่งที่ฉันใช้

public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class 
  {
     if (self != null && to != null)
     {
        Type type = typeof(T);
        List<string> ignoreList = new List<string>(ignore);
        foreach (System.Reflection.PropertyInfo pi in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
        {
           if (!ignoreList.Contains(pi.Name))
           {
              object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
              object toValue = type.GetProperty(pi.Name).GetValue(to, null);

              if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
              {
                 return false;
              }
           }
        }
        return true;
     }
     return self == to;
  }

แก้ไข:

รหัสเดียวกับด้านบน แต่ใช้วิธี LINQ และส่วนขยาย:

public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
            from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            where !ignoreList.Contains(pi.Name) && pi.GetUnderlyingType().IsSimpleType() && pi.GetIndexParameters().Length == 0
            let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
            let toValue = type.GetProperty(pi.Name).GetValue(to, null)
            where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
            select selfValue;
        return !unequalProperties.Any();
    }
    return self == to;
}

public static class TypeExtensions
   {
      /// <summary>
      /// Determine whether a type is simple (String, Decimal, DateTime, etc) 
      /// or complex (i.e. custom class with public properties and methods).
      /// </summary>
      /// <see cref="http://stackoverflow.com/questions/2442534/how-to-test-if-type-is-primitive"/>
      public static bool IsSimpleType(
         this Type type)
      {
         return
            type.IsValueType ||
            type.IsPrimitive ||
            new[]
            {
               typeof(String),
               typeof(Decimal),
               typeof(DateTime),
               typeof(DateTimeOffset),
               typeof(TimeSpan),
               typeof(Guid)
            }.Contains(type) ||
            (Convert.GetTypeCode(type) != TypeCode.Object);
      }

      public static Type GetUnderlyingType(this MemberInfo member)
      {
         switch (member.MemberType)
         {
            case MemberTypes.Event:
               return ((EventInfo)member).EventHandlerType;
            case MemberTypes.Field:
               return ((FieldInfo)member).FieldType;
            case MemberTypes.Method:
               return ((MethodInfo)member).ReturnType;
            case MemberTypes.Property:
               return ((PropertyInfo)member).PropertyType;
            default:
               throw new ArgumentException
               (
                  "Input MemberInfo must be if type EventInfo, FieldInfo, MethodInfo, or PropertyInfo"
               );
         }
      }
   }

Big T - ค่อนข้างแก่ แต่ก็มีจุดประสงค์ที่ดีสำหรับทั้งการทดสอบและการเปรียบเทียบแบบง่ายๆ .. ขอบคุณ +1
jim tollan

1
นี่เป็นสิ่งที่ดี แต่ฉันพบว่ามันไม่ทำงานกับวัตถุที่ซับซ้อนกว่านี้ ตัวอย่างเช่นฉันมีออบเจ็กต์ที่มีสตริงบางอัน (มันเปรียบเทียบได้ดี) แต่อ็อบเจ็กต์นี้ยังมีรายการของอ็อบเจ็กต์อื่นซึ่งมันเปรียบเทียบไม่ถูกต้องดังนั้นต้องเรียกคืนสิ่งนี้ใหม่
Ryan Thomas

1
ฉันต้องเพิ่มในเกณฑ์ในครั้งแรกที่มีอีกสองเกณฑ์เนื่องจากคุณต้องยกเว้นคุณสมบัติที่จัดทำดัชนีซึ่งทำให้เกิดข้อยกเว้นในกรณีอื่น นี่คือเกณฑ์สำหรับข้อผิดพลาดนี้: pi.GetIndexParameters () ความยาว == 0 และเกณฑ์ที่สองในการแก้ไขปัญหาที่ระบุโดย @RyanThomas คือ: pi.GetUnderlyingType (). IsSimpleType () ดังที่คุณจะเห็น IsSimpleType เป็นและส่วนขยายที่ไม่มีอยู่สำหรับประเภทคลาส ฉันแก้ไขคำตอบเพื่อเพิ่มเงื่อนไขและส่วนขยายเหล่านี้ทั้งหมด
ซามูเอล

64

UPDATE:รุ่นล่าสุดของเปรียบเทียบ-Net-วัตถุที่ตั้งอยู่บนGitHub มีแพคเกจ NuGetและการสอน ก็เรียกได้ว่าชอบ

//This is the comparison class
CompareLogic compareLogic = new CompareLogic();

ComparisonResult result = compareLogic.Compare(person1, person2);

//These will be different, write out the differences
if (!result.AreEqual)
    Console.WriteLine(result.DifferencesString);

หรือหากคุณต้องการเปลี่ยนการกำหนดค่าบางอย่างให้ใช้

CompareLogic basicComparison = new CompareLogic() 
{ Config = new ComparisonConfig()
   { MaxDifferences = propertyCount 
     //add other configurations
   }
};

รายการพารามิเตอร์ที่กำหนดค่าได้ทั้งหมดอยู่ในComparisonConfig.cs

คำตอบเดิม:

ข้อ จำกัด ที่ฉันเห็นในรหัสของคุณ:

  • สิ่งที่ใหญ่ที่สุดคือมันไม่ได้ทำการเปรียบเทียบวัตถุเชิงลึก

  • จะไม่ทำการแยกองค์ประกอบโดยการเปรียบเทียบองค์ประกอบในกรณีที่คุณสมบัติเป็นรายการหรือมีรายการเป็นองค์ประกอบ (ซึ่งสามารถไปได้ n-levels)

  • ไม่คำนึงถึงว่าไม่ควรเปรียบเทียบคุณสมบัติบางประเภท (เช่นคุณสมบัติ Func ที่ใช้เพื่อวัตถุประสงค์ในการกรองเช่นเดียวกับคุณสมบัติในคลาส PagedCollectionView)

  • ไม่ได้ติดตามว่าคุณสมบัติใดที่แตกต่างกันจริง ๆ (ดังนั้นคุณสามารถแสดงในคำยืนยันของคุณได้)

ฉันถูกมองในวันนี้สำหรับการแก้ปัญหาบางอย่างสำหรับหน่วยวัตถุประสงค์ในการทดสอบจะทำสถานที่ให้บริการโดยเปรียบเทียบลึกคุณสมบัติและฉันสิ้นสุดที่ใช้: http://comparenetobjects.codeplex.com

เป็นห้องสมุดฟรีที่มีเพียงชั้นเดียวซึ่งคุณสามารถใช้ได้ดังนี้:

var compareObjects = new CompareObjects()
{
    CompareChildren = true, //this turns deep compare one, otherwise it's shallow
    CompareFields = false,
    CompareReadOnly = true,
    ComparePrivateFields = false,
    ComparePrivateProperties = false,
    CompareProperties = true,
    MaxDifferences = 1,
    ElementsToIgnore = new List<string>() { "Filter" }
};

Assert.IsTrue(
    compareObjects.Compare(objectA, objectB), 
    compareObjects.DifferencesString
);

นอกจากนี้ยังสามารถรวบรวมใหม่สำหรับ Silverlight ได้อย่างง่ายดาย เพียงคัดลอกคลาสหนึ่งไปยังโปรเจ็กต์ Silverlight และลบโค้ดหนึ่งหรือสองบรรทัดสำหรับการเปรียบเทียบที่ไม่มีใน Silverlight เช่นการเปรียบเทียบสมาชิกส่วนตัว


2
Liviu ฉันสังเกตเห็นความคิดเห็นของคุณเกี่ยวกับคลาสที่เข้ากันไม่ได้กับ Silverlight ฉันเพิ่งเปลี่ยนให้เข้ากันได้กับ Silverlight และ Windows Phone 7 รับข้อมูลล่าสุด ดูการเปลี่ยนแปลงชุด 74131 ที่Comparenetobjects.codeplex.com/SourceControl/list/changesets
Greg Finzer

นี่ดูมีแนวโน้ม จะลองดู
DJ Burb

ขอบคุณสำหรับตัวอย่างที่ยอดเยี่ยม! นอกจากนี้IgnoreObjectTypesการตั้งค่าอาจมีประโยชน์เมื่อมีประเภทต่างๆ
Sergey Brunov

เวอร์ชัน 2.0 มีเวอร์ชัน Portable Class Library ที่เข้ากันได้กับ Silverlight 5+, Windows Phone 8+, WinRT 8+, Xamarin IOS และ Xamarin Droid
Greg Finzer

DifferencesStringถูกยกเลิกการแก้ไขในคลาส CompareObjects แต่ตอนนี้คุณสามารถรับได้จาก ComparisonResult แทน:var r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
Mariano Desanze

6

ฉันคิดว่าควรทำตามรูปแบบสำหรับ Override Object # Equals ()
เพื่อคำอธิบายที่ดีขึ้น: อ่านC # ที่มีประสิทธิภาพของ Bill Wagner - รายการที่ 9 ฉันคิดว่า

public override Equals(object obOther)
{
  if (null == obOther)
    return false;
  if (object.ReferenceEquals(this, obOther)
    return true;
  if (this.GetType() != obOther.GetType())
    return false;
  # private method to compare members.
  return CompareMembers(this, obOther as ThisClass);
}
  • นอกจากนี้ในวิธีการตรวจสอบความเท่าเทียมกันคุณควรคืนค่าจริงหรือเท็จ ไม่ว่าจะเท่ากันหรือไม่ .. แทนที่จะโยนข้อยกเว้นกลับเป็นเท็จ
  • ฉันจะพิจารณาแทนที่ Object # Equals
  • แม้ว่าคุณจะต้องพิจารณาสิ่งนี้ แต่การใช้การสะท้อนเพื่อเปรียบเทียบคุณสมบัตินั้นค่อนข้างช้า (ฉันไม่มีตัวเลขสำรอง) นี่เป็นลักษณะการทำงานเริ่มต้นสำหรับ valueType # เท่ากับใน C # และขอแนะนำให้คุณแทนที่ Equals สำหรับประเภทค่าและทำการเปรียบเทียบประสิทธิภาพของสมาชิกอย่างชาญฉลาด (ก่อนหน้านี้ฉันอ่านสิ่งนี้อย่างรวดเร็วเนื่องจากคุณมีคอลเล็กชันของวัตถุคุณสมบัติที่กำหนดเอง ... ฉันไม่ดี)

ปรับปรุง - ธันวาคม 2554:

  • แน่นอนว่าหากประเภทนั้นมีการผลิตเท่ากับ () อยู่แล้วคุณต้องมีแนวทางอื่น
  • หากคุณกำลังใช้สิ่งนี้เพื่อเปรียบเทียบโครงสร้างข้อมูลที่ไม่เปลี่ยนรูปเพื่อวัตถุประสงค์ในการทดสอบโดยเฉพาะคุณไม่ควรเพิ่ม Equals ให้กับคลาสการผลิต (อาจมีคนทดสอบการทดสอบด้วยการเชื่อมโยงการใช้งาน Equals หรือคุณอาจป้องกันการสร้างการใช้งาน Equals ที่จำเป็นในการผลิต) .

ฉันพบปัญหาเกี่ยวกับการแทนที่. Equals () เพราะฉันพยายามใช้สิ่งนี้กับคลาสพื้นฐานที่ได้รับการสืบทอดมา ... เพราะฉันไม่รู้คีย์สำหรับคลาสที่จะถูกรันฉันทำไม่ได้ ใช้การแทนที่ที่เหมาะสมสำหรับ GetHasCode () (req'd เมื่อคุณแทนที่ Equals ())
nailitdown

ข้อกำหนดคือถ้า objA.Equals (objB) ตามด้วย objA.GetHashCode () == objB.GetHashCode () GetHashCode ไม่ควรขึ้นอยู่กับสถานะ / ข้อมูลที่ไม่แน่นอนของคลาส ... ฉันไม่ได้รับสิ่งที่คุณหมายถึงโดยคีย์สำหรับคลาส .. ดูเหมือนว่ามีบางอย่างที่สามารถแก้ไขได้ ประเภทพื้นฐานไม่มี 'คีย์'?
Gishu

6

หากประสิทธิภาพไม่สำคัญคุณสามารถทำให้เป็นอนุกรมและเปรียบเทียบผลลัพธ์ได้:

var serializer = new XmlSerializer(typeof(TheObjectType));
StringWriter serialized1 = new StringWriter(), serialized2 = new StringWriter();
serializer.Serialize(serialized1, obj1);
serializer.Serialize(serialized2, obj2);
bool areEqual = serialized1.ToString() == serialized2.ToString();

4
ลองสิ่งนี้เมื่อก่อนคุณจะสงสัยว่ามีวัตถุจำนวนเท่าใดที่ไม่ต่อเนื่องกัน ...
Offler

5

ฉันคิดว่าคำตอบของBig Tค่อนข้างดี แต่การเปรียบเทียบที่ลึกซึ้งหายไปดังนั้นฉันจึงปรับแต่งเล็กน้อย:

using System.Collections.Generic;
using System.Reflection;

/// <summary>Comparison class.</summary>
public static class Compare
{
    /// <summary>Compare the public instance properties. Uses deep comparison.</summary>
    /// <param name="self">The reference object.</param>
    /// <param name="to">The object to compare.</param>
    /// <param name="ignore">Ignore property with name.</param>
    /// <typeparam name="T">Type of objects.</typeparam>
    /// <returns><see cref="bool">True</see> if both objects are equal, else <see cref="bool">false</see>.</returns>
    public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
    {
        if (self != null && to != null)
        {
            var type = self.GetType();
            var ignoreList = new List<string>(ignore);
            foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (ignoreList.Contains(pi.Name))
                {
                    continue;
                }

                var selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                var toValue = type.GetProperty(pi.Name).GetValue(to, null);

                if (pi.PropertyType.IsClass && !pi.PropertyType.Module.ScopeName.Equals("CommonLanguageRuntimeLibrary"))
                {
                    // Check of "CommonLanguageRuntimeLibrary" is needed because string is also a class
                    if (PublicInstancePropertiesEqual(selfValue, toValue, ignore))
                    {
                        continue;
                    }

                    return false;
                }

                if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
                {
                    return false;
                }
            }

            return true;
        }

        return self == to;
    }
}

4

ฉันจะเพิ่มบรรทัดต่อไปนี้ในเมธอด PublicInstancePropertiesEqual เพื่อหลีกเลี่ยงข้อผิดพลาดในการคัดลอกและวาง:

Assert.AreNotSame(self, to);

2

คุณแทนที่. ToString () บนวัตถุทั้งหมดของคุณที่อยู่ในคุณสมบัติหรือไม่? มิฉะนั้นการเปรียบเทียบครั้งที่สองอาจกลับมาพร้อมกับโมฆะ

นอกจากนี้ในการเปรียบเทียบครั้งที่สองนั้นฉันอยู่ในรั้วเกี่ยวกับโครงสร้างของ! (A == B) เทียบกับ (A! = B) ในแง่ของความสามารถในการอ่านหกเดือน / สองปีนับจากนี้ เส้นนั้นค่อนข้างกว้างซึ่งก็ใช้ได้ถ้าคุณมีจอภาพที่กว้าง แต่อาจพิมพ์ออกมาได้ไม่ดีนัก (nitpick)

ออบเจ็กต์ทั้งหมดของคุณใช้คุณสมบัติเพื่อให้โค้ดนี้ใช้ได้หรือไม่? มีข้อมูลภายในที่ไม่เหมาะสมบางอย่างที่อาจแตกต่างจากออบเจ็กต์หนึ่งไปยังอีกวัตถุหนึ่ง แต่ข้อมูลที่เปิดเผยทั้งหมดเหมือนกันหรือไม่ ฉันกำลังคิดถึงข้อมูลบางอย่างที่สามารถเปลี่ยนแปลงได้ตลอดเวลาเช่นเครื่องกำเนิดตัวเลขสุ่มสองตัวที่เกิดขึ้นเพื่อตีหมายเลขเดียวกัน ณ จุดหนึ่ง แต่จะสร้างลำดับข้อมูลที่แตกต่างกันสองลำดับหรือเพียงแค่ข้อมูลใด ๆ ที่ไม่เปิดเผย ผ่านอินเทอร์เฟซคุณสมบัติ


จุดดี -! = ... เห็นด้วยชี้ไป ToString () เป็นความพยายามในการแก้ไขปัญหา GetValue ส่งคืนวัตถุ (ดังนั้นการเปรียบเทียบจะเป็นเท็จเสมอเนื่องจากเป็นการเปรียบเทียบอ้างอิง) .. มีวิธีที่ดีกว่านี้หรือไม่?
nailitdown

หาก GetValue ส่งคืนวัตถุคุณสามารถเรียกคืนผ่านฟังก์ชันนี้อีกครั้งได้หรือไม่ กล่าวคือเรียก PropertiesEqual บนวัตถุที่ส่งคืน?
mmr

1

หากคุณกำลังเปรียบเทียบเฉพาะออบเจ็กต์ประเภทเดียวกันหรือต่อไปในห่วงโซ่การสืบทอดทำไมไม่ระบุพารามิเตอร์เป็นประเภทฐานของคุณแทนที่จะเป็นอ็อบเจ็กต์

ทำการตรวจสอบค่าว่างบนพารามิเตอร์ด้วยเช่นกัน

นอกจากนี้ฉันจะใช้ 'var' เพื่อทำให้โค้ดอ่านง่ายขึ้น (ถ้าเป็นรหัส c # 3)

นอกจากนี้หากวัตถุมีประเภทการอ้างอิงเป็นคุณสมบัติคุณก็แค่เรียก ToString () ซึ่งไม่ได้เปรียบเทียบค่าจริงๆ หาก ToString ไม่ได้ถูกเขียนทับมันจะส่งคืนชื่อประเภทเป็นสตริงซึ่งสามารถส่งคืนค่าบวกเท็จได้


จุดที่ดีเกี่ยวกับประเภทการอ้างอิง - ในกรณีของฉันมันไม่สำคัญ แต่ก็มีโอกาสที่ดี
nailitdown

1

สิ่งแรกที่ฉันจะแนะนำคือแยกการเปรียบเทียบจริงเพื่อให้อ่านได้ง่ายขึ้น (ฉันได้นำ ToString () ออกมาด้วย - จำเป็นหรือไม่):

else {
    object originalProperty = sourceType.GetProperty(pi.Name).GetValue(this, null);
    object comparisonProperty = destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null);

    if (originalProperty != comparisonProperty)
        return false;

ข้อเสนอแนะต่อไปคือการลดการใช้การสะท้อนให้มากที่สุด - มันช้ามาก ฉันหมายความว่าช้าจริงๆ หากคุณจะดำเนินการนี้ฉันขอแนะนำให้แคชการอ้างอิงคุณสมบัติ ฉันไม่คุ้นเคยกับ Reflection API อย่างใกล้ชิดดังนั้นหากสิ่งนี้ไม่ดีนักให้ปรับเปลี่ยนเพื่อให้คอมไพล์:

// elsewhere
Dictionary<object, Property[]> lookupDictionary = new Dictionary<object, Property[]>;

Property[] objectProperties = null;
if (lookupDictionary.ContainsKey(sourceType)) {
  objectProperties = lookupProperties[sourceType];
} else {
  // build array of Property references
  PropertyInfo[] sourcePropertyInfos = sourceType.GetProperties();
  Property[] sourceProperties = new Property[sourcePropertyInfos.length];
  for (int i=0; i < sourcePropertyInfos.length; i++) {
    sourceProperties[i] = sourceType.GetProperty(pi.Name);
  }
  // add to cache
  objectProperties = sourceProperties;
  lookupDictionary[object] = sourceProperties;
}

// loop through and compare against the instances

อย่างไรก็ตามฉันต้องบอกว่าฉันเห็นด้วยกับโปสเตอร์อื่น ๆ กลิ่นนี้ขี้เกียจและไม่มีประสิทธิภาพ คุณควรใช้ IComparable แทน :-)


ฉันแค่มองไปที่ IComparable แต่ดูเหมือนว่ามันใช้สำหรับการเรียงลำดับและการเรียงลำดับ .. มันมีประโยชน์มากสำหรับการเปรียบเทียบความเท่าเทียมกันของวัตถุสองชิ้นหรือไม่?
nailitdown

แน่นอนเพราะ .Equals (อ็อบเจกต์ o) ถูกกำหนดไว้เช่นนี้ CompareTo (o) == 0 ดังนั้นเท่ากับใช้ ComparesTo () เพื่อกำหนดความเท่าเทียมกัน สิ่งนี้จะมีประสิทธิภาพ (และการปฏิบัติตามมาตรฐาน) มากกว่าการใช้การไตร่ตรอง
tsimon

ฉันอาจเข้าใจผิดโดยสมมติว่ามีการใช้ Equals (หรือควรนำไปใช้) โดยอ้างอิงถึง CompareTo () คุณควรพิจารณาการลบล้าง Equals ตามที่อธิบายไว้ที่นี่: stackoverflow.com/questions/104158/…
tsimon

1

นี่คือการแก้ไขหนึ่งเพื่อให้ถือว่า null = null เท่ากับ

 private bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
        {
            if (self != null && to != null)
            {
                Type type = typeof(T);
                List<string> ignoreList = new List<string>(ignore);
                foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (!ignoreList.Contains(pi.Name))
                    {
                        object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                        object toValue = type.GetProperty(pi.Name).GetValue(to, null);
                        if (selfValue != null)
                        {
                            if (!selfValue.Equals(toValue))
                                return false;
                        }
                        else if (toValue != null)
                            return false;
                    }
                }
                return true;
            }
            return self == to;
        }

จะเกิดอะไรขึ้นถ้าฉันมีกราฟออบเจ็กต์แบบลึกวิธีที่ดีที่สุดในการใช้ด้านบนเพื่อส่งคืนรายการคุณสมบัติเก่าและใหม่ที่เปลี่ยนแปลงไปจะเป็นอย่างไร
Rod

1

ฉันลงเอยด้วยการทำสิ่งนี้:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb)
            {
                return false;
            }
        }
        return true;
    }

การใช้งาน:

    if (Compare<ObjectType>(a, b))

ปรับปรุง

หากคุณต้องการละเว้นคุณสมบัติบางอย่างตามชื่อ:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b, params string[] ignore)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb && ignore.Where(x => x == a.GetType().GetProperties()[i].Name).Count() == 0)
            {
                return false;
            }
        }
        return true;
    }

การใช้งาน:

    if (MyFunction.Compare<ObjType>(a, b, "Id","AnotherProp"))

1

คุณสามารถเพิ่มประสิทธิภาพโค้ดของคุณได้โดยโทรหา GetProperties เพียงครั้งเดียวต่อประเภท:

public static string ToStringNullSafe(this object obj)
{
    return obj != null ? obj.ToString() : String.Empty;
}
public static bool Compare<T>(T a, T b, params string[] ignore)
{
    var aProps = a.GetType().GetProperties();
    var bProps = b.GetType().GetProperties();
    int count = aProps.Count();
    string aa, bb;
    for (int i = 0; i < count; i++)
    {
        aa = aProps[i].GetValue(a, null).ToStringNullSafe();
        bb = bProps[i].GetValue(b, null).ToStringNullSafe();
        if (aa != bb && ignore.Where(x => x == aProps[i].Name).Count() == 0)
        {
            return false;
        }
    }
    return true;
}

1

เพื่อความสมบูรณ์ฉันต้องการเพิ่มการอ้างอิงไปที่ http://www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflection มันมีตรรกะที่สมบูรณ์มากกว่าคำตอบอื่น ๆ ส่วนใหญ่ในหน้านี้

อย่างไรก็ตามฉันชอบไลบรารี Compare-Net-Objects https://github.com/GregFinzer/Compare-Net-Objects (อ้างถึงโดยคำตอบของLiviu Trifoi ) ห้องสมุดมีแพ็คเกจ NuGet http://www.nuget.org/packages/ CompareNETObjectsและตัวเลือกมากมายในการกำหนดค่า


1

nullตรวจสอบให้แน่ใจวัตถุไม่ได้

มีobj1และobj2:

if(obj1 == null )
{
   return false;
}
return obj1.Equals( obj2 );

จะเป็นอย่างไรถ้าทั้งคู่เป็นโมฆะ มันไม่เท่ากันเหรอ?
mmr

จุดที่ดีเกี่ยวกับ null ในกรณีของฉันที่ใช้. Equals () ดูเหมือนจะไม่ทำงานซึ่งเป็นสาเหตุที่ฉันคิดวิธีแก้ปัญหานี้ขึ้นมา
nailitdown

กรณีที่ฉันกำลังทดสอบคือสองออบเจ็กต์หนึ่งสร้างขึ้นใหม่หนึ่งชิ้นจากเซสชัน การเปรียบเทียบทั้งสองกับ. Equals () ส่งกลับเท็จแม้ว่าทั้งสองจะมีค่าคุณสมบัติเหมือนกัน
ก็ตาม

0

สิ่งนี้ใช้ได้แม้ว่าวัตถุจะแตกต่างกัน คุณสามารถปรับแต่งเมธอดในคลาสยูทิลิตี้ได้บางทีคุณอาจต้องการเปรียบเทียบคุณสมบัติส่วนตัวด้วย ...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

class ObjectA
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }

    public string FieldA;
    public DateTime FieldB;
}

class ObjectB
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }


    public string FieldA;
    public DateTime FieldB;


}

class Program
{
    static void Main(string[] args)
    {
        // create two objects with same properties
        ObjectA a = new ObjectA() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };
        ObjectB b = new ObjectB() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };

        // add fields to those objects
        a.FieldA = "hello";
        b.FieldA = "Something differnt";

        if (a.ComparePropertiesTo(b))
        {
            Console.WriteLine("objects have the same properties");
        }
        else
        {
            Console.WriteLine("objects have diferent properties!");
        }


        if (a.CompareFieldsTo(b))
        {
            Console.WriteLine("objects have the same Fields");
        }
        else
        {
            Console.WriteLine("objects have diferent Fields!");
        }

        Console.Read();
    }
}

public static class Utilities
{
    public static bool ComparePropertiesTo(this Object a, Object b)
    {
        System.Reflection.PropertyInfo[] properties = a.GetType().GetProperties(); // get all the properties of object a

        foreach (var property in properties)
        {
            var propertyName = property.Name;

            var aValue = a.GetType().GetProperty(propertyName).GetValue(a, null);
            object bValue;

            try // try to get the same property from object b. maybe that property does
                // not exist! 
            {
                bValue = b.GetType().GetProperty(propertyName).GetValue(b, null);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
                continue;

            if (aValue == null && bValue != null)
                return false;

            if (aValue != null && bValue == null)
               return false;

            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }



    public static bool CompareFieldsTo(this Object a, Object b)
    {
        System.Reflection.FieldInfo[] fields = a.GetType().GetFields(); // get all the properties of object a

        foreach (var field in fields)
        {
            var fieldName = field.Name;

            var aValue = a.GetType().GetField(fieldName).GetValue(a);

            object bValue;

            try // try to get the same property from object b. maybe that property does
            // not exist! 
            {
                bValue = b.GetType().GetField(fieldName).GetValue(b);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
               continue;

            if (aValue == null && bValue != null)
               return false;

            if (aValue != null && bValue == null)
               return false;


            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }


}

รหัสนั้นไม่มีประสิทธิภาพ 100% มันไม่ทำงานในบางสถานการณ์ตัวอย่างเช่นหากมีคุณสมบัติของวัตถุประเภท
Tono Nam

0

อัปเดตคำตอบของ Liviu ด้านบน - CompareObjects.DifferencesString เลิกใช้งานแล้ว

สิ่งนี้ใช้ได้ดีในการทดสอบหน่วย:

CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(object1, object2);
Assert.IsTrue(result.AreEqual);

1
เป็นเรื่องดีที่คุณแก้ไขการกีดกัน แต่ฉันคิดว่าคำตอบนี้ควรเป็นความคิดเห็นในคำตอบของ Liviu โดยเฉพาะอย่างยิ่งเนื่องจากโค้ดตัวอย่างของคุณ (เทียบกับ Liviu) ไม่มีพารามิเตอร์ของ CompareLogic (ซึ่งฉันแน่ใจว่าสำคัญ) และข้อความยืนยัน (ซึ่งเป็นรหัสที่เลิกใช้แล้ว) คำยืนยันสามารถแก้ไขได้ด้วย:Assert.IsTrue(result.AreEqual, result.DifferencesString);
Mariano Desanze

0

วิธีการนี้จะได้รับการเรียนและเปรียบเทียบค่าสำหรับแต่ละproperties propertyถ้ามีค่าที่แตกต่างกันก็จะอื่นมันจะreturn falsereturn true

public static bool Compare<T>(T Object1, T object2)
{
    //Get the type of the object
    Type type = typeof(T);

    //return false if any of the object is false
    if (Object1 == null || object2 == null)
        return false;

    //Loop through each properties inside class and get values for the property from both the objects and compare
    foreach (System.Reflection.PropertyInfo property in type.GetProperties())
    {
        if (property.Name != "ExtensionData")
        {
            string Object1Value = string.Empty;
            string Object2Value = string.Empty;
            if (type.GetProperty(property.Name).GetValue(Object1, null) != null)
                Object1Value = type.GetProperty(property.Name).GetValue(Object1, null).ToString();
            if (type.GetProperty(property.Name).GetValue(object2, null) != null)
                Object2Value = type.GetProperty(property.Name).GetValue(object2, null).ToString();
            if (Object1Value.Trim() != Object2Value.Trim())
            {
                return false;
            }
        }
    }
    return true;
}

การใช้งาน:

bool isEqual = Compare<Employee>(Object1, Object2)


0

เพื่อขยายคำตอบของ @nawfal: s ฉันใช้สิ่งนี้เพื่อทดสอบวัตถุประเภทต่างๆในการทดสอบหน่วยของฉันเพื่อเปรียบเทียบชื่อคุณสมบัติที่เท่ากัน ในเอนทิตีฐานข้อมูลกรณีของฉันและ DTO

ใช้แบบนี้ในการทดสอบของฉัน

Assert.IsTrue(resultDto.PublicInstancePropertiesEqual(expectedEntity));



public static bool PublicInstancePropertiesEqual<T, Z>(this T self, Z to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var type2 = typeof(Z);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
           from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
           where !ignoreList.Contains(pi.Name)
           let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
           let toValue = type2.GetProperty(pi.Name).GetValue(to, null)
           where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
           select selfValue;
           return !unequalProperties.Any();
    }
    return self == null && to == null;
}

0

บางครั้งคุณไม่ต้องการเปรียบเทียบคุณสมบัติสาธารณะทั้งหมดและต้องการเปรียบเทียบเฉพาะส่วนย่อยของพวกเขาดังนั้นในกรณีนี้คุณสามารถย้ายตรรกะเพื่อเปรียบเทียบรายการคุณสมบัติที่ต้องการกับคลาสนามธรรม

public abstract class ValueObject<T> where T : ValueObject<T>
{
    protected abstract IEnumerable<object> GetAttributesToIncludeInEqualityCheck();

    public override bool Equals(object other)
    {
        return Equals(other as T);
    }

    public bool Equals(T other)
    {
        if (other == null)
        {
            return false;
        }

        return GetAttributesToIncludeInEqualityCheck()
            .SequenceEqual(other.GetAttributesToIncludeInEqualityCheck());
    }

    public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
    {
        return !(left == right);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        foreach (var obj in this.GetAttributesToIncludeInEqualityCheck())
            hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());

        return hash;
    }
}

และใช้คลาสนามธรรมนี้ในภายหลังเพื่อเปรียบเทียบวัตถุ

public class Meters : ValueObject<Meters>
{
    ...

    protected decimal DistanceInMeters { get; private set; }

    ...

    protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
    {
        return new List<Object> { DistanceInMeters };
    }
}

0

โซลูชันของฉันได้รับแรงบันดาลใจจากคำตอบของ Aras Alenin ด้านบนซึ่งฉันได้เพิ่มการเปรียบเทียบวัตถุหนึ่งระดับและวัตถุที่กำหนดเองสำหรับผลการเปรียบเทียบ ฉันสนใจที่จะรับชื่อคุณสมบัติพร้อมชื่อวัตถุ:

    public static IEnumerable<ObjectPropertyChanged> GetPublicSimplePropertiesChanged<T>(this T previous, T proposedChange,
     string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, true, null, null);
    }

    public static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, false, null, null);
    }

    /// <summary>
    /// Gets the names of the public properties which values differs between first and second objects.
    /// Considers 'simple' properties AND for complex properties without index, get the simple properties of the children objects.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="previous">The previous object.</param>
    /// <param name="proposedChange">The second object which should be the new one.</param>
    /// <param name="namesOfPropertiesToBeIgnored">The names of the properties to be ignored.</param>
    /// <param name="simpleTypeOnly">if set to <c>true</c> consider simple types only.</param>
    /// <param name="parentTypeString">The parent type string. Meant only for recursive call with simpleTypeOnly set to <c>true</c>.</param>
    /// <param name="secondType">when calling recursively, the current type of T must be clearly defined here, as T will be more generic (using base class).</param>
    /// <returns>
    /// the names of the properties
    /// </returns>
    private static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored, bool simpleTypeOnly, string parentTypeString, Type secondType) where T : class
    {
        List<ObjectPropertyChanged> propertiesChanged = new List<ObjectPropertyChanged>();

        if (previous != null && proposedChange != null)
        {
            var type = secondType == null ? typeof(T) : secondType;
            string typeStr = parentTypeString + type.Name + ".";
            var ignoreList = namesOfPropertiesToBeIgnored.CreateList();
            IEnumerable<IEnumerable<ObjectPropertyChanged>> genericPropertiesChanged =
                from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                where !ignoreList.Contains(pi.Name) && pi.GetIndexParameters().Length == 0 
                    && (!simpleTypeOnly || simpleTypeOnly && pi.PropertyType.IsSimpleType())
                let firstValue = type.GetProperty(pi.Name).GetValue(previous, null)
                let secondValue = type.GetProperty(pi.Name).GetValue(proposedChange, null)
                where firstValue != secondValue && (firstValue == null || !firstValue.Equals(secondValue))
                let subPropertiesChanged = simpleTypeOnly || pi.PropertyType.IsSimpleType()
                    ? null
                    : GetPublicGenericPropertiesChanged(firstValue, secondValue, namesOfPropertiesToBeIgnored, true, typeStr, pi.PropertyType)
                let objectPropertiesChanged = subPropertiesChanged != null && subPropertiesChanged.Count() > 0
                    ? subPropertiesChanged
                    : (new ObjectPropertyChanged(proposedChange.ToString(), typeStr + pi.Name, firstValue.ToStringOrNull(), secondValue.ToStringOrNull())).CreateList()
                select objectPropertiesChanged;

            if (genericPropertiesChanged != null)
            {   // get items from sub lists
                genericPropertiesChanged.ForEach(a => propertiesChanged.AddRange(a));
            }
        }
        return propertiesChanged;
    }

การใช้คลาสต่อไปนี้เพื่อเก็บผลการเปรียบเทียบ

[System.Serializable]
public class ObjectPropertyChanged
{
    public ObjectPropertyChanged(string objectId, string propertyName, string previousValue, string changedValue)
    {
        ObjectId = objectId;
        PropertyName = propertyName;
        PreviousValue = previousValue;
        ProposedChangedValue = changedValue;
    }

    public string ObjectId { get; set; }

    public string PropertyName { get; set; }

    public string PreviousValue { get; set; }

    public string ProposedChangedValue { get; set; }
}

และแบบทดสอบหน่วยตัวอย่าง:

    [TestMethod()]
    public void GetPublicGenericPropertiesChangedTest1()
    {
        // Define objects to test
        Function func1 = new Function { Id = 1, Description = "func1" };
        Function func2 = new Function { Id = 2, Description = "func2" };
        FunctionAssignment funcAss1 = new FunctionAssignment
        {
            Function = func1,
            Level = 1
        };
        FunctionAssignment funcAss2 = new FunctionAssignment
        {
            Function = func2,
            Level = 2
        };

        // Main test: read properties changed
        var propertiesChanged = Utils.GetPublicGenericPropertiesChanged(funcAss1, funcAss2, null);

        Assert.IsNotNull(propertiesChanged);
        Assert.IsTrue(propertiesChanged.Count == 3);
        Assert.IsTrue(propertiesChanged[0].PropertyName == "FunctionAssignment.Function.Description");
        Assert.IsTrue(propertiesChanged[1].PropertyName == "FunctionAssignment.Function.Id");
        Assert.IsTrue(propertiesChanged[2].PropertyName == "FunctionAssignment.Level");
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.