345

Is it possible to convert two or more lists into one single list, in .NET using C#?

For example,

public static List<Product> GetAllProducts(int categoryId){ .... }
.
.
.
var productCollection1 = GetAllProducts(CategoryId1);
var productCollection2 = GetAllProducts(CategoryId2);
var productCollection3 = GetAllProducts(CategoryId3);
zekia
  • 4,527
  • 6
  • 38
  • 47

15 Answers15

600

You can use the LINQ Concat and ToList methods:

var allProducts = productCollection1.Concat(productCollection2)
                                    .Concat(productCollection3)
                                    .ToList();

Note that there are more efficient ways to do this - the above will basically loop through all the entries, creating a dynamically sized buffer. As you can predict the size to start with, you don't need this dynamic sizing... so you could use:

var allProducts = new List<Product>(productCollection1.Count +
                                    productCollection2.Count +
                                    productCollection3.Count);
allProducts.AddRange(productCollection1);
allProducts.AddRange(productCollection2);
allProducts.AddRange(productCollection3);

(AddRange is special-cased for ICollection<T> for efficiency.)

I wouldn't take this approach unless you really have to though.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 4
    BTW, `productCollection1.ForEach(p => allProducts.Add(p))` is more performant than AddRange. – Marc Climent Jan 31 '13 at 12:32
  • 10
    @MarcCliment: That's a fairly bold blanket statement - especially as `AddRange` gets to do a block copy from one underlying array to another. Do you have any links for evidence of this? – Jon Skeet Jan 31 '13 at 12:43
  • @JonSkeet For the particular case of adding two lists, I've written this https://gist.github.com/4690433 and consistently List.ForEach is faster. Maybe the test is wrong... – Marc Climent Feb 01 '13 at 10:10
  • 5
    @MarcCliment: Well for one thing, in your tests you're *not* creating the list with the correct final size - whereas the code in my answer does. Do that, and the 10000*10000 test is faster using AddRange, at least - although other results are inconsistent. (You should also force garbage collection between tests - and I'd argue that the very short tests are meaninglessly small.) – Jon Skeet Feb 01 '13 at 10:16
  • 2
    @JonSkeet Thanks for your suggestions. I updated the Gist but ForEach slightly outperforms AddRange in this particular case. – Marc Climent Feb 01 '13 at 10:46
  • 6
    @MarcCliment: Which particular case? Which CLR? Which CPU architecture? On my machine I get a mixture of results. This is why I'm loathe to state/accept blanket statements such as "BTW, `productionCollection1.ForEach(...)` is more performant than AddRange". Performance is very rarely so easily described. I *am* surprised that AddRange isn't beating it handily though - I need to investigate that further. – Jon Skeet Feb 01 '13 at 10:51
  • I have something like this: `var allProducts = lstName.Concat(lstCMSID) .Concat(lstSpecialtyPhys) .ToList();` which adds it to the GridView but as one column. I would like to split them into three separate columns. – Si8 Dec 10 '15 at 19:47
  • 33
    I've updated the gist (https://gist.github.com/mcliment/4690433) with the performance test using BenchmarksDotNet and properly tested, `AddRange` is the best option for raw speed (about 4x and the larger the lists the better the increase), as Jon suggeste. – Marc Climent Dec 27 '16 at 21:38
59

Assuming you want a list containing all of the products for the specified category-Ids, you can treat your query as a projection followed by a flattening operation. There's a LINQ operator that does that: SelectMany.

// implicitly List<Product>
var products = new[] { CategoryId1, CategoryId2, CategoryId3 }
                     .SelectMany(id => GetAllProducts(id))
                     .ToList();

In C# 4, you can shorten the SelectMany to: .SelectMany(GetAllProducts)

If you already have lists representing the products for each Id, then what you need is a concatenation, as others point out.

Ani
  • 111,048
  • 26
  • 262
  • 307
  • 12
    If the OP doesn't need the individual lists for any other reason, this is a great solution. – Jon Skeet Dec 20 '10 at 09:33
  • 2
    Having a similar problem and was about to give up and post a question, when I found this. This is the best answer. Especially if the code already has those categories in a list or array, which was true in my case. –  Aug 23 '16 at 14:49
36

you can combine them using LINQ:

  list = list1.Concat(list2).Concat(list3).ToList();

the more traditional approach of using List.AddRange() might be more efficient though.

Botz3000
  • 39,020
  • 8
  • 103
  • 127
22

List.AddRange will change (mutate) an existing list by adding additional elements:

list1.AddRange(list2); // list1 now also has list2's items appended to it.

Alternatively, in modern immutable style, you can project out a new list without changing the existing lists:

