Я пытаюсь понять концепцию .NET Generics и на самом деле использовать их в своем собственном коде, но у меня все еще возникают проблемы.
Может кто-нибудь попытаться объяснить мне, почему следующая установка не компилируется?
public class ClassA
{
ClassB b = new ClassB();
public void MethodA<T>(IRepo<T> repo) where T : ITypeEntity
{
b.MethodB(repo);
}
}
public class ClassB
{
IRepo<ITypeEntity> repo;
public void MethodB(IRepo<ITypeEntity> repo)
{
this.repo = repo;
}
}
Я получаю следующую ошибку:
невозможно преобразовать из IRepo <'T> в IRepo <' ITypeEntity>
MethodA вызывается с параметром объекта IRepo <'DetailType>, где DetailType наследуется от ITypeEntity.
Я продолжаю думать, что это должно компилироваться, поскольку я ограничиваю T в MethodA типом ITypeEntity.
Любые мысли или отзывы были бы чрезвычайно полезны.
Спасибо.
Редактировать: Nick R имеет отличное предложение, но, к сожалению, в моем контексте у меня нет возможности сделать ClassA Generic. Хотя ClassB может быть.





Проблема непростая, чтобы разобраться в ней. DetailType может наследовать от ITypeEntity, но на самом деле не является ITypeEntity. Ваша реализация DetailType может предоставлять другую функциональность, поэтому DetailType реализует ITypeEntity, но не равен ITypeEntity. Надеюсь это имеет смысл...
Если B является подклассом A, это не означает, что Class<B> является подклассом Class<A>. Таким образом, по той же причине, если вы говорите «T - это ITypeEntity», это не означает, что «IRepo<T> - это IRepo<ITypeEntity>». Возможно, вам придется написать свой собственный метод преобразования, если вы хотите, чтобы это работало.
T - это переменная типа, которая будет привязана к используемому частичному типу. Ограничение гарантирует, что этот тип будет представлять подмножество типов, реализующих ITypeEntity, за исключением других типов, реализующих интерфейс.
во время компиляции, даже если вы ограничиваете его, компилятор знает только, что T в MethodA является ссылочным типом. он не знает, каким типом он ограничен.
Хорошо, это нормально компилируется. Я в основном переделал классы, чтобы они принимали общие параметры. Это может быть нормально в вашем контексте.
public interface IRepo<TRepo>
{
}
public interface ITypeEntity
{
}
public class ClassA<T> where T : ITypeEntity
{
ClassB<T> b = new ClassB<T>();
public void MethodA(IRepo<T> repo)
{
b.MethodB(repo);
}
}
public class ClassB<T> where T : ITypeEntity
{
IRepo<T> repo;
public void MethodB(IRepo<T> repo)
{
this.repo = repo;
}
}
К сожалению, у меня нет возможности сделать ClassA универсальным классом, поскольку это пользовательский элемент управления ASP .Net, и я действительно не хочу пытаться решить эту проблему. Тем не менее, спасибо за вашу мысль. Это было вполне разумно, учитывая ограниченный контекст, который я привел.
В таком случае нужно ли использовать дженерики? Мне кажется, что проблема в том, что ClassA содержит объект типа ClassB. Сначала попробуйте реализовать без дженериков - этого может быть достаточно.
Интерфейс IRepo является универсальным. Я не могу этого изменить. По крайней мере, я думаю, что не могу. Может, я еще разберусь с этим.
Это избыточное использование дженериков, если T может быть только экземпляром ITypeEntity, вам не следует использовать дженерики.
Обобщения нужны, когда у вас есть несколько типов, которые могут быть внутри чего-то.
Это не совсем так. T может быть любым объектом, реализующим ITypeEntity. Это правильное использование дженериков.
Да, но поскольку ITypeEntity - это интерфейс, вы можете вернуть объект обратно в ITypeEntity в любой момент. Таким образом, чтобы действительно передать что-то в этот общий аргумент, он должен реализовать ITypeEntity, в свою очередь, это "ITypeEntity".
I get the following error: cannot convert from IRepo<'T> to IRepo<'ITypeEntity>
Вы получаете эту ошибку компиляции, потому что IRepo<T> и IRepo<ITypeEntity> - это одно и то же нет. Последнее является специализацией первого. IRepo<T> - это определение универсального типа, где параметр типа T является заполнителем, а IRepo<ITypeEntity> - это сконструированный универсальный тип определения универсального типа, где параметр типа T from задан как ITypeEntity.
I keep thinking that this should compile as I'm constraining T within MethodA to be of type ITypeEntity.
Ограничение where здесь не помогает, потому что оно противоречит только типу, который вы можете предоставить для T на сайтах вызовов для MethodA.
Вот терминология из документации MSDN (см. Дженерики в .NET Framework), которая может помочь:
Определение универсального типа - это
класс, структура или интерфейс
объявление, которое функционирует как
шаблон с заполнителями для
типы, которые он может содержать или использовать.
Например, класс Dictionary<<K, V> может содержать
два типа: ключи и значения. Потому что
это всего лишь шаблон, вы не можете
создавать экземпляры класса,
структура или интерфейс, который является
определение универсального типа.
Параметры универсального типа или тип
параметры, заполнители в
определение универсального типа или метода.
Общий тип Dictionary<K, V> имеет два типа
параметры, K и V, что
представляют типы его ключей и
значения.
Сконструированный универсальный тип или сконструированный тип, является результатом определение типов для универсального параметры типа универсального типа определение.
Аргумент универсального типа - это любой тип который заменяется на общий параметр типа.
Общий термин родовой тип включает как сконструированные типы, так и определения универсального типа.
Ограничения - это ограничения, накладываемые на
параметры универсального типа. За
Например, вы можете ограничить тип
параметр для типов, реализующих
общий IComparer<T>
интерфейс, чтобы гарантировать, что экземпляры
типа можно заказать. Ты можешь
также ограничить параметры типа
типы, которые имеют определенную базу
класс, у которого есть значение по умолчанию
конструктор, или ссылки
типы или типы значений. Пользователи
общий тип не может заменить тип
аргументы, не удовлетворяющие
ограничения.
См. Вопрос @monoxide
И как я там сказал, проверка серии сообщений Эрика Липперта о контравариантности и ковариантности для дженериков многое прояснит.
При использовании дженериков наследование работает иначе. Как указывает Smashery, даже если TypeA наследуется от TypeB, myType <TypeA> не наследуется от myType <TypeB>.
Таким образом, вы не можете вызвать метод, определенный как MethodA (myType <TypeB> b), ожидая myType <TypeB>, и вместо этого дать ему myType <TypeA>. Рассматриваемые типы должны точно совпадать. Таким образом, следующее не будет компилироваться:
myType<TypeA> a; // This should be a myType<TypeB>, even if it contains only TypeA's
public void MethodB(myType<TypeB> b){ /* do stuff */ }
public void Main()
{
MethodB(a);
}
Итак, в вашем случае вам нужно будет передать IRepo <ITypeEntity> в MethodB, даже если он содержит только DetailTypes. Вам нужно будет сделать какое-то преобразование между ними. Если вы использовали общий список IList, вы могли бы сделать следующее:
public void MethodA<T>(IList<T> list) where T : ITypeEntity
{
IList<T> myIList = new List<T>();
foreach(T item in list)
{
myIList.Add(item);
}
b.MethodB(myIList);
}
Надеюсь, это поможет.
В контексте изучения общих методов позвольте мне дать вам простую универсальную функцию. Это общий эквивалент VB IIf () (Immediate if), который сам по себе является плохой имитацией тернарного оператора C-стиля (?). Он ни для чего не полезен, поскольку настоящий тернарный оператор лучше, но, возможно, он поможет вам понять, как создаются общие функции и в каких контекстах они должны применяться.
T IIF<T>(bool Expression, T TruePart, T FalsePart)
{
return Expression ? TruePart : FalsePart;
}
Если вы создаете общий класс для первого класса, второй также должен быть универсальным.