none
.NET 4.0 Generic Invariance cast concrete type to base generic type RRS feed

  • Question

  • I am faced with the following problem:

    using System;
    
    using System.Collections.Generic;
    
    using System.Collections.ObjectModel;
    
    
    
    public abstract class Record { }
    
    
    
    public abstract class TableRecord : Record { }
    
    
    
    public abstract class LookupTableRecord : TableRecord { }
    
    
    
    public sealed class UserRecord : LookupTableRecord { }
    
    
    
    public abstract class DataAccessLayer<TRecord> : IDataAccessLayer<TRecord>
    
     where TRecord : Record, new() { }
    
    
    
    public abstract class TableDataAccessLayer<TTableRecord> : DataAccessLayer<TTableRecord>, ITableDataAccessLayer<TTableRecord>
    
     where TTableRecord : TableRecord, new()
    
    {
    
     #region ITableDataAccessLayer<TTableRecord> Members
    
    
    
     public void Add(TTableRecord tableRecord)
    
     {
    
    
    
     }
    
    
    
     public TTableRecord Get(Guid id)
    
     {
    
      return null;
    
     }
    
    
    
     public void Remove(List<TTableRecord> tableRecords)
    
     {
    
    
    
     }
    
    
    
     #endregion
    
    }
    
    
    
    public abstract class LookupTableDataAccessLayer<TLookupTableRecord> : TableDataAccessLayer<TLookupTableRecord>, ILookupTableDataAccessLayer<TLookupTableRecord>
    
     where TLookupTableRecord : LookupTableRecord, new() { }
    
    
    
    public sealed class UserDataAccessLayer : LookupTableDataAccessLayer<UserRecord> { }
    
    public interface IDataAccessLayer<TRecord>
    
     where TRecord : Record { }
    
    public interface ITableDataAccessLayer<TTableRecord> : IDataAccessLayer<TTableRecord>
    
     where TTableRecord : TableRecord
    
    {
    
     void Add(TTableRecord tableRecord);
    
     TTableRecord Get(Guid id);
    
     void Remove(List<TTableRecord> tableRecords);
    
    }
    
    public interface ILookupTableDataAccessLayer<TLookupTableRecord> : ITableDataAccessLayer<TLookupTableRecord>
    
     where TLookupTableRecord : LookupTableRecord { }

     

    Now, when i try the following cast:
    UserDataAccessLayer udal = new UserDataAccessLayer();
    
    ILookupTableDataAccessLayer<LookupTableRecord> itdal = (ILookupTableDataAccessLayer<LookupTableRecord>)udal;

    The compiler says "Cannot convert type 'UserDataAccessLayer' to 'ILookupTableDataAccessLayer<LookupTableRecord>'"

    Someone please help. In essence i am trying to convert a concrete type to it's base generic type.

    • Edited by CSSS SA Thursday, May 6, 2010 1:20 PM format
    Thursday, May 6, 2010 1:15 PM

