2014-09-22 21 views
9

Diciamo che ci sono questi tipi generici in C#:Generico inferenza di tipo in C#

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

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

e questi tipi di cemento:

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

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

Ora la definizione di PersonRepository è ridondante: il fatto che il KeyType di Person è int è indicato in modo esplicito, sebbene possa essere dedotto dal fatto che Person è un sottotipo di Entity<int>.

Sarebbe bello essere in grado di definire IPersonRepository in questo modo:

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

e lasciare che la figura del compilatore che il KeyType è int. È possibile?

+0

Come dovrebbe essere in grado di conoscere il 'KeyType'? Puoi mostrare la sintassi completa di ciò che ti aspetti? La tua ultima definizione di interfaccia sembra non completa. –

+0

Non penso sia possibile. Il compilatore non è abbastanza "intelligente". Non farò una risposta però perché "Non riesco a pensare a nessun modo in cui tu possa farlo" non è una prova sufficiente che non puoi. – Falanwe

+3

Suppongo che l'ultima riga di codice dovrebbe essere l'interfaccia IPersonRepository: IRepository {...} ' – Rawling

risposta

1

Diciamo che vogliamo dichiarare

interface IPersonRepository : IRepository<Person> { } 

che avrebbe richiesto che ci sia una generica interfaccia con un tipo di parametro IRepository<EntityType>.

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

Alla fine della prima linea, si fa riferimento a una cosa chiamata KeyType, che non è stato dichiarato né definito. Non esiste un tipo chiamato "KeyType".

Questo potrebbe funzionare se:

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

O questo:

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

Ma non si può avere entrambe le definizioni contrastanti allo stesso tempo, naturalmente. Ovviamente, non sei contento di questo, perché vuoi essere in grado di definire la tua interfaccia IRpository in modo che funzioni anche con altri tipi di chiavi.

Bene, è possibile, se si rendono generico nel tipo di chiave:

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

Esiste un approccio alternativo:

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

Ora è possibile definire

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

Ovviamente, non ne saresti felice, perché vorresti affermare che il metodo Get deve restituire un Person, non solo qualsiasi Entity<int>.

L'interfaccia generica con due parametri di tipo nell'unica soluzione. E infatti, c'è una relazione richiesta tra loro, come espressa nel vincolo. Ma non c'è ridondanza qui: specificare int per il parametro type non contiene abbastanza informazioni.

Se diciamo

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

V'è infatti la ridondanza: specificando il parametro di tipo int è ridondante se è già stato specificato il parametro type Person.

Sarebbe possibile ottenere op con una sintassi che consenta di dedurre il KeyType. Ad esempio, Patrick Hoffman ha suggerito:

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

Mentre teoricamente possibile, temo che questo sarebbe aggiungere un sacco di complessità alla specifica del linguaggio e il compilatore, per molto poco guadagno. In effetti, c'è qualche guadagno? Sicuramente non staresti salvando le battute! Confronta questi due:

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

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

Il linguaggio è quello che è, e non sembra troppo male per me.

+2

'interface IPersonRepository: IRepository ' Sapendo che il primo parametro di tipo per IRepository dovrebbe essere lo stesso per 'Person' lo rende esplicitamente nella dichiarazione di' IPersonRepository' ridondante, poiché è stato già dichiarato: 'class Persona: Entità '. Il problema che ho è di dire al compilatore che si suppone che siano uguali, cioè inferire il tipo dalla dichiarazione di 'Persona'. – proskor

+0

@proskor Hai un punto, aggiornerò la mia risposta. –

0

No, non è possibile scrivere quello, poiché non deduce il tipo (il compilatore no).

Dovrebbe essere possibile farlo (è necessario essere parte del team del compilatore C# per ottenerlo), poiché non è possibile altro valore che il valore di KeyType immetta nel parametro type per Entity. Non puoi inserire un tipo derivato o un tipo di classe base.

Come altri hanno commentato, potrebbe complicare ulteriormente il codice. Inoltre, questo funziona solo nel caso in cui Entity<T> sia una classe, quando è un'interfaccia non può inferire il tipo poiché può avere più implementazioni. (Forse è la ragione ultima per cui non hanno costruito questo in)

+0

'devi essere parte del team del compilatore C# anche se per farlo entrare - beh, potresti sempre sborsare Roslyn, se non ti dispiace che nessun altro possa compilare il tuo codice :) – Rawling

+2

@Rawling: I attendo con impazienza la tua implementazione;) –

+0

Che aspetto avrebbe la sintassi? 'interfaccia IRepository dove EntityType: Entity ' è sintassi esistente con un significato ben definito. Naturalmente, il compilatore non può compilare che se il tipo 'KeyType' non esiste. Vedi anche la mia risposta. –

1

No, il sistema di tipo C# non è sufficientemente avanzato per esprimere quello che vuoi. La funzionalità necessaria è denominata di tipo più elevato che si trovano spesso in linguaggi funzionali fortemente tipizzati (Haskell, OCaml, Scala).

Lavorare la via del ritorno, si vuole essere in grado di scrivere

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

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

ma in C# non c'è modo per esprimere l'EntityType tipo o, in altre parole, che il parametro tipo ha alcuni parametri generici e usa quel parametro generico nel tuo codice.

Nota a margine: il modello di deposito è il Male e deve morire in un incendio.

+0

Risposta meravigliosa. Ma perché il modello di deposito è malvagio? Quali alternative suggeriresti? – proskor

+0

Non esiste un caso per un modello di repository. Funziona contro di te perché generalmente sono una sottointerfaccia dell'ORM che stai utilizzando al di sotto delle chiamate appena in avanti.Non aiuta con il mocking e/o il testing (non si può prendere in giro seriamente dove/ottenere quando si deve prendere in considerazione le espressioni che il provider di database fa e non fornisce), non rende il codice più modulare, aggiunge un ulteriore livello di complessità e, nei rari casi in cui si sta sostituendo l'ORM (sì, giusto), è probabilmente necessario approfondire la logica di business in ogni caso a causa dei diversi caratteri delle prestazioni. – Martijn

+0

Per quanto riguarda le alternative, è sufficiente utilizzare direttamente l'ORM piuttosto che avvolgerlo in un repository. – Martijn