(function () {
	'use strict';

	Array.prototype.innerJoin = function (arr, outer, inner, result, comparer) {
		comparer = comparer || linq.EqualityComparer;
		var res = [];

		this.forEach(function (t) {
			arr.where(function (u) {
					return comparer(outer(t), inner(u));
				})
				.forEach(function (u) {
					res.push(result(t, u));
				});
		});

		return res;
	};

}());
(function () {
	'use strict';
	
	Array.prototype.innerJoin = function (inner, outerKeySelector, innerKeySelector, resultSelector, comparer) {
		if (!inner) { throw new TypeError("inner is undefined."); }
		if (!innerKeySelector) { throw new TypeError("innerKeySelector is undefined."); }
		if (!outerKeySelector) { throw new TypeError("outerKeySelector is undefined."); }
		
		resultSelector = resultSelector || function (o, i) { 
			return [o, i];
		};
		comparer = comparer || linq.EqualityComparer;
		var innerKeyed = inner.select(function (e) { 
			return {
				key: innerKeySelector(e),
				element: e
			};
		});
		return this.selectMany(function (o) { 
			var key = outerKeySelector(o);
			return innerKeyed.where(function (i) {
					return comparer(key, i.key);
				})
				.select(function (i) {
					return i.element;
				});
		}, resultSelector.bind(null));
	};

}());

Validate arguments, make resultSelector optional, only apply key selectors once
Validate arguments, make resultSelector optional, only apply key selectors once

Validate arguments, make resultSelector optional, only apply key selectors once.