2010-05-14 3 views
1

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

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

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

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

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

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

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>).

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


Update

Я в конечном итоге решить это с помощью специально названные методы и параметр Func<T, TResult>.

Итак, 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 класс, который мог захватите репозиторий из источника, просто зная тип репозитория (который может быть указан как параметр типового типа). Оказывается, это было невозможно (или, по крайней мере, нелегко). Однако то, что is возможно использовать делегат Func<T, TResult> в качестве параметра, чтобы позволить классу RepositoryUtilizer получить репозиторий без необходимости «знать» имя метода.

Вот пример:

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

ответ

1

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

http://blogs.msdn.com/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx

Я бы решить вашу проблему, имея два метода с двумя разными именами.

+0

+1. Спасибо за ваш ответ и за ссылку, Эрик.Мне удалось добиться результата, который я искал, используя специально названные методы (как вы предлагаете) и параметр «Func», который позволяет мне использовать лямбда-выражения для получения репозитория из источника. Не совсем так красиво, как просто быть в состоянии указать тип, который я хочу, но он в основном выполняет степень «родословности», которую я искал :) – devuxer

2

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

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 является действительным аргументом типа для обоих методов, дающих два метода с одинаковой сигнатурой.

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

+0

+1 даже если это не тот ответ, который я хотел услышать :) Есть ли способ, которым я могу заставить подписи различать, не создавая экземпляр объекта? (Я ничего не могу создать, потому что класс библиотеки знает интерфейс, а не реализацию.) – devuxer

0

Единственное решение, которое я могу думать о том, чтобы определить 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(); 
0

общественного класса RepositorySource {

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

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

}

Смежные вопросы