Concat, which presents an unordered sequence of list1's items, followed by list2's items:

var concatenated = list1.Concat(list2).ToList();

Not quite the same, Union projects a distinct sequence of items:

var distinct = list1.Union(list2).ToList();

Note that for the 'value type distinct' behaviour of Union to work on reference types, that you will need to define equality comparisons for your classes (or alternatively use the built in comparators of record types).

StuartLC
  • 104,537
  • 17
  • 209
  • 285
12

You could use the Concat extension method:

var result = productCollection1
    .Concat(productCollection2)
    .Concat(productCollection3)
    .ToList();
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
12

I know this is an old question I thought I might just add my 2 cents.

If you have a List<Something>[] you can join them using Aggregate

public List<TType> Concat<TType>(params List<TType>[] lists)
{
    var result = lists.Aggregate(new List<TType>(), (x, y) => x.Concat(y).ToList());

    return result;
}

Hope this helps.

sQuir3l
  • 1,373
  • 7
  • 12
  • 1
    Finally a useful answer that doesn't assume you have your objects ready and labeled at compile time. – Daniel Feb 02 '21 at 15:10
  • @Daniel: I want to understand your remark but I don't. Can you help me and elaborate? Thx for the help! – qqtf Jul 05 '21 at 10:00
  • 4
    @qqtf instead of `list1.Concat(list2).Concat(list3)` what if you had a variable amount of lists ? or a List> – Ahmed Fwela Nov 24 '21 at 23:24
10
list4 = list1.Concat(list2).Concat(list3).ToList();
decyclone
  • 30,394
  • 6
  • 63
  • 80
4
// I would make it a little bit more simple

 var products = new List<List<product>> {item1, item2, item3 }.SelectMany(id => id).ToList();

This way it is a multi dimensional List and the .SelectMany() will flatten it into a IEnumerable of product then I use the .ToList() method after.

Gringo Jaimes
  • 242
  • 3
  • 11
2

I've already commented it but I still think is a valid option, just test if in your environment is better one solution or the other. In my particular case, using source.ForEach(p => dest.Add(p)) performs better than the classic AddRange but I've not investigated why at the low level.

You can see an example code here: https://gist.github.com/mcliment/4690433

So the option would be:

var allProducts = new List<Product>(productCollection1.Count +
                                    productCollection2.Count +
                                    productCollection3.Count);

productCollection1.ForEach(p => allProducts.Add(p));
productCollection2.ForEach(p => allProducts.Add(p));
productCollection3.ForEach(p => allProducts.Add(p));

Test it to see if it works for you.

Disclaimer: I'm not advocating for this solution, I find Concat the most clear one. I just stated -in my discussion with Jon- that in my machine this case performs better than AddRange, but he says, with far more knowledge than I, that this does not make sense. There's the gist if you want to compare.

Marc Climent
  • 9,434
  • 2
  • 50
  • 55
  • Regardless of your debate with Jon Skeet in the comments, I much prefer the cleanness offered by his proposed solution, this just adds unnecessary interpretation as to the intention of the code. – Vix Jul 15 '15 at 10:16
  • 1
    I think the most clear option is Concat as a generalization of AddRange, semantically more correct and does not modify the list in-place which makes it chainable. The discussion with Jon was about performance, not cleanliness. – Marc Climent Jul 15 '15 at 10:55
  • I have something like this: `var allProducts = lstName.Concat(lstCMSID) .Concat(lstSpecialtyPhys) .ToList();` which adds it to the GridView but as one column. I would like to split them into three separate columns. – Si8 Dec 10 '15 at 19:47
2

To merge or Combine to Lists into a One list.

  • There is one thing that must be true: the type of both list will be equal.

  • For Example: if we have list of string so we can add add another list to the existing list which have list of type string otherwise we can't.

Example:

class Program
{
   static void Main(string[] args)
   {
      List<string> CustomerList_One = new List<string> 
      {
         "James",
         "Scott",
         "Mark",
         "John",
         "Sara",
         "Mary",
         "William",
         "Broad",
         "Ben",
         "Rich",
         "Hack",
         "Bob"
      };

      List<string> CustomerList_Two = new List<string> 
      {
         "Perter",
         "Parker",
         "Bond",
         "been",
         "Bilbo",
         "Cooper"
      };

      // Adding all contents of CustomerList_Two to CustomerList_One.
      CustomerList_One.AddRange(CustomerList_Two);

      // Creating another Listlist and assigning all Contents of CustomerList_One.
      List<string> AllCustomers = new List<string>();

      foreach (var item in CustomerList_One)
      {
         AllCustomers.Add(item);
      }

      // Removing CustomerList_One & CustomerList_Two.
      CustomerList_One = null;

      CustomerList_Two = null;
      // CustomerList_One & CustomerList_Two -- (Garbage Collected)
      GC.Collect();

      Console.WriteLine("Total No. of Customers : " +  AllCustomers.Count());
      Console.WriteLine("-------------------------------------------------");
      foreach (var customer in AllCustomers)
      {
         Console.WriteLine("Customer : " + customer);
      }
      Console.WriteLine("-------------------------------------------------");

   }
}
Rehan Shah
  • 1,505
  • 12
  • 28
