В этой статье я расскажу о довольно простой вещи в C#: Сравните два объекта. Это задача, которую все считают легкой; Постепенно буду поднимать вопрос от простого к сложному. Обходной путь также будет идти от простого к сложному, а затем обратно к простому. Если вы потратите время на то, чтобы прочитать эту статью от начала до конца, вы многое поймете, а технические возможности прилично повысятся.

Уровень 1: Простой класс

Начнем задачу с простого класса. Этот класс имеет три свойства, когда мы вызываем функцию Equals, C# сравнивает только ссылку, поэтому результат False

public class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}
var student1 = new Student
{
FirstName = "Pham",
   LastName = "Hoang",
   Age = 15,
};
var student2 = new Student
{
   FirstName = "Pham",
   LastName = "Hoang",
   Age = 15,
};
student1.Equals(student2); //False

Чтобы справиться с этим, нам нужно переопределить функцию Equals, а это не сложно, все узнают в школе.

public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType()) return false;
var student = (Student) obj;
return FirstName.Equals(student.FirstName)
       && LastName.Equals(student.LastName)
       && Age == student.Age
       && BirthDate.Equals(student.BirthDate);
}

Уровень 2: Используйте метод расширения

У функции Equals все еще есть недостаток: student1 не может быть нулевым. Мы можем решить эту проблему, используя метод расширения следующим образом. Теперь мы можем вызвать student1. Равно (student2), несмотря на то, что student1 имеет значение null:

public static bool DeepEquals (this Student obj, Student another)
{
  // If null or the same, return true
  if (ReferenceEquals (obj, another)) return true;
 
  // If one of them is null, return false
  if ((obj == null) || (another == null)) return false;
 
  return obj.FirstName.Equals (another.FirstName)
         && obj.LastName.Equals (another.LastName)
         && obj.Age == another.Age
         && obj.BirthDate.Equals (another.BirthDate);
}

Уровень 3: Напишите метод для сравнения двух объектов в целом.

Представьте, что в вашем приложении есть несколько десятков классов, каждый из которых имеет несколько десятков свойств. Что вы будете делать? Рукописная функция Equals для каждого класса? Используйте Refection на C#, чтобы написать функцию сравнения объектов, которая может использоваться любым объектом.

public static bool DeepEquals (this object obj, object another)
  {
 
   if (ReferenceEquals (obj, another)) return true;
   if ((obj == null) || (another == null)) return false;
   // Comparing class of 2 objects, if different, then fail
   if (obj.GetType ()! = another.GetType ()) return false;
 
   var result = true;
   // Get all properties of obj
   // then compare the value of each property
   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;
  }

Эта функция работает очень хорошо. Вы задаетесь вопросом: Вау, это легко писать? Пока нет, пожалуйста, продолжайте ниже, нас ждут более «страшные» вещи.

Уровень 4: объект содержит объект или структуру, например DateTime.

На уровне выше мы пишем функцию для сравнения каждого поля. Но предположим, что в классе Student есть DateTime или другой класс?

public class Student
{
    public string FirstName {get; set; }
    public string LastName {get; set; }
    public DateTime BirthDate {get; set; }
    public Teacher Teacher {get; set; }
}
 
public class Teacher
{
    public string Name {get; set; }
    public string Subject {get; set; }
}
 
var student1 = new Student
{
    FirstName = "Pham",
    LastName = "Hoang",
    BirthDate = new DateTime (1992, 12, 5),
    Teacher = new Teacher {Name = "Le Minh", Subject = "Math"}
};
 
var student2 = new Student
{
    FirstName = "Pham",
    LastName = "Hoang",
    BirthDate = new DateTime (1992, 12, 5),
    Teacher = new Teacher {Name = "Le Minh", Subject = "Math"}
};

Задумайтесь на секунду. Чтобы решить эту проблему, мы также будем сравнивать каждое поле, но если поле является объектом, то мы будем сравнивать его с написанной функцией DeepEquals. Только базовый рекурсивный алгоритм.

