Я действительно не понимаю эту вещь со-контравариантности... У меня не может быть как обобщенных методов get, так и set?
Я думаю, что я объясню мои проблемы с некоторыми примерами..
interface IModel {}
class MyModel : IModel {}
interface IRepo<T> where T: IModel {
}
class Repo : IRepo<MyModel> {
}
// Cannot implicitly convert.. An explicit convertion exists. Missing cast?
IRepo<IModel> repo = new Repo();
Так что мне нужна ковариация..
interface IRepo<out T> where T: IModel {
}
Хорошо, это работает. Тогда я хочу использовать это:
interface IRepo<out T> where T: IModel {
T ReturnSomething();
}
class Repo : IRepo<MyModel> {
public MyModel ReturnSomething() { return default(MyModel); }
}
Все хорошо, но в репо тоже нужно вставлять объекты. С параметром out мы не можем сделать это:
// Invalid variance: The type parameter 'T' must be contravariantly valid on 'IRepo<T>.InsertSomething(T)'. 'T' is covariant.
interface IRepo<out T> where T: IModel {
T ReturnSomething();
void InsertSomething(T thing);
}
class Repo : IRepo<MyModel> {
public MyModel ReturnSomething() { return default(MyModel); }
public void InsertSomething(MyModel thing) { }
}
Поэтому я пытаюсь добавить два параметра:
interface IRepo<out TReturn, TInsert>
where TReturn : IModel
where TInsert : IModel
{
TReturn ReturnSomething();
void InsertSomething(TInsert thing);
}
И я получаю ту же ошибку, что и в самом первом примере. Я получаю ту же ошибку при использовании in TInsert
Так как же я могу поддерживать и вставку и получение?
РЕДАКТИРОВАТЬ: Итак, я нашел возможное решение, но оно далеко от оптимального
interface IRepo<out TResult> where TResult : IModel {
TResult ReturnSomething();
// I need to duplicate my constraint here..
void InsertSomething<TInsert>(TInsert thing) where TInsert : IModel;
}
class Repo : IRepo<MyModel> {
public MyModel ReturnSomething() { return default(MyModel); }
// ... And here
public void InsertSomething<T>(T thing) where T: IModel { }
}
РЕДАКТИРОВАТЬ 2: В ответ на Эрика: Это более полный пример того, что я пытаюсь достичь. Я действительно хотел бы ковариации, чтобы я мог сгруппировать экземпляры IRepo, и я все еще хочу, чтобы у них были методы добавления / обновления, использующие модель в качестве экземпляра. Я понимаю, что не могу получить безопасность типов во время компиляции для добавления элементов, но для этого случая использования мне просто нужно прочитать элементы.
interface IModel { }
class SomeModel : IModel { }
class OtherModel : IModel { }
interface IRepo<T>
{
T ReturnSomething();
void AddSomething(T thing);
}
interface ISubRepo<T> : IRepo<T> where T : IModel { }
class SomeSubRepo : ISubRepo<SomeModel> {
public SomeModel ReturnSomething() { return default(SomeModel); }
public void AddSomething(SomeModel thing) { }
}
class OtherSubRepo : ISubRepo<OtherModel> {
public OtherModel ReturnSomething() { return default(OtherModel); }
public void AddSomething(OtherModel thing) { }
}
class Program {
static void Main(string[] args)
{
ISubRepo<IModel>[] everyone = new ISubRepo<IModel>[] {
new SomeSubRepo(),
new OtherSubRepo()
};
WorkOnAll(everyone);
}
static void WorkOnAll(IEnumerable<ISubRepo<IModel>> everyone)
{
foreach(ISubRepo<IModel> repo in everyone) {
IModel model = repo.ReturnSomething();
// Etc.
}
}
}
2 ответа
Я думаю, что вам лучше всего разделить ваш интерфейс на две части:
interface IReadableRepo<out T> where T : IModel
{
T ReturnSomething();
}
interface IWritableRepo<in T> where T : IModel
{
void InsertSomething(T thing);
}
class Repo : IReadableRepo<MyModel>, IWritableRepo<MyModel>
{
...
}
Теперь вы можете создать List<IReadableRepo<IModel>>
который содержит Repo
экземпляров.
Если вы хотите вставить и вернуть объект того же типа, вам нужна инвариантность. Вы просто должны объявить свою переменную с учетом этого.
Просто используйте свой первый фрагмент с этой строкой, чтобы объявить переменную репо:
IRepo<MyModel> repo = new Repo();
Изменить: я взял необходимые 10 минут, чтобы написать необходимый код. Я могу заверить вас, что этот фрагмент кода компилируется на моем компьютере (в Visual C# Express):
public interface IModel {
}
public interface IRepo<T> where T : IModel {
T returnModel();
void putModel(T model);
}
public class MyModel : IModel {
}
public class Repo : IRepo<MyModel> {
}
public static class Program {
void main() {
IRepo<MyModel> repo = new Repo();
var model = new MyModel();
repo.putModel(model);
var model2 = repo.returnModel();
}
}