views:

560

answers:

3

Here's the scenario i am faced with:

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() { }

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 { }

public interface ILookupTableDataAccessLayer<TLookupTableRecord> : ITableDataAccessLayer<TLookupTableRecord>
    where TLookupTableRecord : LookupTableRecord { }

Now, when i try to do the following cast, it does not compile:

UserDataAccessLayer udal = new UserDataAccessLayer();
            ITableDataAccessLayer<TableRecord> itdal = (ITableDataAccessLayer<TableRecord>)udal;

However, when i do the following cast it compiles with no runtime errors:

UserDataAccessLayer udal = new UserDataAccessLayer();
            ITableDataAccessLayer<UserRecord> itdal = (ITableDataAccessLayer<UserRecord>)udal;

I really need to work with the base ITableDataAccessLayer<TableRecord> interface, as i don't know the concrete type.

Hope this is descriptive and helpfull enough to answer my question.

A: 

does this compile?

UserDataAccessLayer udal = new UserDataAccessLayer(); 
ITableDataAccessLayer<TTableRecord> itdal = (ITableDataAccessLayer<TTableRecord>)udal;

or even just

ITableDataAccessLayer<TTableRecord> itdal = new UserDataAccessLayer();

as it is a generic interface, it probably needs to know what type it is?

it would be helpful to know the error message too. that usually sheds light on the subject.

Aran Mulholland
+4  A: 

What you are trying to do is supported in .NET 4.0 but not 3.5. It's called generic covariance. What you can do instead in the meantime is create a non-generic interface called ITableDataAccessLayer (using type Object wherever you'd use T) and provide explicit interface implementation. This is how many generic types in .NET handle it.

Josh Einstein
Hi Josh, thanks for the reply. But if a create a non-generic interface, that would allow any object to be used in place of T. I would only like to allow objects that inherit from TableRecord.
Sameer Shariff
Well no, not really. It would result in a runtime error because your non-generic implementation would typically simply call the generic implementation with a cast. Alternatively, you could implement ITableDataAccessLayer<TableRecord> explicitly in UserDataAccessLayer. It's the same concept except you're just narrowing the types that the compiler will accept.
Josh Einstein
+3  A: 

Indeed, you want covariance. Couple points.

First, understand why sometimes this has to be illegal. Take IList for example. Suppose you have an IList<Giraffe>, a list of giraffes. Can you convert that to a list of animals? No, not safely. Yes, a list of giraffes is a list of animals in the sense that everything in the list is an animal. But lists are mutable; you can stick a tiger into a list of animals, but if it is really a list of giraffes then this has to fail. Since that is not safe, we will not be making IList covariant in C# 4.

Second, if this topic interests you, you might want to read my long series of blog articles on how the feature is designed to maintain type safety.

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

Third, FYI I will be posting the exact rules we use to compute when an interface can be safely covariant or contravariant on my blog in the next couple of weeks.

Eric Lippert
I know it doesn't matter at this point but I think it should've been allowed and just caused runtime exceptions. If my one and only compliant about .NET exists it's it tries to protect the developer from themselves too much. Another big example is forcing the break statement in switch statements, why can't I have case fall through if thats what i want? Or have had switch(variable, true) to allow case fall through etc.
Chris Marisic
OK. Describe how you would like the runtime to determine, at runtime, when to throw an exception. Now describe the total performance cost of your proposed change: to put a type check on EVERY write, EVERY method call, and so on. Note that 100% of the performance cost is borne by CORRECT code. And now try to sell that to a skeptical developer population who already think "managed code equals slow code". We don't catch this stuff at compile time to be paternalistic towards you, we do it because otherwise we have to make your code VERY SLOW.
Eric Lippert
As for your question about fall through: fall through was a bad idea because it is almost never what the user actually wants, and it is an invisible mistake. If you want fall through, you can easily make it explicit: just say "goto case default;" if you want to fall through to the default case.
Eric Lippert