What is the method syntax for this LINQ, is it a join, and if not then what is it?

I find myself using this pattern lots in LINQ:

    class Thing
    {
        public int ID { get; set; }
        public int ColorID { get; set; }
    }
    class Color
    {
        public int ID { get; set; }
        public string Description { get; set; }
    }
    static void Main()
    {
        var things = new List<Thing> { new Thing { ID = 1, ColorID = 1 }, new Thing { ID = 2, ColorID = 1 }, new Thing { ID = 3, ColorID = 2 }, new Thing { ID = 4, ColorID = 1 } };
        var colors = new List<Color> { new Color { ID = 1, Description = "red" }, new Color { ID = 2, Description = "green" }, new Color { ID = 3, Description = "blue" } };
        var joined = (from thing in things
                      from color in colors
                      where thing.ColorID == color.ID
                      select new { ID = thing.ID, Color = color.Description }).ToArray();
        foreach (var thing in joined)
        {
            Console.WriteLine("(" + thing.ID + ", " + thing.Color + ")");
        }
        //Writes:
        //(1, red)
        //(2, red)
        //(3, green
        //(4, red)
    }

The core of it, the four lines of query syntax, feel very much like an INNER JOIN that I might write in tSQL but when I look at examples of LINQ query syntax for joins they use the word join, while the above LINQ does not.

What is the 'join' that the above LINQ is performing and how would I rewrite it in LINQ method syntax?

Jon Skeet
people
quotationmark

It's not performing a join at all, in terms of LINQ understanding. It's just filtering based on two properties where one happens to come from one range variable and the other happens to come from the other. In method syntax you'd write this as:

var joined = things.SelectMany(thing => colors,
                               (thing, color) => new { thing, color })
                   .Where(pair => pair.thing.ColorID == pair.color.ID)
                   .Select(pair => new { ID = pair.thing.ID,
                                         Color = pair.color.Description })
                   .ToArray();

The pair here is effectively automatically introduced by the compiler as a transparent identifier. This isn't due to the filtering in the where, but due to having multiple from clauses... every from clause after the first one uses SelectMany, and introduces a transparent identifier to allow you to refer to multiple range variables (thing and color) which are composed into a separate object so that each stage of the pipeline only notionally deals with one value.

Note that when you've got two from clauses:

from person in people
from book in books

... that acts like a Cartesian join... but LINQ allows for something more subtle, such as:

from person in people
from book in person.Books

In other words, the second sequence can depend on the "current" value from the first sequence. Any subsequent stage of the pipeline (e.g. a where or select) acts on each pair: one from the first sequence, and then one from the second sequence generated from the element in the first sequence.

people

See more on this question at Stackoverflow