比较两个列表,并仅使用具有不同值的列返回新列表

时间:2018-10-09 06:21:08

标签: c# linq

我正在尝试比较List1和List2 [List2是经过编辑的List1],然后生成仅包含更新的列和更新的值的列表

列表1

var x = (from a in table1 where id == 1 select a).firstordefault();

在下面产生

ID | Val1 | Val2 | Val3 | Val4 | Val5
-------------------------
1  | A    | B    | C    | D    | E

ID = 1的项目更新后

列表2

ID | Val1 | Val2 | Val3 | Val4 | Val5
-------------------------
1  | A    | C    | C    | F    | E

然后比较列表1 列表2 以生成输出列表

输出列表

ID | Val2 | Val4 |
-------------------
1  |  C   | F    |

2 个答案:

答案 0 :(得分:0)

public class Difference
{
    public int Id {get;set;}
    public string Val1 {get;set;}
    public string Val2 {get;set;}
    public string Val3 {get;set;}
    public string Val4 {get;set;}
    public string Val5 {get;set;}
}

var result = new List<Difference>();
var props = typeof(Difference).GetProperties();
foreach(var origin in source)
{
    var against = updated.FirstOrDefault(x => x.Id == origin.Id);
    if(against != null)
    {
        var diff = new Difference{ Id = origin.Id };
        var add = false;
        foreach(var prop in props)
        {
            var againstVal = prop.GetValue(against);
            if(prop.GetValue(origin) != againstVal)
            {
                prop.SetValue(diff, againstVal)
                add = true;
            }
        }
        if(add)
            result.Add(diff);
    }
}

foreach(var res in result)
{
    foreach(var prop in props)
    {
        var val = prop.Value;
        if(val != null)
            Console.Write($"{prop.Name}:{prop.Value} ")
    }
    Console.WriteLine();
}
//output:
//Id:1 Val2:C Val4:F
//Id:2 Val2:C Val3:F Val13:S
//...

答案 1 :(得分:0)

您自己变得很困难,只希望获得有关已更改的属性值的信息。这使得结果中的每一行都有不同数量的值。

除了您忘记告诉我们Id列中的值是列表中项目的索引还是每个列表元素都具有属性Id

此外:是列表的类型在编译时已经知道还是仅在运行时知道。换句话说,它是List<TSource>,还是更像System.Data.DataTable

另一个问题:ID是否唯一?并且如果列表2的元素的ID = 5,但列表1的行没有此ID,您是否希望它在结果中作为附加值?如果列表1的ID在列表2中不存在,是否为已删除的值?

  

顺便说一句,我讨厌人们对此做出反应:“哦,对不起,我   忘记提及ID实际上是列表中项目的索引,哦,我   还忘了说我的两个列表长度相等,你不能   从列表中添加或删除元素。   规格,然后问一个问题!

List<TSource>

此解决方案在编译时检测错误。如果您尝试在没有Id的情况下将项目放入列表,则编译器将无法编译。如果您不小心尝试将DateTime与字符串等进行比较,则会出现错误。但是,问题在于您必须事先知道列表中有哪些项目。

IEnumerable<TSource> ListA = ...
IEnumerable<TSource> ListB = ...

为确保您的TSource具有Id的概念,我不会创建输入为List<TSource>而是List> , where the int { {1}}列表中的TSource`或其中一个属性。

is the Id. This can be the index of the到List>`的转换很简单:

List<TSource>

现在我们已经确定了ID,我们可以给出函数的签名

// The index is the Id
static IEnumerable<KeyValuePair<int, TSource> ToIndexedPair<TSource>(
       this IEnumerable<TSource> source)
{
    return source.Select( (source, index) => 
         new KeyValuePair<int, TSource>(index, source));
}

// The index is one of the properties:
static IEnumerable<KeyValuePair<int, TSource> ToIndexedPair<TSource>(
       this IEnumerable<TSource> source,
       Func<TSource, int> IdSelector)
{
    return source.Select(source => new KeyValuePair<int, TSource>
          (IdSelector(source), source));
}

作为输出,您需要一个对象序列,其中包含一个IEnumerable<...> ExtractDifferences<TSource>( IEnumerable<KeyValuePair<int, TSource> listA, IEnumerable<KeyValuePair<int, TSource> listB) { ... } 和一个序列Id,其中每个DiffInfo包含有关更改后的值的信息:已更改,并且更改了值。为了娱乐,我还将添加原始值。

