Повышение приведения к интерфейсу «Foo», когда класс реализует общий интерфейс «T» с ограничением типа T = Foo?

У меня есть интерфейс, объявляющий несколько методов (на С#):

internal interface ILetter<T> where T:IEntity
{
    List<T> GetRecords();
    DocumentParameters GetDocumentParameters(T record);
    void MarkRecordAsHandled(T record, bool letterSent);
}

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

abstract class AbstractLetter<T> : ILetter<T> where T:IEntity
{
    public abstract List<T> GetRecords();

    public abstract DocumentParameters GetDocumentParameters(T record);

    public abstract void MarkRecordAsHandled(T record, bool letterSent);

    protected readonly FamisContext FamisContext = new FamisContext();

    protected bool KnownEmail(string socialSecurityNumber){ doing stuff }
}

Наконец, у меня есть разные классы, наследуемые от вышеуказанного базового класса (AbstractLetter). Они, конечно же, включают в себя конкретные реализации методов, объявленных в интерфейсе.
Я бы хотел сделать пакетную работу, создавая разные виды писем, на основе методов из интерфейса. Я думал, что смогу сделать это, составив список, а затем перебирая список, используя одни и те же методы для каждого элемента. Но, видимо, я не могу этого сделать — по крайней мере, без явного приведения их типов.
Итак, мой вопрос: могу ли я сделать что-то вроде приведенного ниже, но без приведения типов (и, конечно, других букв)?

var test = new List<ILetter<IEntity>> {new JobTrainingLetter() as ILetter<IEntity>};

person Kasper Lethan    schedule 17.11.2014    source источник


Ответы (3)


Я не совсем уверен, что понимаю ваш вопрос, но это то, что вам нужно?

http://ideone.com/bE08rw

В Линкпаде:

interface IEntity
{
    string Name { get; set; }
}

class FooEntity : IEntity
{
    public string Name { get; set; }

    public FooEntity()
    {
        Name = "foo";
    }
}

interface ILetter<T> where T:IEntity
{
    string EntityMetadata { get; set; }
    List<T> GetRecords();
}


abstract class AbstractLetter<T> : ILetter<T> where T:IEntity
{
    public abstract string EntityMetadata { get; set; }
    public abstract List<T> GetRecords();
}

class JobLetter : AbstractLetter<FooEntity>
{
    public override string EntityMetadata { get; set; }
    public override List<FooEntity> GetRecords() { return null; }

    public JobLetter()
    {
        var entity = new FooEntity();
        EntityMetadata = entity.Name;
    }
}

List<T> CreateLetterList<T, K>() where T : AbstractLetter<K>, new() where K : IEntity
{
    return new List<T> { new T(), };
}

void Main()
{
    var jobLetterList = CreateLetterList<JobLetter, FooEntity>();
    foreach (var letter in jobLetterList)
    {
        Console.WriteLine(letter.EntityMetadata);
    }
}

Для обслуживания списка, в котором хранятся несколько типов писем:

abstract class AbstractLetter: ILetter<IEntity>
{
    public abstract string EntityMetadata { get; set; }
    public abstract List<IEntity> GetRecords();
}

class JobLetter<T> : AbstractLetter where T : IEntity, new()
{
    public override string EntityMetadata { get; set; }
    public override List<IEntity> GetRecords() { return null; }

    public JobLetter()
    {
        var entity = new T();
        EntityMetadata = entity.Name;
    }
}

class SubsidyLetter<T> : AbstractLetter where T : IEntity, new()
{
    public override string EntityMetadata { get; set; }
    public override List<IEntity> GetRecords() { return null; }

    public SubsidyLetter()
    {
        var entity = new T();
        EntityMetadata = entity.Name;
    }
}
List<T> CreateLetterList<T>() where T : AbstractLetter, new()
{
    return new List<T> { new T(), };
}

void Main()
{
    var jobLetterList = CreateLetterList<JobLetter<FooEntity>>();
    var subsidyLetterList = CreateLetterList<SubsidyLetter<FooEntity2>>();

    var mergedList = new List<AbstractLetter>();
    mergedList.Add(jobLetterList[0]);
    mergedList.Add(subsidyLetterList[0]);

    foreach (var letter in mergedList)
    {
        Console.WriteLine(letter.EntityMetadata);
    }
}

Как видите, вы потеряете некоторую информацию о типе в базовом классе.

Чтобы поддерживать то, чего вы пытаетесь достичь (но не обязательно необходимую функциональность), вам нужна действительная дисперсия ваших типов:

interface ILetter<out T> where T:IEntity
{
    string EntityMetadata { get; set; }
    IEnumerable<T> GetRecords();
}


abstract class AbstractLetter<T> : ILetter<T> where T:IEntity
{
    public abstract string EntityMetadata { get; set; }
    public abstract IEnumerable<T> GetRecords();
}

class JobLetter : AbstractLetter<FooEntity>
{
    public override string EntityMetadata { get; set; }
    public override IEnumerable<FooEntity> GetRecords() { return null; }

    public JobLetter()
    {
        var entity = new FooEntity();
        EntityMetadata = entity.Name;
    }
}

class SubsidyLetter : AbstractLetter<FooEntity2>
{
    public override string EntityMetadata { get; set; }
    public override IEnumerable<FooEntity2> GetRecords() { return null; }

    public SubsidyLetter()
    {
        var entity = new FooEntity2();
        EntityMetadata = entity.Name;
    }
}

void Main()
{
    var jobLetterList = CreateLetterList<JobLetter, FooEntity>();
    var subsidyLetterList = CreateLetterList<SubsidyLetter, FooEntity2>();
    var mergedList = new List<ILetter<IEntity>>();

    mergedList.Add(jobLetterList[0]);
    mergedList.Add(subsidyLetterList[0]);

    foreach (var letter in mergedList)
    {
        Console.WriteLine(letter.EntityMetadata);
    }
}

IEnumerable будет работать вместо списка, поскольку он не позволяет вам вводить в него значения.

person ilitirit    schedule 17.11.2014
comment
Извините, если я не был ясен в ОП. Тем не менее, вы были очень близки. Продолжая свой код, добавьте еще один FooEntity2 и класс class SubsidyLetter : AbstractLetter<FooEntity2> { }. Я хотел бы иметь список, содержащий как SubsidyLetter, так и JobLetter. Это возможно с вашим предложением? - person Kasper Lethan; 17.11.2014
comment
Я боялся этого. Я должен найти способ обойти это. Спасибо за ваше время в любом случае. Я одобрю ваше решение как правильный ответ, даже если оно не совсем соответствует тому, на что я надеялся. - person Kasper Lethan; 17.11.2014
comment
См. мое последнее редактирование о замене списка типом, поддерживающим ковариацию. - person ilitirit; 17.11.2014

Ну, во-первых, new JobTrainingLetter() as ILetter<IEntity> на самом деле недопустимо, если только вы не пометите T явно как "out", то есть ковариантный.

Если вы пометите параметр типа out, вы также должны убедиться, что доступ к T возможен только из «выходных» позиций.

На самом деле не имеет смысла рассматривать ваше письмо как ILetter<IEntity>, так как это будет означать, что у вас есть доступ к таким методам, как:

DocumentParameters GetDocumentParameters(IEntity record);

на экземпляре ILetter<YourConcreteEntity>. Что произойдет, если вы попробуете GetDocumentParameters(anotherConcreteEntity)? Исключение во время выполнения.

Я предлагаю вам подумать над своим дизайном немного больше.

person Erti-Chris Eelmaa    schedule 17.11.2014

Ваш базовый класс определяется как:

abstract class AbstractLetter<T> : ILetter<T> where T:IEntity

T связан с общим ограничением, но ограничения не являются частью сигнатур типов (а также совместное/контравариантность не поддерживается для классов, это только для интерфейсов, а не то, что у вас есть здесь в любом случае). Итак, ваш класс для компилятора с точки зрения наследования действительно выглядит примерно так *):

