Как мне применить дженерики к следующему методу интерфейса, учитывая текущий код?

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

Скажем, у меня есть следующий абстрактный класс:

public abstract class Entity<TId> : IEquatable<Entity<TId>>
{
    public TId Id { get; protected set; }
    // plus implementation of IEquatable, GetHashCode override, etc.
}

Другой вопрос, который я задал, касался реализации интерфейса для команд сущностей, который теперь выглядит так:

public interface ICommandEntities
{
    void Create<TId>(Entity<TId> entity);
    void Update<TId>(Entity<TId> entity);
    void Delete<TId>(Entity<TId> entity);
    void Reload<TId>(Entity<TId> entity);
}

Раньше я пытался выяснить, как это сделать с ограничениями универсального типа, чтобы ограничить аргументы метода типом Entity<TId>. Ответы на предыдущий вопрос сказали мне, что мне не нужно этого делать, я могу просто использовать свой абстрактный класс в качестве аргумента метода.

Однако я до сих пор не понимаю, как я могу сделать следующее, когда метод интерфейса не имеет аргументов?

public interface IQueryEntities
{
    IQueryable<TEntity> Query<TEntity>() where TEntity : ???????;
}

При вызове этого метода я хочу передать только один общий аргумент, например:

var queryable1 = queries.Query<EntityAbc>();
var queryable2 = queries.Query<EntityXyz>();

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

public class EntityAbc : Entity<string> {...}
public class EntityXyz : Entity<int> {...}

Как я могу ограничить метод интерфейса, чтобы он позволял передавать в качестве универсального аргумента только классы, которые расширяются от Entity<TId>?


person danludwig    schedule 26.11.2013    source источник


Ответы (2)


Измените свой интерфейс на общий:

public interface IQueryEntities<TId>
{
    IQueryable<TEntity> Query<TEntity>() where TEntity : Entity<TId>;
}

а затем, когда вы его реализуете:

public class EntityAbc : Entity<string>, IQueryEntities<string>

или, может быть, событие его:

public abstract class Entity<TId> : IEquatable<Entity<TId>>, IQueryEntities<TId>

Итак, чтобы обновить, чтобы отразить комментарии. Сделать этот интерфейс универсальным по-прежнему остается подходом. Если вам не нужны объекты, реализующие интерфейс, нет проблем, вы можете сделать это:

public class QueryEntities<TId> : IQueryEntities<TId>
{
    public IQueryable<TEntity> Query<TEntity>()
        where TEntity : Entity<TId>
    {
        ...
    }
}

Использование этого может выглядеть так:

var queries = new QueryEntities<string>();
queries.Query<EntityAbc>();

Но вы не сможете отправить любую сущность в этот интерфейс, если не сделаете сам метод получающим TIn. Если бы вы сделали сам метод универсальным, код, конечно, был бы таким:

var queries = new QueryEntities();
queries.Query<EntityAbc, string>();

интерфейс для этого выглядит так:

public interface IQueryEntities
{
    IQueryable<TEntity> Query<TEntity, TId>() where TEntity : Entity<TId>;
}

но ты этого не хочешь.

person Mike Perrenoud    schedule 26.11.2013
comment
Я не хочу, чтобы какие-либо классы Entity реализовывали интерфейс IQueryEntities. Реализация интерфейса IQueryEntities будет в другом классе и должна обрабатывать любое значение для TId, а не только строки или целые числа. - person danludwig; 26.11.2013
comment
@danludwig, так как же вы ожидаете передать entity в качестве универсального типа и при этом ограничить IQueryEntities, который реализован в каком-то неизвестном классе, который совершенно не связан с данным кодом? - person Mike Perrenoud; 26.11.2013
comment
@danludwig, в качестве примечания, создание универсального интерфейса IQueryEntities по-прежнему будет подходом. Даже если его реализовал другой класс. Этот класс будет чем-то вроде public class Queries<TId> : IQueryEntities<TId>. - person Mike Perrenoud; 26.11.2013
comment
Ну, может быть, я недостаточно знаю об общих ограничениях, чтобы знать, возможно ли то, что я хочу сделать. Если возможно, я хочу, чтобы компилятор запретил кому-либо вызывать QueryEntitiesImpl.Query<ClassThatDoesNotInheritFromEntity>() - person danludwig; 26.11.2013
comment
@danludwig, сделав универсальный инструмент реализации или метод, это возможно. Обратите внимание на ограничение, которое я наложил на метод Query в интерфейсе — where TEntity : Entity<TId>; — и поэтому оно ограничено. Класс должен будет, по крайней мере, наследоваться от Entity. - person Mike Perrenoud; 26.11.2013
comment
Верно, но, как вы сказали, у меня должны быть разные реализации IQueryEntities для каждого TId, верно? Если это единственное решение, мне, возможно, придется вернуться к моему предыдущему решению, в котором необщий абстрактный базовый класс был выше общего абстрактного базового класса... - person danludwig; 26.11.2013
comment
@danludwig, вам нужно создать правильный интерфейс IQueryEntities для сущности, которую вы собираетесь отправить, или сделать метод универсальным вместо этого (последний пример, который я вам дал), в котором вы можете отправить любую сущность, потому что вы собираетесь указать TIn при вызове Query. - person Mike Perrenoud; 26.11.2013
comment
Итак, вы говорите, что я не могу иметь одну реализацию интерфейса для всех сущностей и избежать указания второго универсального аргумента при вызове его методов. Если вы говорите, что это невозможно сделать, это ответ, который я ищу. - person danludwig; 26.11.2013
comment
@danludwig, да, это невозможно сделать, потому что в метод не передается параметр, который помог бы ему вывести тип. Поэтому, поскольку inference не может произойти, вы должны указать тип самостоятельно. - person Mike Perrenoud; 26.11.2013
comment
давайте продолжим обсуждение в чате - person danludwig; 26.11.2013

Не уверен, что правильно понял ваш вопрос, просто дикое предположение, сработает ли это для вас?:

IQueryable<Entity<TId>> Query<TId>();
person Andriy Tylychko    schedule 26.11.2013