I don't like to answer my own question, but this one had me stumped and visually it doesn't make sense, and nobody else explained it.
After a night's sleep, it dawned on me that this is simply generic parameter shadowing. Shadowing of generic parameters apparently exists (I did not know this since I don't write inner classes much), and works just like standard parameters. So even though T is a type parameter in the outer class, the compiler treats the T in the inner method (or class) as T2, and they are not the same type, hence the error which amounts to "T is not assignable to T" because I am trying to iterate an IList with T (which should work if you look at it purely from a symbolic level). The error is:
public class MegaList<T> : ObservableCollection<T>
{
public ObservableCollection<TBase> UpCast<TBase, T>() // <-- this T isn't outer T
where TBase : class
where T : TBase
{
var listUpcast = new ObservableCollection<TBase>();
foreach (T t in this.Items) // <-- error: Argument type 'T' is not assignable to parameter type 'TBase'
listUpcast.Add(t);
return listUpcast;
}
}
And since I'd have to provide T as a generic parameter, I cannot constrain it on the inner method, which @hvd supports, so unless someone knows some syntax that I don't, I'll just give up on this and either use a cast within the method, or stick with the extension method where I can type constrain it.
I honestly cannot decide if this is a feature, or a limitation. I guess it would depend on your point of view. Hope this helps someone else. At least the C# compiler should probably improve the error message so that there is an apparent difference between types. Given that one is declared in an outer scope (so to speak) I don't see why the types should be listed as Foo.T vs Foo.T, I would think the inner T would be in the symbol namespace of the outer class, and hence have a different qualified type name like MegaList.T vs MegaList.UpCast.T.