2014-09-22 4 views
9

Скажем, есть эти общие типы в C#:Родовой тип Умозаключение в C#

class Entity<KeyType> 
{ 
    public KeyType Id { get; private set; } 
    ... 
} 

interface IRepository<KeyType, EntityType> where EntityType : Entity<KeyType> 
{ 
    EntityType Get(KeyType id); 
    ... 
} 

и эти конкретные типы:

class Person : Entity<int> { ... } 

interface IPersonRepository : IRepository<int, Person> { ... } 

Теперь определение PersonRepository является излишним: тот факт, что KeyType от Person составляет int, указывается явно, хотя это можно сделать из того факта, что Person является подтипом Entity<int>.

Было бы хорошо, чтобы быть в состоянии определить IPersonRepository так:

interface IPersonRepository : IRepository<Person> { ... } 

и пусть фигурку компилятор, что KeyType является int. Является ли это возможным?

+0

Как он должен знать «KeyType»? Можете ли вы показать полный синтаксис того, что вы ожидаете? Последнее определение интерфейса выглядит неполным. –

+0

Я не думаю, что это возможно. Компилятор недостаточно «умный». Я не стану отвечать, хотя, потому что «я не могу думать о том, как вы это делаете», недостаточно доказательств, которого вы не можете. – Falanwe

+3

Я предполагаю, что последняя строка кода должна быть 'interface IPersonRepository: IRepository {...}' – Rawling

ответ

1

Допустим, мы хотим объявить

interface IPersonRepository : IRepository<Person> { } 

Это потребовало бы, что существует общий интерфейс с одним параметром типа IRepository<EntityType>.

interface IRepository<EntityType> where EntityType : Entity<KeyType> 
{ 
    EntityType Get(KeyType id); 
} 

В конце первой строки, вы имеете в виду то, что называется KeyType, который не был заявлен ни определен. Нет типа «KeyType».

Это будет работать, хотя:

interface IRepository<EntityType> where EntityType : Entity<int> 
{ 
    EntityType Get(int id); 
} 

Или это:

interface IRepository<EntityType> where EntityType : Entity<string> 
{ 
    EntityType Get(string id); 
} 

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

Ну, вы можете, если вы сделаете это родовое в типе ключа:

interface IRepository<KeyType, EntityType> where EntityType : Entity<KeyType> 
{ 
    EntityType Get(KeyType id); 
} 

Существует альтернативный подход:

interface IRepository<KeyType> 
{ 
    EntityType<KeyType> Get(KeyType id); 
} 

Теперь вы можете определить

class PersonRepository : IRepository<int> 
{ 
    public EntityType<int> Get(int id) { ... } 
} 

Очевидно, вы не были бы этому довольны, потому что вы хотели бы заявить, что метод Get должен вернуть Person, а не только Entity<int>.

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

Если мы говорим

class PersonRepository : IRepository<int, Person> 
{ 
    public Person Get(int id) { ... } 
} 

Существует действительно избыточность: определение параметра типа int является избыточным, когда уже был указан параметр типа Person.

Можно было бы получить op с синтаксисом, позволяющим вывести KeyType. Например, Патрик Хоффман предложил:

class PersonRepository : IRepository<EntityType: Person>. 
{ 
    public Person Get(int id) { ... } 
} 

Хотя теоретически возможно, я боюсь, что это добавит много сложностей в спецификации языка и компилятора, очень мало выгоды. На самом деле, есть ли вообще прибыль? Вы, конечно, не сохранили бы нажатия клавиш! Сравните эти два:

// current syntax 
class PersonRepository : IRepository<int, Person> 
{ 
    public Person Get(int id) { ... } 
} 

// proposed syntax 
class PersonRepository : IRepository<EntityType: Person> 
{ 
    public Person Get(int id) { ... } 
} 

Язык такой, какой он есть, и для меня это не выглядит слишком плохо.

+2

'interface IPersonRepository: IRepository ' Зная, что параметр первого типа для 'IRepository' должен быть одинаковым для' Person', делает его явно указанным в объявлении «IPersonRepository» избыточным, поскольку он уже объявлен: 'class Лицо: Объект '. Проблема заключается в том, чтобы сообщить компилятору, что они действительно должны быть одинаковыми, т. Е. Вывести тип из объявления «Личность». – proskor

+0

@proskor У вас есть точка, я обновлю свой ответ. –

0

Нет, это невозможно написать, поскольку он не выводит тип (компилятор не делает).

Должно быть возможным это сделать (вам нужно быть частью команды компилятора C#, хотя и получить ее), поскольку нет другого значения, которое могло бы иметь значение KeyType, введенное в параметр типа для Entity. Вы не можете вводить производный тип или тип базового класса.

Как прокомментировали другие, это может усложнить ваш код. Кроме того, это работает только в том случае, если Entity<T> является классом, когда он является интерфейсом, он не может вывести тип, поскольку он может иметь несколько реализаций. (Возможно, это и есть главная причина, по которой они не построили это)

+0

'вам нужно быть частью команды компилятора C#, хотя, чтобы получить ее, - ну, вы всегда можете разветвить Roslyn, если вы не возражаете, чтобы никто не мог скомпилировать ваш код :) – Rawling

+2

@ Rawling: I с нетерпением ожидаем вашей реализации;) –

+0

Что именно будет выглядеть синтаксис? 'interface IRepository где EntityType: Entity ' существующий синтаксис с четко определенным значением. Конечно, компилятор не может скомпилировать это, если тип 'KeyType' не существует. См. Также мой ответ. –

1

Нет, система типа C# недостаточно развита, чтобы выразить то, что вы хотите. Необходимая функция называется более высокий тип типа, который часто встречается в строго типизированных функциональных языках (Haskell, OCaml, Scala).

Работа наш путь назад, вы хотите, чтобы иметь возможность написать

interface IRepository<EntityType<EntityKey>> { 
    EntityType<EntityKey> Get(KeyType id); 
} 

interface PersonRepository : IRepository<Person> { 
    Person Get(Int id); 
} 

, но в C# нет никакого способа, чтобы выразить доброе EntityType или, другими словами, что параметр типа имеет некоторые общие параметры и используйте этот общий параметр в своем коде.

Сторона примечания: образец хранилища Зло и должен умереть в огне.

+0

Замечательный ответ. Но почему шаблон хранилища злой? Какие альтернативы вы бы предложили? – proskor

+0

Нет никакой информации о шаблоне репозитория. Он работает против вас, потому что обычно они являются дополнительным интерфейсом ORM, который вы используете под тем, который только переадресовывает вызовы.Это не помогает с издевательством и/или тестированием (вы не можете серьезно высмеивать, где/получить, когда вам приходится принимать участие, чтобы учитывать, какие выражения использует ваш поставщик баз данных и не предоставляет), это не делает ваш код более модульный, он добавляет дополнительный уровень сложности, и в редком случае вы заменяете свой ORM (да, правильно), вам, вероятно, придется вникать в бизнес-логику в любом случае из-за разных характеристик производительности. – Martijn

+0

Что касается альтернатив, просто используйте ORM напрямую, а не обертывайте его в репозиторий. – Martijn

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