Squares Array of an Int array

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };

var squaresOfNumbers = from i in numbers
join j in numbers on i equals j
select (new { aa = i, bb = i * j });
foreach (var kk in squaresOfNumbers)
Console.WriteLine(kk.aa.ToString() + " - - " + kk.bb.ToString());
notes:
1) the condition "i equals j" cannot be written as "j equals i" - unlike TSQL
2) new { aa = i, bb = i * j } is an anonymous object
3) Actual execution (for square nXn) did not happen until last line --"var kk in squaresOfNumbers"
==========================
Above code makes a cross join where a filter is applied with a condition.
Following is a way to make Left outer or right outer join
// left outer join -- check ".DefaultIfEmpty()"
var squaresOfNumbers2 = from i in numbers
join j in numbers on i equals j into tempj
from k in tempj
from jj in tempj.DefaultIfEmpty()
select (new { aa = i, bb = i * jj });
// for Right outer join ".DefaultIfEmpty()" need to applied on first list
// a more decent way of doing cross join is
var squaresOfNumbers3 = from i in numbers
from j in numbers
where i == j
select (new { aa = i, bb = i * j });
// simple Join example
int?[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 , null};
var combined2 = from d in numbers
join h in numbers on 1 equals 1
into dh
from subH in dh
where d != subH //&& d!= null && subDH !=null
select new { aa = d, bb = subH * d };