Qual è il motivo dell'utilizzo di un'interfaccia rispetto a un tipo genericamente limitato

15

Nei linguaggi orientati agli oggetti che supportano i parametri di tipo generico (noti anche come modelli di classe e polimorfismo parametrico, sebbene ogni nome abbia connotazioni diverse), è spesso possibile specificare un vincolo di tipo sul parametro type, tale che si discende da un altro tipo. Ad esempio, questa è la sintassi in C #:

//for classes:
class ExampleClass<T> where T : I1 {

}
//for methods:
S ExampleMethod<S>(S value) where S : I2 {
        ...
}

Quali sono i motivi per utilizzare i tipi di interfaccia effettivi su tipi vincolati da tali interfacce? Ad esempio, quali sono i motivi per rendere la firma del metodo I2 ExampleMethod(I2 value) ?

    
posta GregRos 16.03.2015 - 15:24
fonte

3 risposte

21

L'uso della versione parametrica dà

  1. Ulteriori informazioni per gli utenti della funzione
  2. Vincola il numero di programmi che puoi scrivere (controllo bug gratuito)

Come esempio casuale, supponiamo di avere un metodo che calcola le radici di un'equazione quadratica

int solve(int a, int b, int c) {
  // My 7th grade math teacher is laughing somewhere
}

E poi vuoi che funzioni su altri tipi di numeri come cose oltre a int . Puoi scrivere qualcosa come

Num solve(Num a, Num b, Num c){
  ...
}

Il problema è che questo non dice quello che vuoi. Dice

Give me any 3 things that are number like (not necessarily in the same way) and I'll give you back some sort of number

Non possiamo fare qualcosa come int sol = solve(a, b, c) se a , b e c sono int s perché non sappiamo che il metodo restituirà un int nel fine! Questo porta a qualche balletto imbarazzante con downcasting e preghiera se vogliamo usare la soluzione in un'espressione più grande.

All'interno della funzione, qualcuno potrebbe darci un float, un bigint e gradi e dovremmo aggiungerli e moltiplicarli insieme. Vorremmo rifiutare staticamente questo perché le operazioni tra queste 3 classi saranno incomprensibili. I gradi sono mod 360 quindi non sarà il caso che a.plus(b) = b.plus(a) e simili ilarità si presenteranno.

Se usiamo il polimorfismo parametrico con sottotipizzazione possiamo escludere tutto ciò perché il nostro tipo in realtà dice cosa intendiamo

<T : Num> T solve(T a, T b, T c)

O con le parole "Se mi dai un tipo che è un numero, posso risolvere equazioni con questi coefficienti".

Questo succede anche in molti altri posti. Un'altra buona fonte di esempi sono funzioni che si astraggono su una sorta di contenitore, ala reverse , sort , map , ecc.

    
risposta data 16.03.2015 - 15:39
fonte
16

What are the reasons to use actual interface types over types constrained by those interfaces?

Perché è ciò di cui hai bisogno ...

IFoo Fn(IFoo x);
T Fn<T>(T x) where T: IFoo;

sono due firme decisamente differenti. Il primo accetta qualsiasi tipo che implementa l'interfaccia e l'unica garanzia che garantisce è che il valore di ritorno soddisfi l'interfaccia.

Il secondo accetta qualsiasi tipo che implementa l'interfaccia e garantisce che restituirà almeno quel tipo di nuovo (piuttosto che qualcosa che soddisfi l'interfaccia meno restrittiva).

A volte, vuoi la garanzia più debole. A volte vuoi il più strong.

    
risposta data 16.03.2015 - 15:38
fonte
2

L'uso di generici vincolati per i parametri del metodo può consentire a un metodo di restituire il suo tipo basato su quello della cosa passata. In .NET possono anche avere vantaggi aggiuntivi. Tra questi:

  1. Un metodo che accetta un generico vincolato come parametro ref o out può essere passato una variabile che soddisfa il vincolo; al contrario, un metodo non generico con un parametro di tipo interfaccia sarebbe limitato ad accettare variabili di quel preciso tipo di interfaccia.

  2. Un metodo con parametro di tipo generico T può accettare raccolte generiche di T. Un metodo che accetta un IList<T> where T:IAnimal sarà in grado di accettare un List<SiameseCat> , ma un metodo che voleva un IList<Animal> non sarebbe in grado di farlo.

  3. Un vincolo a volte può specificare un'interfaccia in termini di tipo generico, ad es. where T:IComparable<T> .

  4. Una struttura che implementa un'interfaccia può essere mantenuta come un tipo di valore quando viene passata a un metodo che accetta un parametro generico vincolato, ma deve essere racchiusa tra parentesi quando viene passata come tipo di interfaccia. Questo può avere un enorme effetto sulla velocità.

  5. Un parametro generico può avere più vincoli, mentre non c'è altro modo per specificare un parametro di "qualche tipo che implementa sia IFoo che IBar". A volte questa può essere un'arma a doppio taglio, dal momento che il codice che ha ricevuto un parametro di tipo IFoo troverà molto difficile passarlo a un tale metodo aspettandosi un generico a doppio vincolo, anche se l'istanza in questione soddisferebbe tutti vincoli.

Se in una situazione particolare non ci sarebbe alcun vantaggio nell'usare un generico, allora accetta semplicemente un parametro del tipo di interfaccia. L'uso di un generico costringerà il sistema di tipi e JITter a svolgere un lavoro extra, quindi se non c'è alcun vantaggio non si dovrebbe farlo. D'altra parte, è molto comune che si applichi almeno uno dei suddetti vantaggi.

    
risposta data 16.03.2015 - 20:41
fonte

Leggi altre domande sui tag