new - where t c#




Metodo generico con vincoli di tipo o parametro della classe base (4)

Se scrivo un metodo accettando un parametro che deriva da una BaseClass (o un'interfaccia), per quanto ne so ci sono due modi per farlo:

void MyMethod<T>(T obj) where T : BaseClass { ... }

e

void MyMethod(BaseClass obj) { ... }

Quali vantaggi / svantaggi ha l'uno rispetto all'altro?


Come è stato detto, conta solo una volta ottenuto un valore di ritorno. Considera questi casi:

BaseClass MyMethod(BaseClass)

DervivedClass temp = new DervivedClass();
//Error. My Method always returns a BaseClass. No implicit casting available
temp = MyMethod(temp);

Confrontalo con questo:

T MyMethod<T>(T) where T : BaseClass

DervivedClass temp = new DerivedClass();
temp = MyMethod<DerivedClass>(temp);

Strong Typification è uno dei migliori amici che hai in .NET. Abbraccialo. Non cercare mai di evitarlo. Il contrario sarebbe casi come quelli che abbiamo in PHP e JavaScript: http://www.sandraandwoo.com/2015/12/24/0747-melodys-guide-to-programming-languages/


In questo esempio non c'è una grande differenza tra i due, puoi accedere agli stessi membri all'interno del metodo e puoi chiamarlo con le stesse classi derivate. Esiste una differenza di runtime in quanto un metodo generico è compilato per ogni tipo con cui è invocato.

Dove i farmaci generici si rivelino utili sarebbe se si restituisse un valore in base a T

Con i generici si potrebbe fare quanto segue

T MyMethod<T>(T obj) where T : BaseClass { ... }
MyMethod(derivedInstance).derivedProperty

Senza questo sarebbe un errore:

BaseClass MyMethod(BaseClass obj) { ... }
MyMethod(derivedInstance).derivedProperty // error

Nota Sebbene si menzioni il vincolo a una classe base, vale la pena ricordare che se si vincola non a una classe, ma a un'interfaccia, si verificherà un ulteriore boxing se l'implementazione è eseguita da una struttura nella versione non generica, questo può avere prestazioni gravi implicazioni.


Quando T è vincolato a una classe base, non c'è molta differenza a parte ciò che è già stato detto.

Quando T è vincolato a un'interfaccia, la differenza può essere enorme:

int FrobNonGeneric(IFrobbable frob) { //... }
int Frob<T>(T frob) where T: IFrobbable { //... }

struct Frob: IFrobbable { ... }

FrobNonGeneric(new Frob()); //boxing!
Frob(new Frob()); //no boxing

Sicuramente l'esempio che hai citato non fa molta differenza se non le prestazioni di esecuzione del tempo di esecuzione come menzionato in altre risposte.

Tralasciando i benefici delle collezioni generiche (miglioramento delle prestazioni evitando ad esempio il pugilato / l'unboxing) che tutti conosciamo e usiamo frequentemente, anche Generics funziona alla grande dal punto di vista del consumatore. Ad esempio, lo snippet di codice seguente è auto esplicativo per visualizzare la flessibilità dell'utilizzo dell'API dal punto di vista del consumatore:

interface IEntity
{
   int Id {get;set;}
}

class Student : IEntity
{
   int Id {get;set;}
   string SubjectOpted {get;set;}
}

class Employee : IEntity
{
   int Id {get;set;}
   string DepartmentName{get;set;}
}

interface INonGenericRepository
{
   IEntity Get(int id)
}

interface IGenericRepository<T> where T:Entity
{
   T Get(int id)
}

class NonGenericRepository : IRepository
{
   public IEntity Get(int id) {/*implementation goes here */
}

class GenericRepository<T> : IRepository<T>
{
   public T Get(int id) {/*implementation goes here */
}

Class NonGenericStudentConsumer
{
   IEntity student = new NonGenericFRepository().Get(5);
   var Id = student.Id
   var subject = student.SubjectOpted /*does not work, you need to cast */
}

Class GenericStudentConsumer
{
   var student = new GenericFRepository<Student>().Get(5);
   var Id = student.Id
   var subject = student.SubjectOpted /*works perfect and clean */
}

Un paio di altri casi d'uso che promuovono la flessibilità mentre si usano i generici insieme ai vincoli sono:

Diciamo che voglio assicurarmi che il parametro passato al metodo implementa IAdd e IMultiply e io abbia una classe che implementa sia IAdd IMulitply come:

    public class BusinessOpeartion<T> where T : IAdd, IMultiply{
    void SomeBusinessOpeartion(T obj) { /*implementation */}
}

Se ho bisogno di passare attraverso un approccio non generico, sono costretto a creare un'interfaccia fittizia ridondante come:

interface IDummy : IAdd, IMultiply

 public class BusinessOpeartion{
        void SomeBusinessOpeartion(IDummy obj) { /*implementation */}
    }

Il precedente approccio non è più pulito?

Anche una piccola cosa è appena spuntata mentre scrivevo la risposta. Nel caso in cui sia necessario, come otterresti una nuova istanza per il tipo di parametro nel metodo:

non puoi farlo

IDummy dummy = new IDummy(); /*illegal*/

Ma con un generico si potrebbe avere; T temp = new T(); a condizione che ci sia un vincolo di new()

Inoltre, cosa succede se hai bisogno di un valore predefinito per il tipo di parametro?

non puoi farlo

var default = default(IDummy); /*illegal*/

Ma con un generico si potrebbe avere; var default = default(T)





inheritance