There are many approaches depending on what is the priority, for example. Union + Lookup:
//this will create a key value pairs: id -> matching instances var idMap = list1.Union(list2).ToLookup(myClass => myClass.ID); //now just select for each ID the instance you want, ex. with some value var mergedInstances = idMap.Select(row => row.FirstOrDefault(myClass => myClass.ExtId.HasValue) ?? row.First());
The advantage above is that it will work with any number of all lists, even if they contain many duplicate isntances, and then you can easily change the merge conditions
A slight improvement would be to retrieve a method to merge instances:
MyClass MergeInstances(IEnumerable<MyClass> instances){ return instances.FirstOrDefault(myClass => myClass.ExtId.HasValue) ?? instances.First();
and now just use it in the code above
var mergedInstances = idMap.Select(MergeInstances);
Clean, flexible, simple, no additional conditions. Performance is not perfect, but who cares.
Edit: since performance is a priority, a few more options
Do a search as shown above, but only for a smaller list. Then iterate through more and make the necessary changes O (m log m) + O (n). m - the size of the smaller list, n - the size of the list is larger - should be the fastest.
Order both lists by item IDs. Create a for loop that iterates through both of them, keeping the current index for the item with the same identifier for both lists. Move the pointer to the next smallest identifier found in both lists, if there is one, move only that. O (n log n) + O (m log m) + O (n);
mikus source share