Pregunta

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.
¿Fue útil?

Solución

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.

Otros consejos

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;
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top