Tôi có hai đối tượng phức tạp như Object1
và Object2
. Chúng có khoảng 5 cấp độ đối tượng con.
Tôi cần phương pháp nhanh nhất để biết chúng có giống nhau hay không.
Làm thế nào điều này có thể được thực hiện trong C # 4.0?
Tôi có hai đối tượng phức tạp như Object1
và Object2
. Chúng có khoảng 5 cấp độ đối tượng con.
Tôi cần phương pháp nhanh nhất để biết chúng có giống nhau hay không.
Làm thế nào điều này có thể được thực hiện trong C # 4.0?
Câu trả lời:
Triển khai IEquatable<T>
(thường kết hợp với ghi đè các phương thức Object.Equals
và kế thừa Object.GetHashCode
) trên tất cả các loại tùy chỉnh của bạn. Trong trường hợp các kiểu kết hợp, hãy gọi Equals
phương thức kiểu chứa trong các kiểu chứa. Đối với các bộ sưu tập được chứa, hãy sử dụng SequenceEqual
phương thức mở rộng, gọi nội bộ IEquatable<T>.Equals
hoặc Object.Equals
trên từng phần tử. Cách tiếp cận này rõ ràng sẽ yêu cầu bạn mở rộng định nghĩa các kiểu của mình, nhưng kết quả của nó nhanh hơn bất kỳ giải pháp chung chung nào liên quan đến tuần tự hóa.
Chỉnh sửa : Đây là một ví dụ có sẵn với ba cấp độ lồng vào nhau.
Đối với các loại giá trị, thông thường bạn có thể chỉ cần gọi Equals
phương thức của chúng . Ngay cả khi các trường hoặc thuộc tính không bao giờ được gán rõ ràng, chúng vẫn sẽ có giá trị mặc định.
Đối với các loại tham chiếu, trước tiên bạn nên gọi ReferenceEquals
để kiểm tra sự bình đẳng tham chiếu - điều này sẽ đóng vai trò như một sự tăng hiệu quả khi bạn tình cờ tham chiếu đến cùng một đối tượng. Nó cũng sẽ xử lý các trường hợp cả hai tham chiếu đều rỗng. Nếu việc kiểm tra đó không thành công, hãy xác nhận rằng trường hoặc thuộc tính của cá thể của bạn không phải là rỗng (để tránh NullReferenceException
) và gọi Equals
phương thức của nó . Vì các thành viên của chúng ta được nhập đúng cách, nên IEquatable<T>.Equals
phương thức được gọi trực tiếp, bỏ qua Object.Equals
phương thức bị ghi đè (mà việc thực thi sẽ chậm hơn một chút do kiểu ép kiểu).
Khi bạn ghi đè Object.Equals
, bạn cũng sẽ ghi đè lên Object.GetHashCode
; Tôi đã không làm như vậy dưới đây vì lợi ích ngắn gọn.
public class Person : IEquatable<Person>
{
public int Age { get; set; }
public string FirstName { get; set; }
public Address Address { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as Person);
}
public bool Equals(Person other)
{
if (other == null)
return false;
return this.Age.Equals(other.Age) &&
(
object.ReferenceEquals(this.FirstName, other.FirstName) ||
this.FirstName != null &&
this.FirstName.Equals(other.FirstName)
) &&
(
object.ReferenceEquals(this.Address, other.Address) ||
this.Address != null &&
this.Address.Equals(other.Address)
);
}
}
public class Address : IEquatable<Address>
{
public int HouseNo { get; set; }
public string Street { get; set; }
public City City { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as Address);
}
public bool Equals(Address other)
{
if (other == null)
return false;
return this.HouseNo.Equals(other.HouseNo) &&
(
object.ReferenceEquals(this.Street, other.Street) ||
this.Street != null &&
this.Street.Equals(other.Street)
) &&
(
object.ReferenceEquals(this.City, other.City) ||
this.City != null &&
this.City.Equals(other.City)
);
}
}
public class City : IEquatable<City>
{
public string Name { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as City);
}
public bool Equals(City other)
{
if (other == null)
return false;
return
object.ReferenceEquals(this.Name, other.Name) ||
this.Name != null &&
this.Name.Equals(other.Name);
}
}
Cập nhật : Câu trả lời này đã được viết vài năm trước. Kể từ đó, tôi bắt đầu bỏ qua việc triển khai IEquality<T>
các loại có thể thay đổi cho các tình huống như vậy. Có hai khái niệm bình đẳng: bản sắc và sự tương đương . Ở cấp độ biểu diễn bộ nhớ, chúng được phân biệt phổ biến là “bình đẳng tham chiếu” và “bình đẳng giá trị” (xem So sánh bình đẳng ). Tuy nhiên, sự phân biệt tương tự cũng có thể áp dụng ở cấp miền. Giả sử rằng Person
lớp của bạn có một thuộc PersonId
tính, duy nhất cho mỗi người trong thế giới thực riêng biệt. Hai đối tượng có cùng giá trị PersonId
nhưng khác Age
giá trị nên được coi là bằng nhau hay khác nhau? Câu trả lời ở trên giả định rằng một là sau tương đương. Tuy nhiên, có nhiều cách sử dụngIEquality<T>
giao diện, chẳng hạn như bộ sưu tập, giả định rằng việc triển khai như vậy cung cấp danh tính . Ví dụ: nếu bạn đang điền một HashSet<T>
, bạn thường mong đợi một TryGetValue(T,T)
lệnh gọi trả về các phần tử hiện có chỉ chia sẻ danh tính của đối số của bạn, không nhất thiết là các phần tử tương đương có nội dung hoàn toàn giống nhau. Khái niệm này được thực thi bởi các ghi chú về GetHashCode
:
Nói chung, đối với các loại tham chiếu có thể thay đổi, bạn chỉ nên ghi đè
GetHashCode()
khi:
- Bạn có thể tính toán mã băm từ các trường không thể thay đổi; hoặc là
- Bạn có thể đảm bảo rằng mã băm của một đối tượng có thể thay đổi không thay đổi trong khi đối tượng được chứa trong một bộ sưu tập dựa trên mã băm của nó.
partial
- trong trường hợp đó, có, bạn có thể triển khai Equals
phương thức của chúng thông qua khai báo lớp từng phần được thêm theo cách thủ công, tham chiếu đến các trường / thuộc tính từ lớp được tạo tự động một.
Enumerable.SequenceEqual
phương pháp trên các mảng: this.Addresses.SequenceEqual(other.Addresses)
. Điều này sẽ gọi nội bộ Address.Equals
phương thức của bạn cho từng cặp địa chỉ tương ứng, với điều kiện là Address
lớp thực hiện IEquatable<Address>
giao diện.
Tuần tự hóa cả hai đối tượng và so sánh các chuỗi kết quả
+1
như vậy đơn giản vì tôi chưa bao giờ nghĩ đến việc so sánh bình đẳng dựa trên giá trị theo cách này. Nó đẹp và đơn giản. Sẽ rất dễ dàng nếu bạn thấy một số điểm chuẩn với mã này.
Bạn có thể sử dụng phương pháp mở rộng, đệ quy để giải quyết vấn đề này:
public static bool DeepCompare(this object obj, object another)
{
if (ReferenceEquals(obj, another)) return true;
if ((obj == null) || (another == null)) return false;
//Compare two object's class, return false if they are difference
if (obj.GetType() != another.GetType()) return false;
var result = true;
//Get all properties of obj
//And compare each other
foreach (var property in obj.GetType().GetProperties())
{
var objValue = property.GetValue(obj);
var anotherValue = property.GetValue(another);
if (!objValue.Equals(anotherValue)) result = false;
}
return result;
}
public static bool CompareEx(this object obj, object another)
{
if (ReferenceEquals(obj, another)) return true;
if ((obj == null) || (another == null)) return false;
if (obj.GetType() != another.GetType()) return false;
//properties: int, double, DateTime, etc, not class
if (!obj.GetType().IsClass) return obj.Equals(another);
var result = true;
foreach (var property in obj.GetType().GetProperties())
{
var objValue = property.GetValue(obj);
var anotherValue = property.GetValue(another);
//Recursion
if (!objValue.DeepCompare(anotherValue)) result = false;
}
return result;
}
hoặc so sánh bằng cách sử dụng Json (nếu đối tượng rất phức tạp) Bạn có thể sử dụng Newtonsoft.Json:
public static bool JsonCompare(this object obj, object another)
{
if (ReferenceEquals(obj, another)) return true;
if ((obj == null) || (another == null)) return false;
if (obj.GetType() != another.GetType()) return false;
var objJson = JsonConvert.SerializeObject(obj);
var anotherJson = JsonConvert.SerializeObject(another);
return objJson == anotherJson;
}
DeepCompare
thay vì chỉ gọi CompareEx
đệ quy không?
result
bằng return false
sẽ làm cho nó hiệu quả hơn.
Nếu bạn không muốn triển khai IEquatable, bạn luôn có thể sử dụng Reflection để so sánh tất cả các thuộc tính: - nếu chúng là kiểu giá trị, chỉ cần so sánh chúng - nếu chúng là kiểu tham chiếu, hãy gọi hàm đệ quy để so sánh các thuộc tính "bên trong" của nó .
Tôi không nghĩ về hiệu suất, mà là về sự đơn giản. Tuy nhiên, nó phụ thuộc vào thiết kế chính xác của các đối tượng của bạn. Nó có thể trở nên phức tạp tùy thuộc vào hình dạng đối tượng của bạn (ví dụ: nếu có sự phụ thuộc tuần hoàn giữa các thuộc tính). Tuy nhiên, có một số giải pháp ngoài kia mà bạn có thể sử dụng, như giải pháp sau:
Một tùy chọn khác là tuần tự hóa đối tượng dưới dạng văn bản, chẳng hạn như sử dụng JSON.NET và so sánh kết quả tuần tự hóa. (JSON.NET có thể xử lý các phụ thuộc theo chu kỳ giữa các thuộc tính).
Tôi không biết ý bạn là cách nhanh nhất để triển khai nó hay một đoạn mã chạy nhanh. Bạn không nên tối ưu hóa trước khi biết nếu bạn cần. Tối ưu hóa sớm là gốc rễ của mọi điều xấu
IEquatable<T>
triển khai đủ điều kiện là một trường hợp tối ưu hóa sớm. Phản xạ sẽ chậm hơn đáng kể. Việc triển khai mặc định Equals
cho các loại giá trị tùy chỉnh sử dụng phản chiếu; Bản thân Microsoft khuyên bạn nên ghi đè Equals
phương thức cho một kiểu cụ thể để cải thiện hiệu suất của phương pháp và thể hiện chặt chẽ hơn khái niệm bình đẳng cho kiểu. ”
Tuần tự hóa cả hai đối tượng và so sánh các chuỗi kết quả bằng @JoelFan
Vì vậy, để thực hiện việc này, hãy tạo một lớp tĩnh như vậy và sử dụng Phần mở rộng để mở rộng TẤT CẢ các đối tượng (để bạn có thể chuyển bất kỳ loại đối tượng, bộ sưu tập, v.v. vào phương thức)
using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
public static class MySerializer
{
public static string Serialize(this object obj)
{
var serializer = new DataContractJsonSerializer(obj.GetType());
using (var ms = new MemoryStream())
{
serializer.WriteObject(ms, obj);
return Encoding.Default.GetString(ms.ToArray());
}
}
}
Khi bạn tham chiếu lớp tĩnh này trong bất kỳ tệp nào khác, bạn có thể thực hiện việc này:
Person p = new Person { Firstname = "Jason", LastName = "Argonauts" };
Person p2 = new Person { Firstname = "Jason", LastName = "Argonaut" };
//assuming you have already created a class person!
string personString = p.Serialize();
string person2String = p2.Serialize();
Bây giờ bạn có thể chỉ cần sử dụng .Equals để so sánh chúng. Tôi sử dụng điều này để kiểm tra xem các đối tượng cũng có trong bộ sưu tập hay không. Nó hoạt động thực sự tốt.
CultrureInfo
. Điều này sẽ chỉ hoạt động nếu dữ liệu nội bộ chủ yếu là chuỗi và số nguyên. Nếu không nó sẽ là một thảm họa.
Tôi sẽ cho rằng bạn không đề cập đến các đối tượng giống nhau theo nghĩa đen
Object1 == Object2
Bạn có thể đang nghĩ đến việc so sánh trí nhớ giữa hai
memcmp(Object1, Object2, sizeof(Object.GetType())
Nhưng đó thậm chí không phải là mã thực trong c # :). Bởi vì tất cả dữ liệu của bạn có thể được tạo trên heap, bộ nhớ không liền kề và bạn không thể chỉ so sánh sự bằng nhau của hai đối tượng theo cách bất khả tri. Bạn sẽ phải so sánh từng giá trị, từng giá trị một, theo cách tùy chỉnh.
Cân nhắc thêm IEquatable<T>
giao diện vào lớp của bạn và xác định Equals
phương thức tùy chỉnh cho kiểu của bạn. Sau đó, trong phương pháp đó, hãy kiểm tra thủ công từng giá trị. Thêm IEquatable<T>
một lần nữa vào các loại kèm theo nếu bạn có thể và lặp lại quy trình.
class Foo : IEquatable<Foo>
{
public bool Equals(Foo other)
{
/* check all the values */
return false;
}
}
Tuần tự hóa cả hai đối tượng, sau đó tính toán Mã băm, sau đó so sánh.
Tôi tìm thấy chức năng này bên dưới để so sánh các đối tượng.
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 (object.Equals(Object1, default(T)) || object.Equals(object2, default(T)))
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;
}
Tôi đang sử dụng nó và nó đang hoạt động tốt đối với tôi.
if
có nghĩa là Compare(null, null) == false
đó không phải là những gì tôi mong đợi.
Dựa trên một số câu trả lời đã được đưa ra ở đây, tôi quyết định chủ yếu quay lại câu trả lời của JoelFan . Tôi thích các phương thức mở rộng và những phương pháp này đã hoạt động rất hiệu quả đối với tôi khi không có giải pháp nào khác sử dụng chúng để so sánh các lớp phức tạp của tôi.
using System.IO;
using System.Xml.Serialization;
static class ObjectHelpers
{
public static string SerializeObject<T>(this T toSerialize)
{
XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());
using (StringWriter textWriter = new StringWriter())
{
xmlSerializer.Serialize(textWriter, toSerialize);
return textWriter.ToString();
}
}
public static bool EqualTo(this object obj, object toCompare)
{
if (obj.SerializeObject() == toCompare.SerializeObject())
return true;
else
return false;
}
public static bool IsBlank<T>(this T obj) where T: new()
{
T blank = new T();
T newObj = ((T)obj);
if (newObj.SerializeObject() == blank.SerializeObject())
return true;
else
return false;
}
}
if (record.IsBlank())
throw new Exception("Record found is blank.");
if (record.EqualTo(new record()))
throw new Exception("Record found is blank.");
Tôi sẽ nói rằng:
Object1.Equals(Object2)
sẽ là những gì bạn đang tìm kiếm. Đó là nếu bạn đang muốn xem các đối tượng có giống nhau hay không, đó là điều bạn có vẻ đang hỏi.
Nếu bạn muốn kiểm tra xem tất cả các đối tượng con có giống nhau hay không, hãy chạy chúng qua một vòng lặp với Equals()
phương thức.
public class GetObjectsComparison
{
public object FirstObject, SecondObject;
public BindingFlags BindingFlagsConditions= BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
}
public struct SetObjectsComparison
{
public FieldInfo SecondObjectFieldInfo;
public dynamic FirstObjectFieldInfoValue, SecondObjectFieldInfoValue;
public bool ErrorFound;
public GetObjectsComparison GetObjectsComparison;
}
private static bool ObjectsComparison(GetObjectsComparison GetObjectsComparison)
{
GetObjectsComparison FunctionGet = GetObjectsComparison;
SetObjectsComparison FunctionSet = new SetObjectsComparison();
if (FunctionSet.ErrorFound==false)
foreach (FieldInfo FirstObjectFieldInfo in FunctionGet.FirstObject.GetType().GetFields(FunctionGet.BindingFlagsConditions))
{
FunctionSet.SecondObjectFieldInfo =
FunctionGet.SecondObject.GetType().GetField(FirstObjectFieldInfo.Name, FunctionGet.BindingFlagsConditions);
FunctionSet.FirstObjectFieldInfoValue = FirstObjectFieldInfo.GetValue(FunctionGet.FirstObject);
FunctionSet.SecondObjectFieldInfoValue = FunctionSet.SecondObjectFieldInfo.GetValue(FunctionGet.SecondObject);
if (FirstObjectFieldInfo.FieldType.IsNested)
{
FunctionSet.GetObjectsComparison =
new GetObjectsComparison()
{
FirstObject = FunctionSet.FirstObjectFieldInfoValue
,
SecondObject = FunctionSet.SecondObjectFieldInfoValue
};
if (!ObjectsComparison(FunctionSet.GetObjectsComparison))
{
FunctionSet.ErrorFound = true;
break;
}
}
else if (FunctionSet.FirstObjectFieldInfoValue != FunctionSet.SecondObjectFieldInfoValue)
{
FunctionSet.ErrorFound = true;
break;
}
}
return !FunctionSet.ErrorFound;
}
Một cách để làm điều này là ghi đè lên Equals()
từng loại liên quan. Ví dụ: đối tượng cấp cao nhất của bạn sẽ ghi đè Equals()
để gọi Equals()
phương thức của tất cả 5 đối tượng con. Tất cả các đối tượng đó cũng nên ghi đè Equals()
, giả sử chúng là các đối tượng tùy chỉnh, v.v. cho đến khi toàn bộ hệ thống phân cấp có thể được so sánh bằng cách chỉ thực hiện kiểm tra bình đẳng trên các đối tượng cấp cao nhất.
Sử dụng IEquatable<T>
Giao diện có một phương pháp Equals
.
Cảm ơn tấm gương của Jonathan. Tôi đã mở rộng nó cho tất cả các trường hợp (mảng, danh sách, từ điển, kiểu nguyên thủy).
Đây là một so sánh không có tuần tự hóa và không yêu cầu thực hiện bất kỳ giao diện nào cho các đối tượng được so sánh.
/// <summary>Returns description of difference or empty value if equal</summary>
public static string Compare(object obj1, object obj2, string path = "")
{
string path1 = string.IsNullOrEmpty(path) ? "" : path + ": ";
if (obj1 == null && obj2 != null)
return path1 + "null != not null";
else if (obj2 == null && obj1 != null)
return path1 + "not null != null";
else if (obj1 == null && obj2 == null)
return null;
if (!obj1.GetType().Equals(obj2.GetType()))
return "different types: " + obj1.GetType() + " and " + obj2.GetType();
Type type = obj1.GetType();
if (path == "")
path = type.Name;
if (type.IsPrimitive || typeof(string).Equals(type))
{
if (!obj1.Equals(obj2))
return path1 + "'" + obj1 + "' != '" + obj2 + "'";
return null;
}
if (type.IsArray)
{
Array first = obj1 as Array;
Array second = obj2 as Array;
if (first.Length != second.Length)
return path1 + "array size differs (" + first.Length + " vs " + second.Length + ")";
var en = first.GetEnumerator();
int i = 0;
while (en.MoveNext())
{
string res = Compare(en.Current, second.GetValue(i), path);
if (res != null)
return res + " (Index " + i + ")";
i++;
}
}
else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type))
{
System.Collections.IEnumerable first = obj1 as System.Collections.IEnumerable;
System.Collections.IEnumerable second = obj2 as System.Collections.IEnumerable;
var en = first.GetEnumerator();
var en2 = second.GetEnumerator();
int i = 0;
while (en.MoveNext())
{
if (!en2.MoveNext())
return path + ": enumerable size differs";
string res = Compare(en.Current, en2.Current, path);
if (res != null)
return res + " (Index " + i + ")";
i++;
}
}
else
{
foreach (PropertyInfo pi in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
{
try
{
var val = pi.GetValue(obj1);
var tval = pi.GetValue(obj2);
if (path.EndsWith("." + pi.Name))
return null;
var pathNew = (path.Length == 0 ? "" : path + ".") + pi.Name;
string res = Compare(val, tval, pathNew);
if (res != null)
return res;
}
catch (TargetParameterCountException)
{
//index property
}
}
foreach (FieldInfo fi in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
{
var val = fi.GetValue(obj1);
var tval = fi.GetValue(obj2);
if (path.EndsWith("." + fi.Name))
return null;
var pathNew = (path.Length == 0 ? "" : path + ".") + fi.Name;
string res = Compare(val, tval, pathNew);
if (res != null)
return res;
}
}
return null;
}
Để sao chép dễ dàng kho lưu trữ mã đã tạo
Bây giờ bạn có thể sử dụng json.net. Chỉ cần vào Nuget và cài đặt nó.
Và bạn có thể làm điều gì đó như sau:
public bool Equals(SamplesItem sampleToCompare)
{
string myself = JsonConvert.SerializeObject(this);
string other = JsonConvert.SerializeObject(sampleToCompare);
return myself == other;
}
Có lẽ bạn có thể tạo một phương thức mở rộng cho đối tượng nếu bạn muốn có được những cái đẹp hơn. Xin lưu ý rằng điều này chỉ so sánh các tài sản công cộng. Và nếu bạn muốn bỏ qua thuộc tính công khai khi thực hiện so sánh, bạn có thể sử dụng thuộc tính [JsonIgnore].