https://stackoverflow.com/questions/62976822/how-to-create-merge-item-method-in-c-sharp

CSharp · Console

/Program.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Reflection;
using System.Linq.Expressions;

using Newtonsoft.Json;

using NUnit.Framework; // Throws NUnit.Framework.AssertionException

//https://stackoverflow.com/questions/62976822/how-to-create-merge-item-method-in-c-sharp

	public class Items
	{
		public int ItemNumber { get; set; }
		public DateTime ItemDate { get; set; }
		public List<ItemsLine> ItemsLines { get; set; } = new List<ItemsLine>();

		/// MergeItem appends the items from the sourceItem to the current item
		/// </summary>
		/// <param name="sourceItem">Item to merge from</param>
		public void MergeItem( Items sourceItem)
		{
			var mergedItems = ItemsLines.FullOuterJoin(sourceItem.ItemsLines, i => i.ItemLineId, i => i.ItemLineId, (i1, i2) =>
													   {
														   if (i1 == default)
															   return i2;
														   else if (i2 == default)
															   return i1;
														   //It's not clear from your question whether the average cost should be weighted by the quantities.
														   //i1.Cost = (i1.Cost + i2.Cost) / 2.0,
														   i1.Cost = (i1.Quantity * i1.Cost + i2.Quantity * i2.Cost) / (i1.Quantity + i2.Quantity);
														   i1.Quantity = i1.Quantity + i2.Quantity;
														   return i1;
													   })
				.ToList();
			ItemsLines.Clear();
			ItemsLines.AddRange(mergedItems);
		}
	}

	public class ItemsLine
	{
		public int ItemLineId { get; set; }
		public int Quantity { get; set; }
		public double Cost { get; set; }

		// public double Total { get { return this.Cost * this.Quantity; } }

		public ItemsLine(int itemLineId, int quantity, double cost)
		{
			this.ItemLineId = itemLineId;
			this.Quantity = quantity;
			this.Cost = cost;
		}

		public ItemsLine() { }
	}

    class TestClass
    {
        public static void Test()
        {
			Console.WriteLine("\nTesting code from question:");
			MergeItem();
			Console.WriteLine("\nTesting with merge required:");
			TestTestWithMerge();
		}

		private static void MergeItem()
		{
			var item1 = new Items();

			item1.ItemsLines.Add(new ItemsLine()
			{
				ItemLineId = 1,
				Cost = 10.33,
				Quantity = 4,
			});

			var item2 = new Items();

			item2.ItemsLines.Add(new ItemsLine()
			{
				ItemLineId = 2,
				Cost = 5.22,
				Quantity = 1,
			});

			item2.ItemsLines.Add(new ItemsLine()
			{
				ItemLineId = 3,
				Cost = 6.27,
				Quantity = 3,
			});

			item1.MergeItem(item2);
			Console.WriteLine("Grand total after merge = {0}", item1.ItemsLines.Sum(i => i.Cost * i.Quantity));
		}
		
        public static void TestTestWithMerge()
        {
			var item1 = new Items();

			// Fixed
			item1.ItemsLines.Add(new ItemsLine()
							  {
								  ItemLineId = 1,
								  Cost = 10.33,
								  Quantity = 4,
							  });
			item1.ItemsLines.Add(new ItemsLine()
							  {
								  ItemLineId = 101,
								  Cost = 1,
								  Quantity = 1,
							  });

			var item2 = new Items();

			item2.ItemsLines.Add(new ItemsLine()
							  {
								  ItemLineId = 2,
								  Cost = 5.22,
								  Quantity = 1,

							  });

			item2.ItemsLines.Add(new ItemsLine()
							  {
								  ItemLineId = 3,
								  Cost = 6.27,
								  Quantity = 3,

							  });

			item2.ItemsLines.Add(new ItemsLine()
							  {
								  ItemLineId = 101,
								  Cost = 1,
								  Quantity = 1,
							  });
			
			var originalQualtity1 = item1.ItemsLines.Sum(i => i.Quantity);
			var originalQualtity2 = item2.ItemsLines.Sum(i => i.Quantity);
			
			item1.MergeItem(item2);

			Console.WriteLine("Merged items: ");
			Console.WriteLine(JsonConvert.SerializeObject(item1, Formatting.Indented));

			Console.WriteLine("Grand total after merge = {0}", item1.ItemsLines.Sum(i => i.Cost * i.Quantity));
			
			Assert.IsTrue(item1.ItemsLines.Count == 4);
			Assert.AreEqual(1, item1.ItemsLines.Where(i => i.ItemLineId == 101).Count());
			Assert.AreEqual(2, item1.ItemsLines.Where(i => i.ItemLineId == 101).Single().Quantity);
			Assert.AreEqual(1.0, item1.ItemsLines.Where(i => i.ItemLineId == 101).Single().Cost);
			Assert.AreEqual(originalQualtity1 + originalQualtity2, item1.ItemsLines.Sum(i => i.Quantity));
		}	
	}

	// Copied from this answer https://stackoverflow.com/a/43669055/3744182
	// To https://stackoverflow.com/questions/5489987/linq-full-outer-join
	// By https://stackoverflow.com/users/2557128/netmage

	public static class Ext {
		public static IEnumerable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(
			this IEnumerable<TLeft> leftItems,
			IEnumerable<TRight> rightItems,
			Func<TLeft, TKey> leftKeySelector,
			Func<TRight, TKey> rightKeySelector,
			Func<TLeft, TRight, TResult> resultSelector) {

			return from left in leftItems
				   join right in rightItems on leftKeySelector(left) equals rightKeySelector(right) into temp
				   from right in temp.DefaultIfEmpty()
				   select resultSelector(left, right);
		}

		public static IEnumerable<TResult> RightOuterJoin<TLeft, TRight, TKey, TResult>(
			this IEnumerable<TLeft> leftItems,
			IEnumerable<TRight> rightItems,
			Func<TLeft, TKey> leftKeySelector,
			Func<TRight, TKey> rightKeySelector,
			Func<TLeft, TRight, TResult> resultSelector) {

			return from right in rightItems
				   join left in leftItems on rightKeySelector(right) equals leftKeySelector(left) into temp
				   from left in temp.DefaultIfEmpty()
				   select resultSelector(left, right);
		}

		public static IEnumerable<TResult> FullOuterJoinDistinct<TLeft, TRight, TKey, TResult>(
			this IEnumerable<TLeft> leftItems,
			IEnumerable<TRight> rightItems,
			Func<TLeft, TKey> leftKeySelector,
			Func<TRight, TKey> rightKeySelector,
			Func<TLeft, TRight, TResult> resultSelector) {

			return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Union(leftItems.RightOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
		}

		public static IEnumerable<TResult> RightAntiSemiJoin<TLeft, TRight, TKey, TResult>(
			this IEnumerable<TLeft> leftItems,
			IEnumerable<TRight> rightItems,
			Func<TLeft, TKey> leftKeySelector,
			Func<TRight, TKey> rightKeySelector,
			Func<TLeft, TRight, TResult> resultSelector) {

			var hashLK = new HashSet<TKey>(from l in leftItems select leftKeySelector(l));
			return rightItems.Where(r => !hashLK.Contains(rightKeySelector(r))).Select(r => resultSelector(default(TLeft),r));
		}

		public static IEnumerable<TResult> FullOuterJoin<TLeft, TRight, TKey, TResult>(
			this IEnumerable<TLeft> leftItems,
			IEnumerable<TRight> rightItems,
			Func<TLeft, TKey> leftKeySelector,
			Func<TRight, TKey> rightKeySelector,
			Func<TLeft, TRight, TResult> resultSelector)  where TLeft : class {

			return leftItems.LeftOuterJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector).Concat(leftItems.RightAntiSemiJoin(rightItems, leftKeySelector, rightKeySelector, resultSelector));
		}
		
		// IQueryable implementations omitted.
	}

	// End of copied code

	public class Program
	{
		public static void Main()
		{
			Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
			Console.WriteLine();

			try
			{
				TestClass.Test();
			}
			catch (Exception ex)
			{
				Console.WriteLine("Failed with unhandled exception: ");
				Console.WriteLine(ex);
				throw;
			}
		}
		
		public static string GetNetCoreVersion()
		{
			//https://techblog.dorogin.com/how-to-detect-net-core-version-in-runtime-ecd65ad695be
			//
			var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
			var assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
			int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
			if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
				return assemblyPath[netCoreAppIndex + 1];
			return null;
		}
	}

#if false

It sounds as though you want to do a [full outer join](https://www.w3schools.com/sql/sql_join_full.asp) of the `ItemsLines` lists, matching on `ItemLineId` and using the projection logic as described in your question.  So I grabbed an implementation of full outer join from [this answer](https://stackoverflow.com/a/43669055/3744182) to [LINQ - Full Outer Join](https://stackoverflow.com/q/5489987/3744182) by [NetMage](https://stackoverflow.com/users/2557128/netmage) and it worked perfectly, see https://dotnetfiddle.net/AuDSPI.  Does that answer your question?
													   
#endif
;