So sánh thuộc tính đối tượng trong c # [đóng]


111

Đây là những gì tôi đã nghĩ ra như một phương thức trên một lớp được nhiều lớp khác của tôi kế thừa. Ý tưởng là nó cho phép so sánh đơn giản giữa các thuộc tính của các Đối tượng cùng Loại.

Bây giờ, điều này đã hoạt động - nhưng vì lợi ích của việc cải thiện chất lượng mã của tôi, tôi nghĩ rằng tôi sẽ ném nó ra ngoài để xem xét kỹ lưỡng. Làm thế nào nó có thể tốt hơn / hiệu quả hơn / vv.?

/// <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;
}


3
Bằng cách này bạn có biết trang web này SE: codereview.stackexchange.com
WIP

Có một vài thư viện so sánh đối tượng: CompareNETObjects , DeepEqual , AutoCompare , ZCompare ...
nawfal

... và rất nhiều trình thực hiện so sánh bình đẳng chung, một số trong số đó là: MemberwiseEqualityComparer , Equ , SemanticComparison , EqualityComparer , DeepEqualityComparer , Equality , Equals.Fody . Nhóm thứ hai có thể bị hạn chế về phạm vi và tính linh hoạt đối với những gì họ có thể đạt được.
nawfal

Tôi bỏ phiếu để đóng câu hỏi này như off-topic vì nó thuộc về xem xét mã
Xiaoy312

Câu trả lời:


160

Tôi đang tìm kiếm một đoạn mã có tác dụng tương tự để giúp viết bài kiểm tra đơn vị. Đây là những gì tôi đã sử dụng.

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;
  }

BIÊN TẬP:

Mã tương tự như trên nhưng sử dụng các phương pháp LINQ và Extension:

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 - khá cũ, nhưng chắc chắn phục vụ một mục đích tuyệt vời cho cả thử nghiệm và so sánh đơn giản .. cảm ơn +1
jim số điện thoại

1
Điều này là tốt, nhưng tôi thấy nó không hoạt động với các đối tượng phức tạp hơn. Ví dụ: tôi có một đối tượng với một số chuỗi (nó so sánh chúng tốt) nhưng sau đó đối tượng này cũng có một danh sách của đối tượng khác, mà nó không so sánh chính xác, vì vậy cần phải lặp lại điều này bằng cách nào đó.
Ryan Thomas

1
Tôi đã phải thêm vào tiêu chí trong đầu tiên, nơi có thêm hai tiêu chí vì bạn phải loại trừ các thuộc tính được lập chỉ mục ném ngoại lệ trong trường hợp khác. Đây là tiêu chí cho lỗi này: pi.GetIndexParameters (). Length == 0. Và tiêu chí thứ hai để giải quyết vấn đề được nêu bởi @RyanThomas là: pi.GetUnderlyingType (). IsSimpleType (). Như bạn sẽ thấy, IsSimpleType là và phần mở rộng không tồn tại cho Loại lớp. Tôi đã sửa đổi câu trả lời để thêm tất cả các điều kiện này và phần mở rộng.
Samuel

64

CẬP NHẬT: Phiên bản mới nhất của Compare-Net-Objects nằm trên GitHub , có gói NuGetHướng dẫn . Nó có thể được gọi là

//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);

Hoặc nếu bạn cần thay đổi một số cấu hình, hãy sử dụng

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

Danh sách đầy đủ các tham số có thể định cấu hình có trong ComparisonConfig.cs

Câu trả lời ban đầu:

Những hạn chế tôi thấy trong mã của bạn:

  • Điểm lớn nhất là nó không thực hiện so sánh đối tượng sâu.

  • Nó không thực hiện so sánh phần tử theo phần tử trong trường hợp các thuộc tính là danh sách hoặc chứa danh sách dưới dạng phần tử (điều này có thể tăng cấp n).

  • Không tính đến việc không nên so sánh một số loại thuộc tính (ví dụ: thuộc tính Func được sử dụng cho mục đích lọc, như thuộc tính trong lớp PagedCollectionView).

  • Nó không theo dõi những thuộc tính thực sự khác nhau (vì vậy bạn có thể hiển thị trong các xác nhận của mình).

Hôm nay tôi đang tìm kiếm một số giải pháp cho mục đích kiểm tra đơn vị để thực hiện so sánh sâu thuộc tính theo đặc tính và tôi đã sử dụng: http://comparenetobjects.codeplex.com .

Đây là một thư viện miễn phí chỉ với một lớp mà bạn có thể đơn giản sử dụng như sau:

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
);

