2

I renamed the question from: "Why does my UpCast() not compile as an instance method but does as an extension?" to something a bit more useful for the future emaciated adventurer.

I originally set out to implement an UpCast() as an instance method, but eventually ended up boggling over a compiler message that didn't seem to make sense. The original question is below, with the update.

I have a container class derived from ObservableCollection. Just now I tried to write an UpCast<> generic method so that instead of writing:

Columns = new MegaList<DbTableColumn>();
Columns.AddRange( oracleDictionary.ListTableColumns(tableName) );  // IEnumerable<OracleColumn>

// or

(v.Columns = new MegaList<DbTableColumn>()).AddRange( oracleDictionary.ListTableColumns(tableName) );

// I could instead write
Columns = oracleDictionary.ListTableColumns(tableName).UpCast<DbTableColumn>();

MegaList is ObservableCollection with some added convenience methods that I won't show here. Since ObservableCollection does not have ConvertAll(), I tried this.

Basically, why doesn't the following instance method compile, yet I can implement the seemingly equivalent as an extension method (listed at the bottom), it does?

 public class MegaList<T> : ObservableCollection<T>
 {
    // ...rest of class snipped...

    public ObservableCollection<TBase> UpCast<TBase, T>()
       where TBase: class
       where T : TBase
    {
       var listUpcast = new ObservableCollection<TBase>();
       foreach (T t in this.Items) <-- Error 14 Cannot convert type 'T' to 'T' ??? Excuse me?
          listUpcast.Add(t);
       return listUpcast;
    } 
 }

I think the following is equivalent. Just exchanges the "this" parameter for the OberservableCollection.Items property, both hold type T. I am especially confused because of the type constraint that states "T must be TBase".

    static public ObservableCollection<TBase> UpCast<TBase, T>(this ObservableCollection<T> list)
      where TBase : class
      where T : TBase
    {
       var listUpcast = new ObservableCollection<TBase>();
       foreach (var t in list)
          listUpcast.Add(t);
       return listUpcast;
    } 

UPDATE: The answer is below, and I found the following to be true:

  1. C# has generic type parameter shadowing, just like regular field/parameter shadowing.
  2. I can't write a type constraint in a generic method using a type parameter from the enclosing class, because of (1) and I don't think there is a way to refer to type within a type constraint, where T is a generic type parameter.
codenheim
  • 20,467
  • 1
  • 59
  • 80
  • Can you post the definition of Items property. The type of Items property can't be inferred from the usage. – Shalin Ved Apr 18 '14 at 05:31
  • It is the standard Items property in the Collection base class. http://msdn.microsoft.com/en-us/library/ms132435(v=vs.110).aspx – codenheim Apr 18 '14 at 07:20

2 Answers2

2

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.

codenheim
  • 20,467
  • 1
  • 59
  • 80
  • Oh! You're right, I completely missed that you defined the instance method as `UpCast` instead of `UpCast`. That *should* give you a compiler warning, but even if you do get that warning, it's easy to miss if it's followed by errors. You should accept your own answer, you're entirely correct (and I'm pretty certain C# indeed doesn't support what you want to do), and I'll delete my incorrect answer. –  Apr 19 '14 at 07:45
0

Should not the definition of UpCast method be

public class MegaList<T> : ObservableCollection<T>
{
    // ...rest of class snipped...

    public ObservableCollection<TBase> UpCast<TBase>()
    {
        var listUpcast = new ObservableCollection<TBase>();
        foreach (var t in this.Items)
            listUpcast.Add((TBase)t); // <-- error: Argument type 'T' is not assignable to parameter type 'TBase'
        return listUpcast;
    }
}
Parimal Raj
  • 20,189
  • 9
  • 73
  • 110
  • According to the documentation, "where T : U" means "type argument supplied for T must be or derive from the argument supplied for U", so that is why I have "where T : TBase" .. it looks like your example is the reverse, a down-cast, no? It says where TBase derives from T or I'm reading the docs wrong. – codenheim Apr 18 '14 at 07:32