2014-09-22 13 views
9

Lassen Sie uns sagen, es gibt diese generischen Typen in C#:Allgemein Type Inference in C#

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

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

und diese konkreten Typen:

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

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

Nun ist die Definition von PersonRepository ist überflüssig: die Tatsache, dass die KeyType von Person ist int ist explizit angegeben, obwohl daraus abgeleitet werden kann, dass Person ein Subtyp von Entity<int> ist.

Es wäre schön zu können, IPersonRepository wie folgt definieren:

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

und lassen Sie die Compiler Figur heraus, dass die KeyTypeint ist. Ist es möglich?

+0

Wie soll es den 'KeyType' wissen? Können Sie die vollständige Syntax Ihrer Erwartungen zeigen? Ihre letzte Schnittstellendefinition ist nicht vollständig. –

+0

Ich glaube nicht, dass es möglich ist. Der Compiler ist nicht "schlau" genug. Ich werde es aber nicht zur Antwort machen, denn "Ich kann mir keinen Weg vorstellen, wie du es tun kannst" ist nicht Beweis genug, dass du es nicht kannst. – Falanwe

+3

Ich nehme an, die letzte Zeile des Codes sollte sein 'Schnittstelle IPersonRepository: IRepository {...} ' – Rawling

Antwort

1

Lassen Sie uns sagen, dass wir

interface IPersonRepository : IRepository<Person> { } 

erklären möchten, dass erfordern würde, dass es eine generische Schnittstelle IRepository<EntityType> mit einem Typ-Parameter ist.

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

Am Ende der ersten Zeile, beziehen Sie sich auf eine Sache KeyType genannt, die noch definiert erklärt worden ist. Es gibt keinen Typ namens "KeyType".

obwohl dies funktionieren würde:

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

Oder diese:

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

Aber man kann beide widersprüchlichen Definitionen nicht zur gleichen Zeit natürlich hat. Offensichtlich sind Sie damit nicht zufrieden, weil Sie Ihre IR-Repository-Schnittstelle so definieren möchten, dass sie auch mit anderen Schlüsseltypen funktioniert.

Nun, können Sie, wenn Sie es machen Generika in den Schlüsseltyp:

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

Es ist ein alternativer Ansatz:

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

Jetzt können Sie definieren

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

Offensichtlich würden Sie damit nicht zufrieden sein, denn Sie möchten angeben, dass die Get-Methode einezurückgeben muss, nicht irgendein Entity<int>.

Die generische Schnittstelle mit zwei Typparametern in der einzigen Lösung. Und tatsächlich gibt es eine erforderliche Beziehung zwischen ihnen, wie sie in der Einschränkung ausgedrückt wird. Aber hier gibt es keine Redundanz: Das Spezifizieren von int für den Typparameter führt nicht genügend Informationen.

Wenn wir sagen,

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

Es in der Tat Redundanz ist: Angabe des Typs Parameter int redundant ist, wenn der Typ-Parameter Person bereits angegeben wurde.

Es wäre möglich, mit einer Syntax zu arbeiten, die es ermöglicht, den KeyType abzuleiten. Zum Beispiel schlug Patrick Hoffman:

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

Während theoretisch möglich, fürchte ich, dass dies eine Menge Komplexität der Sprachspezifikation und dem Compiler hinzufügen würde, für sehr wenig Gewinn. Gibt es überhaupt einen Gewinn? Sie würden sicherlich keine Tastenanschläge speichern! Vergleichen Sie diese zwei:

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

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

Die Sprache ist was es ist, und es sieht nicht so schlecht zu mir.

+2

'interface IPersonRepository: IRepository ' Wenn man weiß, dass der erste Typparameter für 'IRepository' für 'Person' gleich sein soll, ist es explizit in der Deklaration von' IPersonRepository' zu deklarieren, da es bereits deklariert wurde: 'class Person: Entität '. Das Problem, das ich habe, besteht darin, dem Compiler mitzuteilen, dass sie tatsächlich identisch sein sollen, d. H. Den Typ aus der Deklaration von "Person" abzuleiten. – proskor

+0

@proskor Sie haben einen Punkt, ich werde meine Antwort aktualisieren. –

0

Nein, das ist nicht möglich, das zu schreiben, da es den Typ nicht ableitet (der Compiler nicht).

Es sollte möglich sein, dies zu tun (Sie müssen jedoch Teil des C# -Compiler-Teams sein, um es zu bekommen), da kein anderer Wert möglich ist, der den Wert KeyType in den Typparameter für Entity legt. Sie können keinen abgeleiteten Typ oder einen Basisklassentyp eingeben.

Wie andere kommentierte, könnte es Ihren Code komplizieren. Dies funktioniert auch nur in dem Fall, dass Entity<T> eine Klasse ist, wenn es eine Schnittstelle ist, kann es den Typ nicht ableiten, da es mehrere Implementierungen haben kann. (Vielleicht ist das der ultimative Grund, warum sie das nicht eingebaut haben)

+0

'Sie müssen ein Teil des C# -Compiler-Teams sein, um es zu bekommen' - nun, Sie könnten Roslyn immer abzweigen, wenn es Ihnen nichts ausmacht, dass Sie Ihren Code kompilieren können :) – Rawling

+2

@Rawling: I freue mich auf deine Umsetzung;) –

+0

Wie genau würde die Syntax aussehen? 'interface IRepository wobei EntityType: Entity ' bestehende Syntax mit einer gut definierten Bedeutung ist. Natürlich kann der Compiler das nicht kompilieren, wenn der Typ 'KeyType' nicht existiert. Siehe auch meine Antwort. –

1

Nein, C# 's Typ System ist nicht ausreichend fortgeschritten, um auszudrücken, was Sie wollen. Das benötigte Merkmal heißt höher kited Typ, die oft in stark typisierten funktionalen Sprachen (Haskell, OCaml, Scala) gefunden werden.

Arbeiten dem Weg zurück, Sie wollen in der Lage sein

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

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

zu schreiben, aber in C# gibt es keine Möglichkeit, die Art EntityType oder, mit anderen Worten auszudrücken, dass der Typ-Parameter einige generische Parameter hat und Verwenden Sie diesen generischen Parameter in Ihrem Code.

Randnotiz: Das Repository-Muster ist böse und muss in einem Feuer sterben.

+0

Wunderbare Antwort. Aber warum ist das Repository-Muster böse? Welche Alternativen würden Sie vorschlagen? – proskor

+0

Es gibt keinen Anwendungsfall für ein Repository-Muster. Es funktioniert gegen Sie, weil sie im Allgemeinen eine Subschnittstelle des ORM sind, das Sie verwenden, das nur Anrufe weiterleitet.Es hilft nicht beim Mocking und/oder beim Testen (Sie können nicht ernsthaft darüber spotten, wo/wann Sie herausfinden müssen, welche Ausdrücke Ihr Datenbankprovider ausführt und nicht liefert), es macht Ihren Code nicht mehr modular, fügt es eine zusätzliche Schicht von Komplexität hinzu, und in dem seltenen Fall, dass Sie Ihr ORM austauschen (ja, rechts), müssen Sie sich wahrscheinlich wegen anderer Leistungsmerkmale in die Geschäftslogik vertiefen. – Martijn

+0

Für Alternativen, verwenden Sie einfach Ihre ORM direkt, anstatt es in ein Repository zu wickeln. – Martijn