LINQ - Full Outer Join

后端 未结 16 1562
既然无缘
既然无缘 2020-11-21 22:45

I have a list of people\'s ID and their first name, and a list of people\'s ID and their surname. Some people don\'t have a first name and some don\'t have a surname; I\'d l

16条回答
  •  粉色の甜心
    2020-11-21 22:59

    I'm guessing @sehe's approach is stronger, but until I understand it better, I find myself leap-frogging off of @MichaelSander's extension. I modified it to match the syntax and return type of the built-in Enumerable.Join() method described here. I appended the "distinct" suffix in respect to @cadrell0's comment under @JeffMercado's solution.

    public static class MyExtensions {
    
        public static IEnumerable FullJoinDistinct (
            this IEnumerable leftItems, 
            IEnumerable rightItems, 
            Func leftKeySelector, 
            Func rightKeySelector,
            Func resultSelector
        ) {
    
            var leftJoin = 
                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);
    
            var rightJoin = 
                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);
    
            return leftJoin.Union(rightJoin);
        }
    
    }
    

    In the example, you would use it like this:

    var test = 
        firstNames
        .FullJoinDistinct(
            lastNames,
            f=> f.ID,
            j=> j.ID,
            (f,j)=> new {
                ID = f == null ? j.ID : f.ID, 
                leftName = f == null ? null : f.Name,
                rightName = j == null ? null : j.Name
            }
        );
    

    In the future, as I learn more, I have a feeling I'll be migrating to @sehe's logic given it's popularity. But even then I'll have to be careful, because I feel it is important to have at least one overload that matches the syntax of the existing ".Join()" method if feasible, for two reasons:

    1. Consistency in methods helps save time, avoid errors, and avoid unintended behavior.
    2. If there ever is an out-of-the-box ".FullJoin()" method in the future, I would imagine it will try to keep to the syntax of the currently existing ".Join()" method if it can. If it does, then if you want to migrate to it, you can simply rename your functions without changing the parameters or worrying about different return types breaking your code.

    I'm still new with generics, extensions, Func statements, and other features, so feedback is certainly welcome.

    EDIT: Didn't take me long to realize there was a problem with my code. I was doing a .Dump() in LINQPad and looking at the return type. It was just IEnumerable, so I tried to match it. But when I actually did a .Where() or .Select() on my extension I got an error: "'System Collections.IEnumerable' does not contain a definition for 'Select' and ...". So in the end I was able to match the input syntax of .Join(), but not the return behavior.

    EDIT: Added "TResult" to the return type for the function. Missed that when reading the Microsoft article, and of course it makes sense. With this fix, it now seems the return behavior is in line with my goals after all.

提交回复
热议问题