Проблема с абстрактными обобщенными методами - PullRequest
1 голос
/ 15 мая 2010

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

public interface ISomeEntity { /* ... */ }
public interface ISomeOtherEntity { /* ... */ }

Эта библиотека также определяет интерфейс IRepository:

public interface IRepository<TEntity> { /* ... */ }

И, наконец, библиотекаимеет абстрактный класс RepositorySourceBase (см. ниже), который должен реализовать основной проект.Цель этого класса - позволить базовому классу захватывать новые Repository объекты во время выполнения.Поскольку необходимы определенные репозитории (в этом примере репозиторий для ISomeEntity и ISomeOtherEntity), я пытаюсь написать общие перегрузки метода GetNew<TEntity>().

Следующая реализация не компилируется (второй метод GetNew() помечается как "уже определенный", хотя предложение where отличается), но он добивается того, чего я пытаюсь достичь:

public abstract class RepositorySourceBase // This doesn't work!
{
    public abstract Repository<TEntity> GetNew<TEntity>()
        where TEntity : SomeEntity;
    public abstract Repository<TEntity> GetNew<TEntity>()
        where TEntity : SomeOtherEntity;
}

Предполагаемое использование этого класса будетбыть примерно таким:

public class RepositorySourceTester
{
    public RepositorySourceTester(RepositorySourceBase repositorySource)
    {
        var someRepository = repositorySource.GetNew<ISomeEntity>();
        var someOtherRepository = repositorySource.GetNew<ISomeOtherEntity>();
    }
}

Между тем, в моем основном проекте (который ссылается на проект библиотеки) у меня есть реализации ISomeEntity и ISomeOtherEntity:

public class SomeEntity : ISomeEntity { /* ... */ }
public class SomeOtherEntity : ISomeOtherEntity { /* ... */ }

Основной проект также имеет реализацию для IRepository<TEntity>:

public class Repository<TEntity> : IRepository<TEntity>
{
    public Repository(string message) { }
}

И самое главное, он имеет реализацию абстрактного RepositorySourceBase:

public class RepositorySource : RepositorySourceBase
{
    public override IRepository<ISomeEntity> GetNew()
    {
        return new (IRepository<ISomeEntity>)Repository<SomeEntity>(
            "stuff only I know");
    }

    public override IRepository<ISomeOtherEntity> GetNew()
    {
        return new (IRepository<ISomeEntity>)Repository<SomeOtherEntity>(
            "other stuff only I know");
    }
}

Так же, какс RepositorySourceBase второй метод GetNew() помечается как «уже определенный».


Итак, C # в основном думает, что я повторяю тот же метод, потому что нет никакого способа отличить методы от их параметолько , но если вы посмотрите на мой пример использования, мне кажется, что я смогу отличить, какой GetNew() я хочу, от параметра универсального типа, например, <ISomeEntity> или <ISomeOtherEntity>).

Что мне нужно сделать, чтобы заставить это работать?


Обновление

В итоге я решил эту проблему с помощью специально названных методов и *Параметр 1055 *.

Итак, RepositorySourceBase теперь выглядит так:

public abstract class RepositorySourceBase
{
    public abstract Repository<ISomeEntity> GetNewSomeEntity();
    public abstract Repository<ISomeOtherEntity> GetNewSomeOtherEntity();
}

И RepositorySource выглядит так:

public class RepositorySource : RepositorySourceBase
{
    public override IRepository<ISomeEntity> GetNewSomeEntity()
    {
        return new (IRepository<ISomeEntity>)Repository<SomeEntity>(
            "stuff only I know");
    }

    public override IRepository<ISomeOtherEntity> GetNewSomeOtherEntity()
    {
        return new (IRepository<ISomeEntity>)Repository<SomeOtherEntity>(
            "other stuff only I know");
    }
}

Теперь, что началосьвсе это было связано с тем, что мне был нужен общий класс RepositoryUtilizer, который мог бы получить репозиторий из источника, просто зная тип репозитория (который можно указать как параметр универсального типа).Оказывается, это было невозможно (или, по крайней мере, нелегко).Однако то, что возможно , это возможность использовать делегат Func<T, TResult> в качестве параметра, чтобы класс RepositoryUtilizer мог получить хранилище без необходимости «знать» имя метода.

Вотпример:

public class RepositoryUtilizer
{
    public DoSomethingWithRepository<TEntity>(
        Func<TRepositorySource, IRepository<TEntity>> repositoryGetter)
        {
            using (var repository = repositoryGetter(RepositorySource))
            {
                return repository.DoSomething();
            }
        }
    }
}

Ответы [ 4 ]

2 голосов
/ 15 мая 2010

Вы не можете получить эту работу, как вы планировали. Ограничения типов не могут использоваться для выбора между вашими двумя методами.

public abstract Repository<TEntity> GetNew<TEntity>()
    where TEntity : SomeEntity;

public abstract Repository<TEntity> GetNew<TEntity>()
    where TEntity : SomeOtherEntity;

Пусть

public class SomeEntity { }

public class SomeOtherEntity : SomeEntity { }

и SomeOtherEntity является допустимым аргументом типа для обоих методов, в результате чего получаются два метода с одинаковой сигнатурой.

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

1 голос
/ 15 мая 2010

Ограничения не являются частью подписи.Этот факт имеет многочисленные последствия, многие из которых, очевидно, раздражают людей до бесконечности.Некоторые из этих разветвлений и около миллиона комментариев о том, что я НЕПРАВИЛЬНО НЕПРАВИЛЬНО, см. В этой статье и сопровождающих ее комментариях.два метода с двумя разными именами.

0 голосов
/ 15 мая 2010

открытый класс RepositorySource {

static IRepository<T> IRepositorySource.GetNew<T>()

{
    if (typeof(T) == typeof(ISomeEntity))
       return (IRepository<T>)new SomeEntityRepository();
    ...
}

}

0 голосов
/ 15 мая 2010

Единственное решение, которое я могу придумать, - это определить IRepositorySource<T> интерфейс, который каждый класс RepositorySource может реализовать явно:

public interface IRepositorySource<T>
{
    IRepository<T> GetNew();
}

public class RepositorySource : IRepositorySource<ISomeEntity>, IRepositorySource<ISomeOtherEntity>
{
    IRepository<ISomeEntity> IRepositorySource<ISomeEntity>.GetNew()
    {
        ...
    }

    IRepository<ISomeOtherEntity> IRepositorySource<ISomeOtherEntity>.GetNew()
    {
        ...
    }
}

Чтобы получить доступ к этим методам, вам нужно привести экземпляр RepositorySource в требуемый тип интерфейса, например,

IRepository<IEntity> r = ((IRepositorySource<IEntity>)repositorySource).GetNew();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...