Answers

  • You need to rethink through the design, since there are competing requirements.

    Consider an AddressRecord that derives from LookupTableRecord (so it's a sibling of UserRecord).

    If you could cast UserDataAccessLayer to ILookupTableDataAccessLayer<LookupTableRecord>, then you could call Add(new AddressRecord()) on the UserDataAccessLayer object. This is clearly wrong, and the compiler is currently preventing you from making this mistake by enforcing strict compile-time type checking.

    If you really need to do this kind of casting, then you'll have to relax your Add/Remove methods to only work on base classes (e.g., throwing at runtime if the wrong type is passed in) and then use generic out variance:

      public abstract class Record { }
    
      public abstract class TableRecord : Record { }
    
      public abstract class LookupTableRecord : TableRecord { }
    
      public sealed class UserRecord : LookupTableRecord { }
    
      public abstract class DataAccessLayer<TRecord> : IDataAccessLayer<TRecord>
        where TRecord : Record, new()
      {
      }
    
      public interface IDataAccessLayer<out TRecord>
        where TRecord : Record
      {
      }
    
      public abstract class TableDataAccessLayer<TTableRecord> : DataAccessLayer<TTableRecord>,
                                    ITableDataAccessLayer<TTableRecord>
        where TTableRecord : TableRecord, new()
    
      {
        public void Add(TableRecord tableRecord)
        {
          TTableRecord record = tableRecord as TTableRecord;
          if (record == null)
          {
            throw new InvalidOperationException("Invalid type passed to Add.");
          }
        }
    
        public TTableRecord Get(Guid id)
        {
          return null;
        }
    
        public void Remove(IList<TableRecord> tableRecords)
        {
          if (tableRecords.Any(x => !(x is TTableRecord)))
          {
            throw new InvalidOperationException("Invalid type passed to Remove.");
          }
        }
      }
    
      public abstract class LookupTableDataAccessLayer<TLookupTableRecord> : TableDataAccessLayer<TLookupTableRecord>,
                                          ILookupTableDataAccessLayer
                                            <TLookupTableRecord>
        where TLookupTableRecord : LookupTableRecord, new()
      {
      }
    
      public sealed class UserDataAccessLayer : LookupTableDataAccessLayer<UserRecord>
      {
      }
    
      public interface ITableDataAccessLayer<out TTableRecord> : IDataAccessLayer<TTableRecord>
        where TTableRecord : TableRecord
    
      {
        void Add(TableRecord tableRecord); // throw InvalidOperationException if tableRecord is not a TTableRecord
    
        TTableRecord Get(Guid id);
    
        void Remove(IList<TableRecord> tableRecords); // throw InvalidOperationException if any element is not a TTableRecord
      }
    
      public interface ILookupTableDataAccessLayer<out TLookupTableRecord> : ITableDataAccessLayer<TLookupTableRecord>
        where TLookupTableRecord : LookupTableRecord
      {
      }

    At the end of the day, you can either have strict compile-time type checking (e.g., your original code) or defer it to runtime (by relaxing the parameters to base types and enforcing type checking explicitly). It's up to you to determine which approach is better for your design.

    One thing to consider is that there may be other ways to write the code that is now dependent on ITableDataAccessLayer<TableRecord>. You could make it generic on TTableRecord, so that it uses ITableDataAccessLayer<TTableRecord>. Another possibility is to make its dependency late-bound by using "dynamic".

            -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
      and How to Implement IDisposable and Finalizers: 3 Easy Rules
    Microsoft Certified Professional Developer

    How to get to Heaven according to the Bible
    • Marked as answer by eryang Friday, May 21, 2010 7:34 AM
    Friday, May 7, 2010 1:32 PM

All replies

  • You can add generic variance parameters to your generic classes if you're on .NET 4.

    Otherwise, UserDataAccessLayer implements ILookupTableDataAccessLayer<UserRecord>, which is a completely different type than ILookupTableDataAccessLayer<LookupTableRecord>.

           -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
      and How to Implement IDisposable and Finalizers: 3 Easy Rules
    Microsoft Certified Professional Developer

    How to get to Heaven according to the Bible
    Thursday, May 6, 2010 1:36 PM
  • Hi Steve Thanks for the response.

    I should have mentioned earlier that i did try using the out parameters on the data access layer interfaces. If i try and use out/in parameters, the compiler complains about invalid variance. Says that the generic constraints must be invariant, i.e, neither contravariant nor covariant.

    I have to work with all concrete data access layers as generic types in the form of either

    ITableDataAccessLayer<TableRecord> or ILookupTableDataAccessLayer<LookupTableRecord>

    I thought that because UserRecord inherits from LookupTableRecord inherits from TableRecord, this would be easy enough to do.

    Any other ideas?

    Friday, May 7, 2010 4:42 AM
  • You need to rethink through the design, since there are competing requirements.

    Consider an AddressRecord that derives from LookupTableRecord (so it's a sibling of UserRecord).

    If you could cast UserDataAccessLayer to ILookupTableDataAccessLayer<LookupTableRecord>, then you could call Add(new AddressRecord()) on the UserDataAccessLayer object. This is clearly wrong, and the compiler is currently preventing you from making this mistake by enforcing strict compile-time type checking.

    If you really need to do this kind of casting, then you'll have to relax your Add/Remove methods to only work on base classes (e.g., throwing at runtime if the wrong type is passed in) and then use generic out variance:

      public abstract class Record { }
    
      public abstract class TableRecord : Record { }
    
      public abstract class LookupTableRecord : TableRecord { }
    
      public sealed class UserRecord : LookupTableRecord { }
    
      public abstract class DataAccessLayer<TRecord> : IDataAccessLayer<TRecord>
        where TRecord : Record, new()
      {
      }
    
      public interface IDataAccessLayer<out TRecord>
        where TRecord : Record
      {
      }
    
      public abstract class TableDataAccessLayer<TTableRecord> : DataAccessLayer<TTableRecord>,
                                    ITableDataAccessLayer<TTableRecord>
        where TTableRecord : TableRecord, new()
    
      {
        public void Add(TableRecord tableRecord)
        {
          TTableRecord record = tableRecord as TTableRecord;
          if (record == null)
          {
            throw new InvalidOperationException("Invalid type passed to Add.");
          }
        }
    
        public TTableRecord Get(Guid id)
        {
          return null;
        }
    
        public void Remove(IList<TableRecord> tableRecords)
        {
          if (tableRecords.Any(x => !(x is TTableRecord)))
          {
            throw new InvalidOperationException("Invalid type passed to Remove.");
          }
        }
      }
    
      public abstract class LookupTableDataAccessLayer<TLookupTableRecord> : TableDataAccessLayer<TLookupTableRecord>,
                                          ILookupTableDataAccessLayer
                                            <TLookupTableRecord>
        where TLookupTableRecord : LookupTableRecord, new()
      {
      }
    
      public sealed class UserDataAccessLayer : LookupTableDataAccessLayer<UserRecord>
      {
      }
    
      public interface ITableDataAccessLayer<out TTableRecord> : IDataAccessLayer<TTableRecord>
        where TTableRecord : TableRecord
    
      {
        void Add(TableRecord tableRecord); // throw InvalidOperationException if tableRecord is not a TTableRecord
    
        TTableRecord Get(Guid id);
    
        void Remove(IList<TableRecord> tableRecords); // throw InvalidOperationException if any element is not a TTableRecord
      }
    
      public interface ILookupTableDataAccessLayer<out TLookupTableRecord> : ITableDataAccessLayer<TLookupTableRecord>
        where TLookupTableRecord : LookupTableRecord
      {
      }

    At the end of the day, you can either have strict compile-time type checking (e.g., your original code) or defer it to runtime (by relaxing the parameters to base types and enforcing type checking explicitly). It's up to you to determine which approach is better for your design.

    One thing to consider is that there may be other ways to write the code that is now dependent on ITableDataAccessLayer<TableRecord>. You could make it generic on TTableRecord, so that it uses ITableDataAccessLayer<TTableRecord>. Another possibility is to make its dependency late-bound by using "dynamic".

            -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
      and How to Implement IDisposable and Finalizers: 3 Easy Rules
    Microsoft Certified Professional Developer

    How to get to Heaven according to the Bible
    • Marked as answer by eryang Friday, May 21, 2010 7:34 AM
    Friday, May 7, 2010 1:32 PM
  • Hi CSSS,

    I'm writing to check the issue status, does Steve's suggestion help? please feel free to let us know if you have any concern.


    Sincerely,
    Eric
    MSDN Subscriber Support in Forum
    If you have any feedback of our support, please contact msdnmg@microsoft.com.
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
    Monday, May 10, 2010 2:26 AM