将结构不同的对象的不同列表合并或压缩到单个列表中

问题描述 投票:0回答:3

大家好,我有如下三个班级

class A 
{
    public int number{get; set;}
    public string name{get; set;}
}
class B
{
   public string casted{get; set;}
}
class C
{
    public int Id{get; set;}
    public bool isSelect {get; set;}
}

而且我有这样的数据格式

var aObject = new A () { number = 11, name = "John" };
var aObject2= new A () { number = 22, name = "Steve" }; 

IList<A> ListA= new List<A>(){ aObject,aObject2 };

var bObject1 = new B () { casted = "test" };
var bObject2 = new B () { casted = "test1" };
var bObject3 = new B () { casted = "test2" };

IList<B> ListB = new List<B>(){ bObject1 , bObject2 ,bObject3 };

var cObject = new C() { Id = "1", isSelect = true };
var cObject2 = new C(){ Id = "2", isSelect = false };

IList<C> ListC = new List<C>() { cObject ,cObject2 };

所有人都具有不同的结构,我将在上述类的列表中获取数据,例如List<A>, List<B> and List<C>

我希望在下面将这三个列表合并为一个,从而形成一种数据类型的结构,如下所示

结果

Number    name    casted    Id    isSelect
 11       john    test      1     true
 22       Steve   test1     2     false
  -        -      test2     -       -

我有什么方法可以实现这种结果对象,我知道如果您具有相同的结构,我们可以将列表连接起来,但是在这里我具有不同的结构。

任何人都可以让我知道如何实现这一目标的想法,这将非常感谢我

c# .net list linq generics
3个回答
1
投票

因此,您具有三个不同项目的序列,并且需要一个方法,该方法返回一个序列,该序列包含序列中相同的索引元素。

这很难说,您想要一个类似的序列:

A[0] / B[0] / C[0]
A[1] / B[1] / C[1]
A[2] / null / C[2] 
etc.

因此,如果序列之一用完了元素,您想继续使用NULL作为值进行枚举

此方法与Enumerable.Zip非常相似,不同之处在于您有三个序列,并且如果其中一个序列太短,则要继续枚举。