Ngoài ra, nó có thể được biên dịch lại dễ dàng cho Silverlight. Chỉ cần sao chép một lớp vào dự án Silverlight và xóa một hoặc hai dòng mã để so sánh không có trong Silverlight, như so sánh các thành viên riêng.


2
Liviu, tôi nhận thấy nhận xét của bạn về việc lớp học không tương thích với Silverlight. Tôi vừa thay đổi nó để tương thích với Silverlight và Windows Phone 7. Hãy cập nhật phiên bản mới nhất. Xem tập hợp thay đổi 74131 tại so sánhnetobjects.codeplex.com/SourceControl/list/changesets
Greg Finzer

Điều này có vẻ đầy hứa hẹn. Sẽ dùng thử
DJ Burb

Cảm ơn bạn vì ví dụ tuyệt vời! Ngoài ra, IgnoreObjectTypescài đặt có thể hữu ích khi có nhiều loại khác nhau.
Sergey Brunov

Phiên bản 2.0 có phiên bản Thư viện lớp di động tương thích với Silverlight 5+, Windows Phone 8+, WinRT 8+, Xamarin IOS và Xamarin Droid
Greg Finzer

DifferencesStringđã được mô tả trong lớp CompareObjects. Nhưng bây giờ bạn có thể nhận được điều đó từ ComparisonResult để thay thế:var r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
Mariano Desanze

6

Tôi nghĩ tốt nhất nên làm theo mẫu cho Ghi đè Đối tượng # Bằng ()
Để có mô tả tốt hơn: Đọc C # Hiệu quả của Bill Wagner - Mục 9 Tôi nghĩ

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);
}
  • Ngoài ra, trong các phương thức kiểm tra sự bằng nhau, bạn nên trả về true hoặc false. hoặc chúng bằng nhau hoặc chúng không .. thay vì ném một ngoại lệ, hãy trả về false.
  • Tôi sẽ xem xét việc ghi đè Đối tượng # Bằng.
  • Mặc dù bạn phải cân nhắc điều này, nhưng việc sử dụng Reflection để so sánh các thuộc tính được cho là khá chậm (tôi không có số để sao lưu điều này). Đây là hành vi mặc định cho valueType # Equals trong C # và bạn nên ghi đè Equals cho các loại giá trị và thực hiện một thành viên so sánh khôn ngoan để biết hiệu suất. (Trước đó, tôi đã đọc nhanh điều này vì bạn có một bộ sưu tập các đối tượng Thuộc tính tùy chỉnh ... thật tệ.)

Cập nhật-Tháng 12 năm 2011:

  • Tất nhiên, nếu kiểu đã có sản phẩm Equals () thì bạn cần một cách tiếp cận khác.
  • Nếu bạn đang sử dụng tính năng này để so sánh các cấu trúc dữ liệu bất biến dành riêng cho mục đích thử nghiệm, bạn không nên thêm Equals vào các lớp sản xuất (Ai đó có thể sắp xếp các thử nghiệm bằng cách theo dõi việc triển khai Equals hoặc bạn có thể ngăn việc tạo triển khai Equals theo yêu cầu sản xuất) .

Tôi đã gặp sự cố với việc ghi đè .Equals () vì tôi đang cố gắng triển khai điều này trên một lớp cơ sở được kế thừa ... bởi vì tôi không biết các khóa cho lớp này sẽ được chạy ngược lại, tôi không thể thực hiện ghi đè phù hợp cho GetHasCode () (yêu cầu khi bạn ghi đè Equals ()).
nailitdown

Yêu cầu là nếu objA.Equals (objB) thì objA.GetHashCode () == objB.GetHashCode (). GetHashCode không nên phụ thuộc vào trạng thái / dữ liệu có thể thay đổi của một lớp ... Tôi không hiểu ý bạn bằng các khóa cho lớp .. Có vẻ như điều gì đó có thể được giải quyết. Loại cơ sở không có 'phím'?
Gishu

6

Nếu hiệu suất không quan trọng, bạn có thể tuần tự hóa chúng và so sánh kết quả:

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
thử một này whila trước, bạn sẽ tự hỏi có bao nhiêu đối tượng không phải là serializable ...
Offler

5

Tôi nghĩ câu trả lời của Big T khá tốt nhưng phần so sánh sâu bị thiếu, vì vậy tôi đã chỉnh sửa nó một chú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

Tôi sẽ thêm dòng sau vào phương thức PublicInstancePropertiesEqual để tránh lỗi sao chép và dán:

Assert.AreNotSame(self, to);

2

