【问题标题】:Compare two list using linq in C#在 C# 中使用 linq 比较两个列表
【发布时间】:2019-10-21 08:32:07
【问题描述】:

我们有下面的学生列表,我想通过比较学生姓名来比较两个列表(来源、目的地)。如果学生姓名发生变化,那么我们需要执行更新查询,以更新新的学生姓名。

Public Class Student
{
   public int StudentId { get; set; }
   public int StudentName { get; set; }
}

我们需要将学生名单与来源和目的地进行比较。

IList<Student> studentList1 = new List<Student>() { 
        new Student() { StudentId = 1, StudentName = "John", Age = 18 } ,
        new Student() { StudentId = 2, StudentName = "Steve",  Age = 15 }

};

IList<Student> studentList2 = new List<Student>() { 
        new Student() { StudentId = 1, StudentName = "Michaiel", Age = 18 } ,
        new Student() { StudentId = 2, StudentName = "Mike",  Age = 15 } ,
};

所以,到目前为止,我们正在考虑使用SequenceEqual 方法来比较两个列表。那么,这种方法是否也比较区分大小写的值和任何其他更好的替代方法?

这种方法可以用于其他数据类型,如布尔、整数等?

对此的任何帮助表示赞赏!

【问题讨论】:

  • 我的猜测是覆盖equals 方法。 stackoverflow.com/questions/9317582/…
  • 为什么不同的学生会有相同的StudentId?这是您正在处理的数据的良好表示吗?
  • @BrettCaswell 我通过比较来更新记录,所以没有重复的 ID。

标签: c# linq asp.net-core


【解决方案1】:

与大多数 LINQ 方法一样,SequenceEquals 接受 comparer 参数,该参数允许您使用不同的比较来比较元素。

您可以直接实现 IEqualityComparer 接口,也可以从EqualityComparer继承:

class StudentNameComparer: EqualityComparer<Student>
{
    public override bool Equals(Student st1, Student st2)
    {
        return String.Equals(st1?.StudentName,st2?.StudentName);
    }

    public override int GetHashCode(Student st)
    {
        return st.Name.GetHashCode();
    }
}

这允许您检查差异:

var hasDiffs=studentList1.SequenceEquals(studentList2, new StudentNameComparer());

Except 也有一个比较器。您可以通过以下方式找到实际差异:

var diffs=studentList1.Except(studentList2,new StudentNameComparer());

.NET 中的字符串比较区分大小写。如果您想使用不区分大小写的比较,您可能应该提取并比较名称。您可以将String.Equals 与所需的StringComparison 参数一起使用,例如:String.Equals(st1.StudentName,st2.StudentName,StringComparison.OrdinalIgnoreCase),但是哈希码呢?

StringComparer 已经实现了适当的比较和哈希码,因此更易于使用:

var studentNames1=studentList1.Select(st=>st.StudentName);
var studentNames2=studentList2.Select(st=>st.StudentName);
var haveDiffs=studentNames1.SequenceEqual(studentName2,StringComparer.OrdinalIgnoreCase);

【讨论】:

  • nice.. 这基本上就是我要回答这个问题的方式.. 定义和使用 EqualityComparer 并根据意图将其传递到收集方法中......
  • 你不需要考虑StudentId吗?
  • @RufusL 不是如何仅按名称进行比较的问题吗?如果需要IdSequenceEqual 是没有意义的——它只有在假设列表已经排序并且项目匹配时才有效。如果这不成立,则必须在比较之前通过 ID 加入,或者根据两个键生成哈希集
【解决方案2】:

如果学生姓名发生变化,那么我们需要执行更新查询,以更新新的学生姓名。

根据该陈述,您需要了解的不仅仅是两个列表是否包含相同的成员。在我看来,您应该遍历第一个列表并查看第二个列表中是否有更新的名称:

foreach (var student in studentList1)
{
    var otherStudent = studentList2.FirstOrDefault(other => 
        other.SudentId == student.StudentId && 
        !other.StudentName.Equals(student.StudentName, StringComparison.OrdinalIgnoreCase));

    if (otherStudent != null)
    {
        // Update student record with student.StudentId to otherStudent.StudentName
    }
}

同样,您可以使用类似以下的方式来获取所有具有新名称的学生:

var studentsWithNewNames = studentList2.Where(s2 =>
    studentList1.Any(s1 =>
        s1.StudentId == s2.StudentId &&
        !s1.StudentName.Equals(s2.StudentName, StringComparison.OrdinalIgnoreCase)));

foreach (var newNameStudent in studentsWithNewNames)
{
    // Update student with newNameStudent.StudentId to newNameStudent.StudentName
}

或者您可以完全使用 linq 并执行以下操作:

studentList2.Where(s2 => studentList1.Any(s1 =>
    s1.StudentId == s2.StudentId &&
    !s1.StudentName.Equals(s2.StudentName, StringComparison.OrdinalIgnoreCase)))
    .ToList()
    .ForEach(student =>
    {
        // update student.StudentId with student.StudentName
    });

还请注意,您不应将Age 存储为属性,因为它会不断变化。您应该存储DateOfBirth,然后将Age 显示为计算字段。而Name 应该是string(还要注意你不需要在属性名中重复类名):

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public TimeSpan Age => DateTime.Now - DateOfBirth;
}

【讨论】:

  • 是的..这是一个答案..为了简洁起见确实需要添加它,但是看到人们循环和比较这样的集合总是让我感到奇怪..你可能会这样做使用Join(或连接的结果集).. 或创建一个类似的扩展。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-06
  • 1970-01-01
  • 2011-10-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多