Я действительно не понимаю эту вещь со-контравариантности ... У меня не может быть и общих методов get и set? - PullRequest
9 голосов
/ 09 ноября 2010

Я думаю, что я объясню свои проблемы с некоторыми примерами ..

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

Так как же я могу поддерживать как вставку, так и выборку?

EDIT : Итак, я нашел возможное решение, но это далеко от оптимального

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

EDIT2 : В ответ Эрику: это более полный пример того, чего я пытаюсь достичь.Я действительно хотел бы ковариации, чтобы я мог сгруппировать экземпляры 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 ]

2 голосов
/ 10 ноября 2010

Я думаю, что вам лучше всего разделить ваш интерфейс на две части:

    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 экземпляров.

2 голосов
/ 09 ноября 2010

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

Просто используйте свой первый фрагмент с этой строкой, чтобы объявить переменную репо:

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();
    }
}
...