Bạn có ghi đè .ToString () trên tất cả các đối tượng của bạn có trong thuộc tính không? Nếu không, phép so sánh thứ hai đó có thể trở lại với null.

Ngoài ra, trong lần so sánh thứ hai đó, tôi đang đứng trước hàng rào về cấu trúc của! (A == B) so với (A! = B), về khả năng đọc sáu tháng / hai năm kể từ bây giờ. Bản thân đường này khá rộng, điều này là tốt nếu bạn có một màn hình rộng, nhưng có thể in ra không tốt. (xoi mói)

Có phải tất cả các đối tượng của bạn luôn sử dụng các thuộc tính để mã này hoạt động không? Có thể có một số dữ liệu nội bộ, không được chỉnh sửa phù hợp có thể khác với đối tượng này sang đối tượng khác, nhưng tất cả dữ liệu được tiếp xúc đều giống nhau? Tôi đang nghĩ đến một số dữ liệu có thể thay đổi theo thời gian, chẳng hạn như hai trình tạo số ngẫu nhiên xảy ra cùng một số tại một điểm, nhưng sẽ tạo ra hai chuỗi thông tin khác nhau hoặc chỉ là bất kỳ dữ liệu nào không bị lộ thông qua giao diện thuộc tính.


điểm tốt -! = ... đồng ý, lấy điểm. ToString () là một nỗ lực để giải quyết .GetValue trả về một đối tượng (do đó so sánh luôn sai, vì đó là so sánh tham chiếu) .. có cách nào tốt hơn không?
nailitdown

Nếu GetValue đang trả về một đối tượng, bạn có thể đệ quy lại thông qua hàm này không? tức là, gọi PropertiesEqual trên các đối tượng trả về?
mmr

1

Nếu bạn chỉ so sánh các đối tượng cùng kiểu hoặc xa hơn trong chuỗi kế thừa, tại sao không chỉ định tham số làm kiểu cơ sở của bạn, thay vì đối tượng?

Cũng thực hiện kiểm tra null đối với tham số.

Hơn nữa, tôi sẽ sử dụng 'var' chỉ để làm cho mã dễ đọc hơn (nếu mã c # 3 của nó)

Ngoài ra, nếu đối tượng có các kiểu tham chiếu là thuộc tính thì bạn chỉ đang gọi ToString () trên chúng mà không thực sự so sánh các giá trị. Nếu ToString không bị ghi đè thì nó sẽ chỉ trả về tên kiểu dưới dạng một chuỗi có thể trả về kết quả xác thực sai.


điểm tốt về các loại tham chiếu - trong trường hợp của tôi, điều đó không quan trọng nhưng có khả năng nó sẽ làm được.
nailitdown

1

Điều đầu tiên tôi đề xuất là chia nhỏ so sánh thực tế để nó dễ đọc hơn một chút (Tôi cũng đã lấy ra ToString () - có cần thiết không?):

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

    if (originalProperty != comparisonProperty)
        return false;

Gợi ý tiếp theo là giảm thiểu việc sử dụng phản xạ càng nhiều càng tốt - nó thực sự rất chậm. Ý tôi là, rất chậm. Nếu bạn định làm điều này, tôi khuyên bạn nên lưu vào bộ nhớ đệm các tham chiếu thuộc tính. Tôi không quen thuộc lắm với API phản chiếu, vì vậy nếu điều này hơi sai, chỉ cần điều chỉnh để làm cho nó biên dịch:

// 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

Tuy nhiên, tôi phải nói rằng tôi đồng ý với các áp phích khác. Điều này có mùi lười biếng và không hiệu quả. Thay vào đó, bạn nên triển khai ICompABLE :-).


Tôi chỉ nhìn vào IComp so sánh được nhưng có vẻ như nó là để phân loại và sắp xếp .. nó có thực sự hữu ích để so sánh sự bằng nhau của hai đối tượng không?
nailitdown

Hoàn toàn đúng, bởi vì .Equals (đối tượng o) được định nghĩa như thế này.CompareTo (o) == 0. Vì vậy, equals sử dụng ComparesTo () để xác định đẳng thức. Điều này sẽ hiệu quả hơn nhiều (và thực hành tiêu chuẩn) so với sử dụng phản xạ.
tsimon

Tôi có thể nhầm lẫn khi giả định rằng Equals được triển khai (hoặc nên được triển khai) với tham chiếu đến CompareTo (). Bạn nên xem xét ghi đè các Equals như được mô tả ở đây: stackoverflow.com/questions/104158/…
tsimon

1

đây là sửa đổi một để coi null = null bằng

 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;
        }