1

In the special case: "All elements of List1 goes to a new List2": (e.g. a string list)

List<string> list2 = new List<string>(list1);

In this case, list2 is generated with all elements from list1.

Arthur Zennig
  • 2,058
  • 26
  • 20
0

You need to use Concat operation

Artem
  • 81
  • 1
  • 5
0

When you got few list but you don't know how many exactly, use this:

listsOfProducts contains few lists filled with objects.

List<Product> productListMerged = new List<Product>();

listsOfProducts.ForEach(q => q.ForEach(e => productListMerged.Add(e)));
john.kernel
  • 332
  • 3
  • 16
0

If you have an empty list and you want to merge it with a filled list, do not use Concat, use AddRange instead.

List<MyT> finalList = new ();

List<MyT> list = new List<MyT>() { a = 1, b = 2, c = 3 };

finalList.AddRange(list);
Ahmet Firat Keler
  • 2,603
  • 2
  • 11
  • 22
0

The answer from @JonSkeet is still correct but if you are using LINQ with Entity Framework (EF) then Concat can cause runtime errors and AddRange is not available for IEnumerable.

The simplest way to do this for Entity Framework would probably be a Union if you only need unique values.

https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.union?view=net-8.0

Using my DbContext with code like this:

UnresolvedThreatAndCountermeasures =
    product.ThreatAndCountermeasures.Where(tac => !tac.NotApplicable && !(tac.Verified && tac.Implemented)).Select(
        vuln => new ProductSummaryVulnerabilityDtoHelper()
        {
            AllExternalLinkIds = vuln.ExternalLinks.Select(x => x.Id).Union(vuln.Threat.ExternalLinks.Select(x => x.Id)).Union(vuln.Threat.Fork.ExternalLinks.Select(x => x.Id))
        })  

SQL generated looks like this:

SELECT [t0].[Id], [t1].[Id] AS [Id0], [t2].[Id] AS [Id1], [t3].[Id] AS [Id2], [t0].[ProductId]
FROM [ThreatAndCountermeasure] AS [t0]
INNER JOIN [Threats] AS [t1] ON [t0].[ThreatId] = [t1].[Id]
LEFT JOIN [Threats] AS [t2] ON [t1].[ForkId] = [t2].[Id]
OUTER APPLY (
    SELECT [e].[Id]
    FROM [ExternalLink] AS [e]
    WHERE [t0].[Id] = [e].[ThreatAndCountermeasureId]
    UNION
    SELECT [e0].[Id]
    FROM [ExternalLink] AS [e0]
    WHERE [t1].[Id] = [e0].[ThreatId]
    UNION
    SELECT [e1].[Id]
    FROM [ExternalLink] AS [e1]
    WHERE ([t2].[Id] IS NOT NULL) AND [t2].[Id] = [e1].[ThreatId]
) AS [t3]
WHERE [t0].[NotApplicable] = CAST(0 AS bit) AND ([t0].[Verified] = CAST(0 AS bit) OR [t0].[Implemented] = CAST(0 AS bit))

You could also do it like this if you for some reason actually want duplicates:

public class MergedList {
  public int PrincipalId {get;set;}
  public IEnumerable<int> CombinedIds {get;set;}
}

var entitySource = _dBcontext.EntitySource.Select(e => new MergedList()
{
    PrincipalId = e.Id,
    CombinedIds = CombineLists<int>(e.table1.Select(e => e.Id), e.table2.Select(e => e.Id), e.table3.Select(e => e.Id))
}).FirstOrDefault();

private static IEnumerable<T> CombineLists<T>(params IEnumerable<T>[] lists)
{
    List<T> result = new();

    foreach (var list in lists.Where(l => l != null))
    {
        result.AddRange(list);
    }

    return result;
}

I tested this using my DbContext like this:

UnresolvedThreatAndCountermeasures =
    product.ThreatAndCountermeasures.Where(tac => !tac.NotApplicable && !(tac.Verified && tac.Implemented)).Select(
        vuln => new ProductSummaryVulnerabilityDtoHelper()
        {
            AllExternalLinkIds = CombineLists<int>(vuln.ExternalLinks.Select(x => x.Id), vuln.Threat.ExternalLinks.Select(x => x.Id), vuln.Threat.Fork.ExternalLinks.Select(x => x.Id))
        })