问题是:结果中,您需要具有更改后的值和更改后的属性的序列。这些值可以是DiffInfoDatetimeint或任何其他类。因此,在返回序列中,我对列表中的属性的全部了解是它们是string并且它们具有值。除非您对objects有更多了解,否则您不能做太多事,因此我也提供objects

PropertyInfo

注意,通过以这种方式定义类,我只需要存储一些指针和class DiffPropertyInfo<TSource> { public int Id {get; set;} public TSource OriginalValue {get; set;} public TSource AlternativeValues {get; set;} public PropertyInfo PropertyInfo {get; set;} public object OriginalPropertyValue { get {return this.PropertyInfo.GetValue(this.OriginalValue);} } public object AlternativePropertyValue { get { return this.PropertyInfo.GetValue(this.AlternativeValue); } } public bool IsChanged() { object originalPropertyValue = this.OriginalPropertyValue; object alternativePropertyValue = this.AlternativePropertyValue; return Object.Equals(originalPropertyValue, alternativePropertyValue); } } 。直到您真正询问它的值是否已更改,才使用价格昂贵的GetPropertyValue。

顺便说一句:使用Id,而不是Object.Equals,因为如果获取的对象是值类型,则我们需要正确的X == Y的重写版本。另外,如果您有一个覆盖Object.Equals的类,则需要该相等性,而不是默认的Equals

提取两个TSource对象之间的不同属性:

Object.Equals

对于您输入序列中的每个元素,我们都需要存储差异的结果:

IEnumerable<DiffPropertyInfo> ExtractProperties(int Id, TSource original, TSource alternative)
{
     // examine only readable properties that can be changed:
     IEnumerable<PropertyInfo> propertyInfos = typeof(TSource)
        .GetProperties()
        .Where(property => property.CanRead && property.CanWrite);

     // the following can be done as one LINQ statement
     // for readability I'll use yield return
     foreach (PropertyInfo propertyInfo in propertyInfos
     {
         yield return new DiffPropertyInfo()
         {
              Id = Id,
              OriginalValue = original,
              AlternativeValue = alternative,
              PropertyInfo = propertyInfo, 
         };
     }
}

在这里,您会看到相同的方法:只有在有人要求时才计算差异。

因此,现在我们可以返回将执行您想要的操作的原始功能

class PropertyComparisonCollection<TSource>
{
    public int Id {get; set;}
    public TSource OriginalValue {get; set;}
    public TSource AlternativeValues {get; set;}

    // returns a sequence of changed properties:
    public IEnumerable<DiffPropertyInfo> GetChangedProperties()
    {
         // Use the function ExtractProperties defined above
         return Extractproperties(this.OriginalValue, this.AlternativeValue)
             .Where(extractedpropery => extractedProperty.IsChanged());
    }

}

结果是IEnumerable<...> ExtractDifferences<TSource>( IEnumerable<KeyValuePair<int, TSource> listA, IEnumerable<KeyValuePair<int, TSource> listB) { // The Id is in the Key of the KeyValuePair // inner join on same Id and get Id / OriginalValue / AlternativeValue // then Create PropertyComparisonCollection per Id / OriginalValue / AlternativeValue // Finally per Id return the properties that are changed return listA.Join(listB, // join ListA and ListB listA => listA.Key, // from every ListA take the Key (which is Id) listB => listB.Key, // from every ListB take the Key (listA, listB) => new PropertyComparisonCollection() // when they match { // make one new object Id = listA.Key, OriginalValue = listA.Value, AlternativeValue = listB.Value, }) // Keep only the Ids that have at least one changed value .Where(collectionElement => collectionElement.GetChangedProperties().Any()); } 的序列。序列中的每个元素都包含DiffPropertyInfoIdOriginalValue。您可以向每个元素询问已更改的属性。

如果您真的只希望Id和更改的属性值(如什么?作为字符串?作为对象?),请使用GetChangedProperties:

AlternativeValue

但是,那样您将丢失很多信息。我会去寻找原始的返回集合,您可以在其中轻松找到更改后的值。

顺便说一句,您是否注意到,直到知道我没有列举?您的序列尚未被访问。没有进行任何比较,没有枚举PropertyInfo,等等。一旦您请求第一个元素,就会枚举PropertyInfo,并获取第一个值。

注意:有些人不喜欢在各个班级中进行这种分离。如果需要,可以将所有内容合并为一个大的LINQ语句。我不确定这是否会提高可读性,可重用性,可维护性和可测试性。