Приведение класса бетона к общему базовому интерфейсу

Вот сценарий, с которым я столкнулся:

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

Теперь, когда я пытаюсь сделать следующее приведение, оно не компилируется:

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

Однако, когда я делаю следующее приведение, оно компилируется без ошибок времени выполнения:

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

Мне действительно нужно работать с базовым интерфейсом ITableDataAccessLayer<TableRecord>, так как я не знаю конкретного типа.

Надеюсь, что это описательно и достаточно полезно, чтобы ответить на мой вопрос.


person Sameer Shariff    schedule 22.11.2009    source источник
comment
Я думаю, у вас опечатка - обе части кода компиляции/некомпиляции равны.   -  person weismat    schedule 22.11.2009
comment
Всем привет. Извините, не правильно отформатировал html. Спасибо что подметил это. Пожалуйста, просмотрите усиление поста. Спасибо!   -  person Sameer Shariff    schedule 22.11.2009
comment
они все еще одинаковы.   -  person Aran Mulholland    schedule 22.11.2009


Ответы (3)


То, что вы пытаетесь сделать, поддерживается в .NET 4.0, но не в 3.5. Это называется общая ковариация. Что вы можете сделать вместо этого, так это создать необщий интерфейс с именем ITableDataAccessLayer (используя тип Object везде, где вы бы использовали T) и предоставить явную реализацию интерфейса. Именно так с этим справляются многие универсальные типы в .NET.

person Josh    schedule 22.11.2009
comment
Привет, Джош, спасибо за ответ. Но если создать необщий интерфейс, это позволит использовать любой объект вместо T. Я хотел бы разрешить только объекты, которые наследуются от TableRecord. - person Sameer Shariff; 22.11.2009
comment
Ну нет, не совсем. Это приведет к ошибке времени выполнения, потому что ваша неуниверсальная реализация обычно просто вызывает универсальную реализацию с приведением типов. В качестве альтернативы вы можете реализовать ITableDataAccessLayer‹TableRecord› явно в UserDataAccessLayer. Это та же концепция, за исключением того, что вы просто сужаете типы, которые примет компилятор. - person Josh; 22.11.2009

Действительно, вы хотите ковариации. Пара очков.

Во-первых, поймите, почему иногда это должно быть незаконным. Возьмем, к примеру, IList. Предположим, у вас есть IList<Giraffe>, список жирафов. Вы можете преобразовать это в список животных? Нет, не безопасно. Да, список жирафов — это список животных в том смысле, что все в списке — животные. Но списки изменяемы; вы можете вставить тигра в список животных, но если это действительно список жирафов, то это должно потерпеть неудачу. Поскольку это небезопасно, мы не будем делать IList ковариантным в C# 4.

Во-вторых, если эта тема вас интересует, вы можете прочитать мою длинную серию статей в блоге о том, как эта функция разработана для обеспечения безопасности типов.

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

В-третьих, к вашему сведению, в ближайшие пару недель я опубликую в своем блоге точные правила, которые мы используем для вычисления того, когда интерфейс может быть безопасно ковариантным или контравариантным.

person Eric Lippert    schedule 22.11.2009
comment
Я знаю, что на данный момент это не имеет значения, но я думаю, что это должно было быть разрешено и просто вызывало исключения во время выполнения. Если и существует мой единственный закон о .NET, то он слишком сильно пытается защитить разработчика от самих себя. Еще один важный пример — форсирование оператора break в операторах switch. Почему я не могу пропустить случай, если это то, что я хочу? Или иметь переключатель (переменная, истина), чтобы позволить регистру провалиться и т. д. - person Chris Marisic; 03.12.2009
comment
В ПОРЯДКЕ. Опишите, как вы хотите, чтобы среда выполнения во время выполнения определяла, когда выдавать исключение. Теперь опишите общую стоимость предложенного вами изменения: проверка типа при КАЖДОЙ записи, КАЖДОМ вызове метода и т. д. Обратите внимание, что 100% затрат на производительность несет ПРАВИЛЬНЫЙ код. А теперь попытайтесь продать это скептически настроенным разработчикам, которые уже думают, что управляемый код равен медленному коду. Мы не отлавливаем эти вещи во время компиляции, чтобы быть патерналистскими по отношению к вам, мы делаем это, потому что в противном случае мы должны сделать ваш код ОЧЕНЬ МЕДЛЕННЫМ. - person Eric Lippert; 03.12.2009
comment
Что касается вашего вопроса о провале: провал был плохой идеей, потому что это почти никогда не то, что на самом деле хочет пользователь, и это невидимая ошибка. Если вы хотите провалиться, вы можете легко сделать это явным: просто скажите goto case default; если вы хотите перейти к случаю по умолчанию. - person Eric Lippert; 03.12.2009

это компилируется?

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

или даже просто

ITableDataAccessLayer<TTableRecord> itdal = new UserDataAccessLayer(); 

поскольку это общий интерфейс, вероятно, ему нужно знать, какой он тип?

было бы полезно также знать сообщение об ошибке. обычно проливает свет на тему.

person Aran Mulholland    schedule 22.11.2009