abstract class AbstractLetter<T> : ILetter<T>

который не сообщает компилятору, что он реализует ILetter<IEntity>.

Просто добавьте эту часть явно:

abstract class AbstractLetter<T> : ILetter<IEntity>, ILetter<T> where T:IEntity

Конечно, это может заставить вас добавить некоторый код, чтобы удовлетворить этот дополнительный интерфейс (вы можете сделать так, чтобы VS сгенерировал их для вас и заполнил проходами к текущему коду), но это будет работать.

РЕДАКТИРОВАТЬ: Или, вы можете использовать настройки дисперсии в интерфейсе, как мудро предложил ChrisEelmaa (я удивлен, что не подумал об этом, даже после упоминания дисперсии, хех!). Но это не будет работать напрямую с текущим набором методов. Я также не рекомендую создавать два интерфейса ILetter<int T> + ILetter2<out T> — это почти всегда приводит к раздуванию и очень странному и сложному коду. Слишком сложно по сравнению с несколькими слепками.

*) весь этот абзац является очень расплывчатым описанием

person quetzalcoatl    schedule 17.11.2014
comment
В конечном итоге вы снимаете ограничение времени компиляции ковариации, согласно которому T не может отображаться как в исходных, так и в исходных позициях, поэтому по определению одна из функций ILetter<IEntity> в AbstractLetter<T> должна вызывать исключение во время выполнения/добавлять явную проверку: P - person Erti-Chris Eelmaa; 17.11.2014