0

Hi all i have three classes like this as below

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;}
}

and i have data format like this

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 };

All are having different structure and i will be getting data in list of above classes like List<A>, List<B> and List<C>

I am looking to form a data kind of structure below with these three lists merged into single one like as below

I am looking for result list looks like as below.

Result :

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

Is there any way i can achieve this kind of result object, I know if you have same structure we can concatenate the list but here i have different structure.

Could any one please let me know any ideas on how to do achieve this that would be very grateful to me Many thanks in advance

3
  • What logic decides that this row is valid: 1 john test 1 true but not this 2 Steve test 2 false? is it just by order? Commented May 20, 2020 at 1:49
  • it is just by order only and in each list we need to go through the order and concatenate the items Commented May 20, 2020 at 1:49
  • It seems, that you'll need something like union type, which will added in future versions of C# Commented May 20, 2020 at 7:53

3 Answers 3

1

Addition after comment: in the while part of my code I forgot to MoveNext. Repaired. Thanks enigma state for noticing.

So you have three sequences of different items, and you want a method, that returns a sequence containing the same indexed elements of your sequences.

This is a difficult way to say, that you want a sequence like:

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

So if one of the sequences runs out of elements, you want to continue enumerating using NULL as value

This method is very similar to Enumerable.Zip, except that you have three sequences, and you want to continue enumerating if one of the sequences is too short.

Whenever you think there is a missing LINQ method, consider writing an extension method for IEnumerable. Especially if you think you can reuse it in other situations

Creating an extension method for an IEnumerable is usually fairly simple. See extension methods demystified

I'll write a generic extension method, that has three input sequences, and a result selector to define the returned output sequence. This result selector is similar to the selector parameter in 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;

        t1Available = enumerator1.MoveNext();
        t2Available = enumerator2.MoveNext();
        t3Available = enumerator3.MoveNext();
        anyAvailabe = t1Available || t2Available || t3Available;
    }
}

Usage:

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,
    });

Do note that this result selector can be optimized. For instance, whether parameter a equals null is checked twice. With a little more effort you can make sure that every input of the resultSelector is checked only once. For the example I chose simplicity above efficiency.

The nice thing is that you can use ZipWithNull for any sequence that you want to zip with null values as substitute. Because I created ZipWithNull as any other LINQ method, I can intertwine it with other LINQ methods:

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();
Sign up to request clarification or add additional context in comments.

1 Comment

When i am trying to loop through the result using foreach like this foreach (var item in result) {} it is going through the infinite loop.. i need to process the results for further data manipulation but it is going through the infinite loop
0

It's not very clear what you are trying to do, but I see 3 ways you could possibly accomplish this.

Most simply, you can create a List<object> that contains your data of different classes. You would then lose the information about what class each object is (you could store these objects in the List as Tuple with Type).

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

Another solution would be to have all of these classes share a base class or interface and construct the List of that type.

When reading from the List you could check types like 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);

You could also create a "joined" version of A, B, and C which might suit your use-case (since it looks like you might be creating a table from the data).

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

4 Comments

thanks for the input, how can we use aggregate since there is no relationship between three classes to join
You would either need an identifier on each object that indicates them to be related (e.g. the Id on C or some other data that describes the relationships (e.g. C.Id of 1 is equivalent to A.Name of John). How were you able to generate the example Result rows in your question? I think you would use the same defining relationship
I am trying to export document in word and looking to generate a table with sample data i have mentioned and it does not have any relationship
I am not able to establish the relationship between those classes and is there any other way to get through this
0

So you have a couple of challenges in that the models you provided don't match the lists you provided (e.g. classes are A and C not a and c and Id on C should be string not int), but after changing all that we can do sth like this:

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]);
            }
        }
    }

And then

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

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

If you kind of really wanted an object instead of a dictionary, you could go for ExpandoObject but then essentially you will get experience reverse to the above.

In my listing, if two objects (e.g. 'A' and 'C') have a duplicate Property Name, the latter will override the former. With ExpandoObject the first value you set will be the one that comes in the result.

Update Here's an example with Expando

Add using System.Dynamic; to your file.

Then

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); 

1 Comment

these are all creating list of lists where as i need only single list with items combined from those three lists.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.