public static bool DeepEquals (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;
 
  // If the property is not a class, just int, double, DateTime, etc. v
  // Call regular equal function
  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);
     // Continue recursion
     if (! objValue.DeepEquals (anotherValue)) result = false;
  }
  return result;
}

Ничего себе, дело сделано, все проблемы решены, вы себя хвалите. О, как насчет случая не объекта, а List, как насчет этой натяжки.

Уровень 5: Сравните 2 списка

К счастью, мы можем написать метод расширения для списка следующим образом (слово ‹T› является общим, вы можете прочитать эту статью для ознакомления).

public static bool DeepEquals <T> (this IEnumerable <T> obj, IEnumerable <T> another)
{
  if (ReferenceEquals (obj, another)) return true;
  if ((obj == null) || (another == null)) return false;
 
  bool result = true;
// Browse each element in 2 given list
  using (IEnumerator <T> enumerator1 = obj.GetEnumerator ())
  using (IEnumerator <T> enumerator2 = another.GetEnumerator ())
  {
    while (true)
    {
      bool hasNext1 = enumerator1.MoveNext ();
      bool hasNext2 = enumerator2.MoveNext ();
 
      // If there is 1 list, or 2 different elements, exit the loop
      if (hasNext1! = hasNext2 ||! enumerator1.Current.DeepEquals (enumerator2.Current))
      {
         result = false;
         break;
      }
 
       // Stop the loop when 2 lists are all
       if (! hasNext1) break;
    }
  }
 
  return result;
}

Вау, я временно закончил. Наверное, больше ничего, да?

var list1 = new List <Student> {student1, student2};
var list2 = new List <Student> {student1, student2};
 
list1 == list2; // True

Уровень 6: Куча других вещей

Вы вдруг вспоминаете, что в C# есть бессчетное количество вещей, похожих на List, таких как Dictionary, HashSet, может быть, чтобы писать в них все. Есть еще несколько тривиальных случаев, например, класс Student будет содержать список учителей; метод, который мы пишем, не может работать.

var teacherA = new Teacher {Name = "Le Minh", Subject = "Math"};
var teacherB = new Teacher {Name = "Tai Phu", Subject = "Physics"};
 
var student1 = new Student
{
  FirstName = "Pham",
  LastName = "Hoang",
  Age = 15,
  BirthDate = new DateTime (1992, 12, 5),
  Teacher = new List <Teacher> {teacherA, teacherB}
};
 
var student2 = new Student
{
  FirstName = "Pham",
  LastName = "Hoang",
  Age = 15,
  BirthDate = new DateTime (1992, 12, 5),
  Teacher = new List <Teacher> {teacherA, teacherB}
};

На данный момент я тоже сдался, дорога впереди довольно сложная и трудная: ‘(Вы можете выбрать один из двух следующих вариантов:

  1. Для слишком сложных классов напишите функцию Equals самостоятельно, не слишком.
  2. Следуйте универсальной функции до конца, продолжая читать эту статью.

Конечный уровень: JSON

Решение проблемы проще, чем вы думаете. Давайте сериализуем эти два объекта как строку JSON, сравним две сгенерированные строки. (К счастью, сериализация JSON решила 99% сложной проблемы, связанной с типами данных: D).

Этапы, которые необходимо выполнить:

  1. Добавьте Reference Newtonsoft.JSON в соответствии с инструкциями, результат, как показано на рисунке 3, в порядке.

2. Напишите простую компактную функцию сравнения:

public static bool JSONEquals (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;
}

Есть еще несколько библиотек, которые вы можете найти в Google по ключевым словам: Deep Compare C#.

Так как статья довольно длинная, слегка техническая, я постарался сделать ее более привлекательной. Поздравляю, если вы дочитали до конца. Награда для вас здесь терпеливая: первые пять комментаторов в этой статье имеют право попросить меня написать статью об одном аспекте C#, MVC или javascript, который вы хотите изучить. Удачи.