and this is the query generated with expected results:

SELECT [t0].[Id], [t1].[Id] AS [Id0], [t3].[Id] AS [Id1], [e].[Id] AS [Id2], [e0].[Id] AS [Id3], [e1].[Id] AS [Id4], [t0].[ProductId]
FROM [ThreatAndCountermeasure] AS [t0]
INNER JOIN [Threats] AS [t1] ON [t0].[ThreatId] = [t1].[Id]
LEFT JOIN [Threats] AS [t3] ON [t1].[ForkId] = [t3].[Id]
LEFT JOIN [ExternalLink] AS [e] ON [t0].[Id] = [e].[ThreatAndCountermeasureId]
LEFT JOIN [ExternalLink] AS [e0] ON [t1].[Id] = [e0].[ThreatId]
LEFT JOIN [ExternalLink] AS [e1] ON [t3].[Id] = [e1].[ThreatId]
WHERE [t0].[NotApplicable] = CAST(0 AS bit) AND ([t0].[Verified] = CAST(0 AS bit) OR [t0].[Implemented] = CAST(0 AS bit)

Using Concat like this:

var entitySource = _dBcontext.EntitySource.Select(e => new MergedList()
{
    PrincipalId = e.Id,
    CombinedIds = e.table1.Select(e => e.Id).Concat(e.table2.Select(e => e.Id)).Concat(e.table3.Select(e => e.Id))
}).FirstOrDefault();

Or like this:

CombinedValues = e.table1.Concat(e.table2).Concat(e.table3)

Would cause an exception like this for EF Core 7:

System.InvalidOperationException: 'Unable to translate a collection subquery in a projection since either parent or the subquery doesn't project necessary information required to uniquely identify it and correctly generate results on the client side. This can happen when trying to correlate on keyless entity type. This can also happen for some cases of projection before 'Distinct' or some shapes of grouping key in case of 'GroupBy'. These should either contain all key properties of the entity that the operation is applied on, or only contain simple property access expressions.'

Even if you try to project the list chances are quite high you would end up with something like this for Concat:

System.InvalidOperationException: 'The LINQ expression 'MaterializeCollectionNavigation(
    Navigation: MyObject.MyList,
    subquery: DbSet<MyListObject>()
        .Where(e => EF.Property<int?>(t.Outer.Outer, "Id") != null && object.Equals(
            objA: (object)EF.Property<int?>(t.Outer.Outer, "Id"), 
            objB: (object)EF.Property<int?>(e, "MyObjectId")))
        .Where(i => EF.Property<int?>(t.Outer.Outer, "Id") != null && object.Equals(
            objA: (object)EF.Property<int?>(t.Outer.Outer, "Id"), 
            objB: (object)EF.Property<int?>(i, "MyObjectId"))))
    .AsQueryable()
    .Concat(MaterializeCollectionNavigation(
        Navigation: MyObject2.MyList,
        subquery: DbSet<MyListObject>()
            .Where(e0 => EF.Property<int?>(t.Outer.Inner, "Id") != null && object.Equals(
                objA: (object)EF.Property<int?>(t.Outer.Inner, "Id"), 
                objB: (object)EF.Property<int?>(e0, "MyObject2Id")))
            .Where(i => EF.Property<int?>(t.Outer.Inner, "Id") != null && object.Equals(
                objA: (object)EF.Property<int?>(t.Outer.Inner, "Id"), 
                objB: (object)EF.Property<int?>(i, "MyObject2Id")))))
    .Concat(t.Outer.Inner.ForkId != null ? MaterializeCollectionNavigation(
        Navigation: MyObject2.MyList,
        subquery: DbSet<MyListObject>()
            .Where(e1 => EF.Property<int?>(t.Inner, "Id") != null && object.Equals(
                objA: (object)EF.Property<int?>(t.Inner, "Id"), 
                objB: (object)EF.Property<int?>(e1, "MyObject2Id")))
            .Where(i => EF.Property<int?>(t.Inner, "Id") != null && object.Equals(
                objA: (object)EF.Property<int?>(t.Inner, "Id"), 
                objB: (object)EF.Property<int?>(i, "MyObject2Id")))) : new List<MyListObject>())' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'

See this post for more info:

https://github.com/dotnet/efcore/issues/26703#issuecomment-981843751

Original post:

https://stackoverflow.com/a/76046347/3850405

Ogglas
  • 62,132
  • 37
  • 328
  • 418