[只要您认为缺少LINQ方法,请考虑为IEnumerable编写扩展方法。尤其是如果您认为您可以在其他情况下重用它的话

为IEnumerable创建扩展方法通常非常简单。参见extension methods demystified

我将编写一个通用扩展方法,该方法具有三个输入序列,以及一个用于选择返回的输出序列的结果选择器。此结果选择器类似于Enumerable.Select中的选择器参数。

public static IEnumerable<TResult> ZipWithNull<T1, T2, T2, TResult>(
    this IEnumerable<T1> source1,
    IEnumerable<T2> source2,
    IEnumerable<T3> source3,
    Func<T1, T2, T3, TResult> resultSelector)
{
    // get the enumerators and start enumerating until there are no more elements
    IEnumerator<T1> enumerator1 = source1.GetEnumerator();
    IEnumerator<T2> enumerator2 = source2.GetEnumerator();
    IEnumerator<T3> enumerator3 = source3.GetEnumerator();

    // check if there is at least one item available
    bool t1Available = enumerator1.MoveNext();
    bool t2Available = enumerator2.MoveNext();
    bool t3Available = enumerator3.MoveNext();
    bool anyAvailabe = t1Available || t2Available || t3Available;

    while (anyAvailable)
    {
        // if available use the Current, else use default (= null for classes)
        T1 t1 = t1Available ? enumerator1.Current ?? default(T1);
        T2 t2 = t2Available ? enumerator2.Current ?? default(T2);
        T3 t3 = t3Available ? enumerator3.Current ?? default(T3);

        TResult result = resultSelector(t1, t2, t3);
        yield return result;
    }
}

用法:

List<A> listA = ...
List<B> listA = ...
List<C> listA = ...

// you want a dash if the value is null
const string dash = "-";

var result = listA.ZipWithNull(listB, listC,

    // parameter ResultSelector: taks one a, b, c and create the output:
    (a, b, c) => new
    {
        Number = a?.Number.ToString() ?? dash,
        Name = a?.Name ?? dash,
        Casted = b?.Casted ?? dash,
        Id = c?.Id.ToString() ?? dash,
        IsSelect = c?.IsSelect.ToString() ?? dash,
    });

请注意,可以优化此结果选择器。例如,两次检查参数a是否等于null。稍加努力,您可以确保对resultSelector的每个输入仅检查一次。对于该示例,我选择了简单而不是效率。

很好的是,对于要压缩以空值代替的任何序列,可以使用ZipWithNull。因为我创建了ZipWithNull作为其他任何LINQ方法,所以我可以将其与其他LINQ方法交织在一起:

var result = customers
    .Where(customer => customer.BirthDay.Year >= 2000)
    .ZipWithNull(
         addresses.Where(address => address.City == "Amsterdam"),
         orders.Where(order => order.Total > 1000,
         (customer, address, order) => new {...});
    .GroupBy(...)
    .OrderByDescending(...)
    // Etc();

0
投票

尚不清楚您要做什么,但是我看到了三种可能的方法。

最简单地说,您可以创建一个List<object>,其中包含不同类别的数据。然后,您将丢失有关每个对象是什么类的信息(可以将这些对象作为ListTuple一起存储在Type中。)>

List<object> joinedList = new List<object>();
joinedList.AddRange(ListA.Cast<object>());
joinedList.AddRange(ListB.Cast<object>());
joinedList.AddRange(ListC.Cast<object>());

另一个解决方案是让所有这些类共享一个基类或接口并构造该类型的List

List读取时,您可以检查listItem is A之类的类型>

class z
{
}
class a : z
{
    public int number{get; set;}
    public string name{get; set;}
}
class B : z
{
   public string casted{get; set;}
}
class c : z
{
    public int Id{get; set;}
    public bool isSelect {get; set;}
}

List<z> joinedList = new List<z>();
joinedList.AddRange(ListA);
joinedList.AddRange(ListB);
joinedList.AddRange(ListC);

您还可以创建可能适合您的用例的ABC的“联合”版本(因为看起来好像您是根据数据创建表的。)

class Aggregate
{
   A aData {get;set;}
   B bData {get;set;}
   C cData {get;set;}
}

List<Aggregate> aggregatedData = new List<Aggregate>();
// join your data together into aggregate and add to list

所以您面临一些挑战,因为您提供的模型与您提供的列表不匹配(例如,A上的类是Ca而不是c以及IdC应该是string而不是int),但是在更改所有内容后,我们可以这样做:

private static void AddAllValuesToDictionary<T>(List<Dictionary<string, object>> target, IList<T> source) {
        var properties = typeof(T).GetProperties();
        for (var i = 0; i < source.Count(); i++) 
        {
            if (target.Count() <= i) {
               target.Add(new Dictionary<string, object>()); 
            }

            foreach(var prop in properties) {
                target[i][prop.Name] = prop.GetValue(source[i]);
            }
        }
    }

然后

var result = new List<Dictionary<string, object>>();

AddAllValuesToDictionary(result, ListA);
AddAllValuesToDictionary(result, ListB);
AddAllValuesToDictionary(result, ListC);

如果您真的想要一个对象而不是一个字典,可以选择ExpandoObject,但从本质上来说,您将获得与上述相反的经验。

在我的清单中,如果两个对象(例如'A'和'C')具有相同的属性名称,则后者将覆盖前者。使用ExpandoObject,您设置的第一个值将是结果中出现的值。

更新

以下是带有Expando]的示例

using System.Dynamic;添加到您的文件。

然后

private static void AddAllValuesToListOfExpandos<T>(List<ExpandoObject> target, IList<T> source) {
        var properties = typeof(T).GetProperties();
        for (var i = 0; i < source.Count(); i++) 
        {
            if (target.Count() <= i) {
               target.Add(new ExpandoObject()); 
            }

            foreach(var prop in properties) {
                target[i].TryAdd(prop.Name, prop.GetValue(source[i]));
            }
        }
    }
}

        var result2 = new List<ExpandoObject>();
        AddAllValuesToListOfExpandos(result2, ListA);
        AddAllValuesToListOfExpandos(result2, ListB);
        AddAllValuesToListOfExpandos(result2, ListC);
// Since you're using ExpandoObject you can now use the properties
// the same way you do with dynamic
        Console.WriteLine(((dynamic)result2.First()).casted);
        Console.WriteLine(((dynamic)result2.First()).number);
        Console.WriteLine(((dynamic)result2.Last()).casted);
// note: Next line will fail with RuntimeBinderException
        Console.WriteLine(((dynamic)result2.Last()).number); 


0
投票

所以您面临一些挑战,因为您提供的模型与您提供的列表不匹配(例如,A上的类是Ca而不是c以及IdC应该是string而不是int),但是在更改所有内容后,我们可以这样做:

© www.soinside.com 2019 - 2024. All rights reserved.