Điều gì sẽ xảy ra nếu tôi có một biểu đồ đối tượng sâu, cách tốt nhất để sử dụng ở trên là gì để trả về danh sách các thuộc tính cũ và mới đã được thay đổi?
Rod,

1

Tôi đã kết thúc việc này:

    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;
    }

Sử dụng:

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

Cập nhật

Nếu bạn muốn bỏ qua một số thuộc tính theo tên:

    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;
    }

Sử dụng:

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

1

Bạn có thể tối ưu hóa mã của mình bằng cách gọi GetProperties chỉ một lần cho mỗi loại:

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

Để có tính đầy đủ, tôi muốn thêm tài liệu tham khảo vào http://www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflection Nó có logic đầy đủ hơn hầu hết các câu trả lời khác trên trang này.

Tuy nhiên tôi thích So sánh-Net-Objects thư viện https://github.com/GregFinzer/Compare-Net-Objects (gọi tắt bởi Liviu Trifoi 's câu trả lời )
Thư viện có NuGet gói http://www.nuget.org/packages/ CompareNETObjects và nhiều tùy chọn để cấu hình.


1

Đảm bảo rằng các đối tượng không null.

obj1obj2:

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

nếu cả hai đều rỗng thì sao? không phải sau đó họ bằng nhau?
mmr

điểm tốt về null, trong trường hợp của tôi sử dụng .Equals () dường như không hoạt động, đó là lý do tại sao tôi đã đưa ra giải pháp này
nailitdown

tốt, trường hợp tôi đang thử nghiệm là hai đối tượng, một đối tượng mới được tạo, một từ phiên. so sánh cả hai với .Equals () trả về false mặc dù cả hai đều có các giá trị thuộc tính giống nhau
nailitdown

0

Điều này hoạt động ngay cả khi các đối tượng khác nhau. bạn có thể tùy chỉnh các phương thức trong lớp tiện ích có thể bạn cũng muốn so sánh các thuộc tính riêng tư ...

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;
    }


}

Mã đó không hiệu quả 100%. nó không hoạt động trong một số trường hợp chẳng hạn nếu nó chứa thuộc tính của đối tượng kiểu.
Tono Nam

0

Cập nhật về câu trả lời của Liviu ở trên - CompareObjects.DifferencesString đã không được dùng nữa.

Điều này hoạt động tốt trong một bài kiểm tra đơn vị:

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

1
Thật tuyệt khi bạn đã khắc phục sự cố, nhưng tôi nghĩ câu trả lời này thực sự nên là một nhận xét trong câu trả lời của Liviu. Đặc biệt là vì mã mẫu của bạn (so với mã của Liviu) thiếu các tham số của CompareLogic (mà tôi chắc chắn là quan trọng) và cả thông báo khẳng định (không được dùng nữa). Khẳng định có thể được sửa bằng:Assert.IsTrue(result.AreEqual, result.DifferencesString);
Mariano Desanze 4/10/14

0

Phương thức này sẽ lấy propertiescủa lớp và so sánh các giá trị cho mỗi property. Nếu bất kỳ giá trị nào khác nhau, nó sẽ có return false, nếu không thì nó sẽ làm như vậy return 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;
}

Sử dụng:

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


0

Để mở rộng trên câu trả lời của @nawfal: s, tôi sử dụng điều này để kiểm tra các đối tượng thuộc các loại khác nhau trong các bài kiểm tra đơn vị của tôi để so sánh các tên thuộc tính bằng nhau. Trong trường hợp của tôi thực thể cơ sở dữ liệu và DTO.

Được sử dụng như thế này trong thử nghiệm của tôi;

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

đôi khi bạn không muốn so sánh tất cả các thuộc tính chung và chỉ muốn so sánh tập hợp con của chúng, vì vậy trong trường hợp này, bạn chỉ có thể di chuyển logic để so sánh danh sách các thuộc tính mong muốn với lớp trừu tượng.

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;
    }
}

và sử dụng lớp trừu tượng này sau đó để so sánh các đối tượng

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

    protected decimal DistanceInMeters { get; private set; }

    ...

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

0

giải pháp của tôi lấy cảm hứng từ câu trả lời của Aras Alenin ở trên, nơi tôi đã thêm một cấp độ so sánh đối tượng và một đối tượng tùy chỉnh để có kết quả so sánh. Tôi cũng muốn lấy tên thuộc tính với tên đối tượng:

    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;
    }

Sử dụng lớp sau để lưu trữ kết quả so sánh

[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; }
}

Và một bài kiểm tra đơn vị mẫu:

    [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");
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.